Fixed keep-alive header and broken connection handling
This commit is contained in:
parent
9dd87a62ad
commit
8cc028764d
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
uvloop
|
||||||
|
httptools
|
||||||
|
ujson
|
@ -1,2 +1 @@
|
|||||||
from .sanic import Sanic
|
from .sanic import Sanic
|
||||||
from .server import Response
|
|
@ -1,6 +1,47 @@
|
|||||||
import ujson
|
import ujson
|
||||||
|
|
||||||
from .server import Response
|
STATUS_CODES = {
|
||||||
|
200: 'OK',
|
||||||
|
404: 'Not Found'
|
||||||
|
}
|
||||||
|
class HTTPResponse:
|
||||||
|
__slots__ = ('body', 'status', 'content_type')
|
||||||
|
|
||||||
|
def __init__(self, body='', status=200, content_type='text/plain'):
|
||||||
|
self.content_type = 'text/plain'
|
||||||
|
self.body = body
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body_bytes(self):
|
||||||
|
body_type = type(self.body)
|
||||||
|
if body_type is str:
|
||||||
|
body = self.body.encode('utf-8')
|
||||||
|
elif body_type is bytes:
|
||||||
|
body = self.body
|
||||||
|
else:
|
||||||
|
body = b'Unable to interpret body'
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
def output(self, version="1.1", keep_alive=False):
|
||||||
|
body = self.body_bytes
|
||||||
|
return b''.join([
|
||||||
|
'HTTP/{} {} {}\r\n'.format(version, self.status, STATUS_CODES.get(self.status, 'FAIL')).encode('latin-1'),
|
||||||
|
'Content-Type: {}\r\n'.format(self.content_type).encode('latin-1'),
|
||||||
|
'Content-Length: {}\r\n'.format(len(body)).encode('latin-1'),
|
||||||
|
'Connection: {}\r\n'.format('keep-alive' if keep_alive else 'close').encode('latin-1'),
|
||||||
|
b'\r\n',
|
||||||
|
body,
|
||||||
|
#b'\r\n'
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def error_404(request, *args):
|
||||||
|
return HTTPResponse("404!", status=404)
|
||||||
|
error_404.is_async = False
|
||||||
|
|
||||||
def json(input):
|
def json(input):
|
||||||
return Response(ujson.dumps(input), content_type="application/json")
|
return HTTPResponse(ujson.dumps(input), content_type="application/json")
|
||||||
|
def text(input):
|
||||||
|
return HTTPResponse(input, content_type="text/plain")
|
@ -12,4 +12,8 @@ class Router():
|
|||||||
self.routes[route] = handler
|
self.routes[route] = handler
|
||||||
|
|
||||||
def get(self, uri):
|
def get(self, uri):
|
||||||
return self.routes.get(uri.decode('utf-8'), self.default)
|
handler = self.routes.get(uri.decode('utf-8'), self.default)
|
||||||
|
if handler:
|
||||||
|
return handler
|
||||||
|
else:
|
||||||
|
return self.default
|
@ -1,15 +1,16 @@
|
|||||||
import inspect
|
import inspect
|
||||||
from .router import Router
|
from .router import Router
|
||||||
from .server import Response, serve
|
from .response import HTTPResponse, error_404
|
||||||
|
from .server import serve
|
||||||
from .log import log
|
from .log import log
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
name = None
|
name = None
|
||||||
routes = []
|
routes = []
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name, router=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.router = Router(default=self.handler_default)
|
self.router = router or Router(default=error_404)
|
||||||
|
|
||||||
def route(self, *args, **kwargs):
|
def route(self, *args, **kwargs):
|
||||||
def response(handler):
|
def response(handler):
|
||||||
@ -21,6 +22,3 @@ class Sanic:
|
|||||||
|
|
||||||
def run(self, host="127.0.0.1", port=8000, debug=False):
|
def run(self, host="127.0.0.1", port=8000, debug=False):
|
||||||
return serve(router=self.router, host=host, port=port, debug=debug)
|
return serve(router=self.router, host=host, port=port, debug=debug)
|
||||||
|
|
||||||
def handler_default(self, request, *args):
|
|
||||||
return Response("404!", status=404)
|
|
||||||
|
@ -16,6 +16,7 @@ from socket import *
|
|||||||
|
|
||||||
from .log import log
|
from .log import log
|
||||||
from .config import LOGO
|
from .config import LOGO
|
||||||
|
from .response import HTTPResponse
|
||||||
|
|
||||||
PRINT = 0
|
PRINT = 0
|
||||||
|
|
||||||
@ -29,41 +30,6 @@ class Request:
|
|||||||
self.version = version
|
self.version = version
|
||||||
self.method = method
|
self.method = method
|
||||||
|
|
||||||
STATUS_CODES = {
|
|
||||||
200: 'OK',
|
|
||||||
404: 'Not Found'
|
|
||||||
}
|
|
||||||
class Response:
|
|
||||||
__slots__ = ('body', 'status', 'content_type')
|
|
||||||
|
|
||||||
def __init__(self, body='', status=200, content_type='text/plain'):
|
|
||||||
self.content_type = 'text/plain'
|
|
||||||
self.body = body
|
|
||||||
self.status = status
|
|
||||||
|
|
||||||
@property
|
|
||||||
def body_bytes(self):
|
|
||||||
body_type = type(self.body)
|
|
||||||
if body_type is str:
|
|
||||||
body = self.body.encode('utf-8')
|
|
||||||
elif body_type is bytes:
|
|
||||||
body = self.body
|
|
||||||
else:
|
|
||||||
body = b'Unable to interpret body'
|
|
||||||
|
|
||||||
return body
|
|
||||||
|
|
||||||
def output(self, version):
|
|
||||||
body = self.body_bytes
|
|
||||||
return b''.join([
|
|
||||||
'HTTP/{} {} {}\r\n'.format(version, self.status, STATUS_CODES.get(self.status, 'FAIL')).encode('latin-1'),
|
|
||||||
'Content-Type: {}\r\n'.format(self.content_type).encode('latin-1'),
|
|
||||||
'Content-Length: {}\r\n'.format(len(body)).encode('latin-1'),
|
|
||||||
b'\r\n',
|
|
||||||
body
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class HttpProtocol(asyncio.Protocol):
|
class HttpProtocol(asyncio.Protocol):
|
||||||
|
|
||||||
__slots__ = ('loop',
|
__slots__ = ('loop',
|
||||||
@ -85,6 +51,8 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
def connection_made(self, transport):
|
def connection_made(self, transport):
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
|
#TODO: handle keep-alive/connection timeout
|
||||||
|
|
||||||
# TCP Nodelay
|
# TCP Nodelay
|
||||||
# I have no evidence to support this makes anything faster
|
# I have no evidence to support this makes anything faster
|
||||||
# So I'll leave it commented out for now
|
# So I'll leave it commented out for now
|
||||||
@ -108,7 +76,12 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
self.headers = []
|
self.headers = []
|
||||||
self.parser = httptools.HttpRequestParser(self)
|
self.parser = httptools.HttpRequestParser(self)
|
||||||
|
|
||||||
|
try:
|
||||||
|
#print(data)
|
||||||
self.parser.feed_data(data)
|
self.parser.feed_data(data)
|
||||||
|
except httptools.parser.errors.HttpParserError:
|
||||||
|
#log.error("Invalid request data, connection closed")
|
||||||
|
self.transport.close()
|
||||||
|
|
||||||
def on_url(self, url):
|
def on_url(self, url):
|
||||||
self.url = url
|
self.url = url
|
||||||
@ -124,6 +97,10 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
version=self.parser.get_http_version(),
|
version=self.parser.get_http_version(),
|
||||||
method=self.parser.get_method()
|
method=self.parser.get_method()
|
||||||
)
|
)
|
||||||
|
global n
|
||||||
|
n += 1
|
||||||
|
self.n = n
|
||||||
|
#print("res {} - {}".format(n, self.request))
|
||||||
self.loop.call_soon(self.handle, self.request)
|
self.loop.call_soon(self.handle, self.request)
|
||||||
|
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
@ -138,13 +115,20 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
future.add_done_callback(self.handle_result)
|
future.add_done_callback(self.handle_result)
|
||||||
else:
|
else:
|
||||||
response = handler(request)
|
response = handler(request)
|
||||||
self.write_response(response)
|
self.write_response(request, response)
|
||||||
|
|
||||||
def write_response(self, response):
|
def write_response(self, request, response):
|
||||||
self.transport.write(response.output(request.version))
|
#print("response - {} - {}".format(self.n, self.request))
|
||||||
|
try:
|
||||||
if not self.parser.should_keep_alive():
|
keep_alive = self.parser.should_keep_alive()
|
||||||
|
self.transport.write(response.output(request.version, keep_alive))
|
||||||
|
#print("KA - {}".format(self.parser.should_keep_alive()))
|
||||||
|
if not keep_alive:
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
|
except:
|
||||||
|
log.error("Writing request failed, connection closed")
|
||||||
|
self.transport.close()
|
||||||
|
|
||||||
self.parser = None
|
self.parser = None
|
||||||
self.request = None
|
self.request = None
|
||||||
|
|
||||||
@ -153,12 +137,12 @@ class HttpProtocol(asyncio.Protocol):
|
|||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
|
|
||||||
async def handle_response(self, future, handler, request):
|
async def handle_response(self, future, handler, request):
|
||||||
result = await handler(request)
|
response = await handler(request)
|
||||||
future.set_result(result)
|
future.set_result((request, response))
|
||||||
|
|
||||||
def handle_result(self, future):
|
def handle_result(self, future):
|
||||||
response = future.result()
|
request, response = future.result()
|
||||||
self.write_response(response)
|
self.write_response(request, response)
|
||||||
|
|
||||||
|
|
||||||
def abort(msg):
|
def abort(msg):
|
||||||
|
15
test.go
Normal file
15
test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8000", nil)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user