Reformatted code to use spaces instead of tabs
This commit is contained in:
@@ -1 +1 @@
|
||||
from .sanic import Sanic
|
||||
from .sanic import Sanic
|
||||
|
||||
@@ -20,5 +20,5 @@ class Config:
|
||||
▌ ▐ ▀▀▄▄▄▀
|
||||
▀▀▄▄▀
|
||||
"""
|
||||
REQUEST_MAX_SIZE = 100000000 # 100 megababies
|
||||
REQUEST_TIMEOUT = 60 # 60 seconds
|
||||
REQUEST_MAX_SIZE = 100000000 # 100 megababies
|
||||
REQUEST_TIMEOUT = 60 # 60 seconds
|
||||
|
||||
@@ -1,43 +1,51 @@
|
||||
from .response import text
|
||||
from traceback import format_exc
|
||||
|
||||
|
||||
class SanicException(Exception):
|
||||
def __init__(self, message, status_code=None):
|
||||
super().__init__(message)
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
def __init__(self, message, status_code=None):
|
||||
super().__init__(message)
|
||||
if status_code is not None:
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class NotFound(SanicException):
|
||||
status_code = 404
|
||||
status_code = 404
|
||||
|
||||
|
||||
class InvalidUsage(SanicException):
|
||||
status_code = 400
|
||||
status_code = 400
|
||||
|
||||
|
||||
class ServerError(SanicException):
|
||||
status_code = 500
|
||||
status_code = 500
|
||||
|
||||
|
||||
class Handler:
|
||||
handlers = None
|
||||
def __init__(self, sanic):
|
||||
self.handlers = {}
|
||||
self.sanic = sanic
|
||||
handlers = None
|
||||
|
||||
def add(self, exception, handler):
|
||||
self.handlers[exception] = handler
|
||||
def __init__(self, sanic):
|
||||
self.handlers = {}
|
||||
self.sanic = sanic
|
||||
|
||||
def response(self, request, exception):
|
||||
"""
|
||||
Fetches and executes an exception handler and returns a reponse object
|
||||
:param request: Request
|
||||
:param exception: Exception to handle
|
||||
:return: Response object
|
||||
"""
|
||||
handler = self.handlers.get(type(exception), self.default)
|
||||
response = handler(request=request, exception=exception)
|
||||
return response
|
||||
def add(self, exception, handler):
|
||||
self.handlers[exception] = handler
|
||||
|
||||
def default(self, request, exception):
|
||||
if issubclass(type(exception), SanicException):
|
||||
return text("Error: {}".format(exception), status=getattr(exception, 'status_code', 500))
|
||||
elif self.sanic.debug:
|
||||
return text("Error: {}\nException: {}".format(exception, format_exc()), status=500)
|
||||
else:
|
||||
return text("An error occurred while generating the request", status=500)
|
||||
def response(self, request, exception):
|
||||
"""
|
||||
Fetches and executes an exception handler and returns a reponse object
|
||||
:param request: Request
|
||||
:param exception: Exception to handle
|
||||
:return: Response object
|
||||
"""
|
||||
handler = self.handlers.get(type(exception), self.default)
|
||||
response = handler(request=request, exception=exception)
|
||||
return response
|
||||
|
||||
def default(self, request, exception):
|
||||
if issubclass(type(exception), SanicException):
|
||||
return text("Error: {}".format(exception), status=getattr(exception, 'status_code', 500))
|
||||
elif self.sanic.debug:
|
||||
return text("Error: {}\nException: {}".format(exception, format_exc()), status=500)
|
||||
else:
|
||||
return text("An error occurred while generating the request", status=500)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s: %(levelname)s: %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Middleware:
|
||||
def __init__(self, process_request=None, process_response=None):
|
||||
self.process_request = process_request
|
||||
self.process_response = process_response
|
||||
def __init__(self, process_request=None, process_response=None):
|
||||
self.process_request = process_request
|
||||
self.process_response = process_response
|
||||
|
||||
@@ -6,20 +6,25 @@ from ujson import loads as json_loads
|
||||
|
||||
from .log import log
|
||||
|
||||
|
||||
class RequestParameters(dict):
|
||||
"""
|
||||
Hosts a dict with lists as values where get returns the first
|
||||
value of the list and getlist returns the whole shebang
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.super = super()
|
||||
self.super.__init__(*args, **kwargs)
|
||||
|
||||
def get(self, name, default=None):
|
||||
values = self.super.get(name)
|
||||
return values[0] if values else default
|
||||
|
||||
def getlist(self, name, default=None):
|
||||
return self.super.get(name, default)
|
||||
|
||||
|
||||
class Request:
|
||||
__slots__ = (
|
||||
'url', 'headers', 'version', 'method',
|
||||
@@ -75,7 +80,7 @@ class Request:
|
||||
@property
|
||||
def files(self):
|
||||
if self.parsed_files is None:
|
||||
_ = self.form # compute form to get files
|
||||
_ = self.form # compute form to get files
|
||||
|
||||
return self.parsed_files
|
||||
|
||||
@@ -89,7 +94,10 @@ class Request:
|
||||
|
||||
return self.parsed_args
|
||||
|
||||
|
||||
File = namedtuple('File', ['type', 'body', 'name'])
|
||||
|
||||
|
||||
def parse_multipart_form(body, boundary):
|
||||
"""
|
||||
Parses a request body and returns fields and files
|
||||
@@ -117,7 +125,7 @@ def parse_multipart_form(body, boundary):
|
||||
|
||||
colon_index = form_line.index(':')
|
||||
form_header_field = form_line[0:colon_index]
|
||||
form_header_value, form_parameters = parse_header(form_line[colon_index+2:])
|
||||
form_header_value, form_parameters = parse_header(form_line[colon_index + 2:])
|
||||
|
||||
if form_header_field == 'Content-Disposition':
|
||||
if 'filename' in form_parameters:
|
||||
@@ -126,11 +134,10 @@ def parse_multipart_form(body, boundary):
|
||||
elif form_header_field == 'Content-Type':
|
||||
file_type = form_header_value
|
||||
|
||||
|
||||
post_data = form_part[line_index:-4]
|
||||
if file_name or file_type:
|
||||
files[field_name] = File(type=file_type, name=file_name, body=post_data)
|
||||
else:
|
||||
fields[field_name] = post_data.decode('utf-8')
|
||||
|
||||
return fields, files
|
||||
return fields, files
|
||||
|
||||
@@ -18,6 +18,7 @@ STATUS_CODES = {
|
||||
504: 'Gateway Timeout',
|
||||
}
|
||||
|
||||
|
||||
class HTTPResponse:
|
||||
__slots__ = ('body', 'status', 'content_type', 'headers')
|
||||
|
||||
@@ -43,18 +44,25 @@ class HTTPResponse:
|
||||
additional_headers.append('{}: {}\r\n'.format(name, value).encode('utf-8'))
|
||||
|
||||
return b''.join([
|
||||
'HTTP/{} {} {}\r\n'.format(version, self.status, STATUS_CODES.get(self.status, 'FAIL')).encode(),
|
||||
b'Content-Type: ', self.content_type.encode(), b'\r\n',
|
||||
b'Content-Length: ', str(len(self.body)).encode(), b'\r\n',
|
||||
b'Connection: ', ('keep-alive' if keep_alive else 'close').encode(), b'\r\n',
|
||||
] + additional_headers + [
|
||||
b'\r\n',
|
||||
self.body,
|
||||
])
|
||||
'HTTP/{} {} {}\r\n'.format(version, self.status,
|
||||
STATUS_CODES.get(self.status, 'FAIL')).encode(),
|
||||
b'Content-Type: ', self.content_type.encode(), b'\r\n',
|
||||
b'Content-Length: ', str(len(self.body)).encode(), b'\r\n',
|
||||
b'Connection: ', ('keep-alive' if keep_alive else 'close').encode(), b'\r\n',
|
||||
] + additional_headers + [
|
||||
b'\r\n',
|
||||
self.body,
|
||||
])
|
||||
|
||||
|
||||
def json(body, status=200, headers=None):
|
||||
return HTTPResponse(ujson.dumps(body), headers=headers, status=status, content_type="application/json; charset=utf-8")
|
||||
return HTTPResponse(ujson.dumps(body), headers=headers, status=status,
|
||||
content_type="application/json; charset=utf-8")
|
||||
|
||||
|
||||
def text(body, status=200, headers=None):
|
||||
return HTTPResponse(body, status=status, headers=headers, content_type="text/plain; charset=utf-8")
|
||||
|
||||
|
||||
def html(body, status=200, headers=None):
|
||||
return HTTPResponse(body, status=status, headers=headers, content_type="text/html; charset=utf-8")
|
||||
return HTTPResponse(body, status=status, headers=headers, content_type="text/html; charset=utf-8")
|
||||
|
||||
@@ -5,6 +5,7 @@ from .exceptions import NotFound, InvalidUsage
|
||||
Route = namedtuple("Route", ['handler', 'methods', 'pattern', 'parameters'])
|
||||
Parameter = namedtuple("Parameter", ['name', 'cast'])
|
||||
|
||||
|
||||
class Router:
|
||||
"""
|
||||
Router supports basic routing with parameters and method checks
|
||||
@@ -42,9 +43,10 @@ class Router:
|
||||
"""
|
||||
|
||||
# Dict for faster lookups of if method allowed
|
||||
methods_dict = { method: True for method in methods } if methods else None
|
||||
methods_dict = {method: True for method in methods} if methods else None
|
||||
|
||||
parameters = []
|
||||
|
||||
def add_parameter(match):
|
||||
# We could receive NAME or NAME:PATTERN
|
||||
parts = match.group(1).split(':')
|
||||
@@ -93,11 +95,13 @@ class Router:
|
||||
|
||||
if route:
|
||||
if route.methods and not request.method in route.methods:
|
||||
raise InvalidUsage("Method {} not allowed for URL {}".format(request.method, request.url), status_code=405)
|
||||
raise InvalidUsage("Method {} not allowed for URL {}".format(request.method, request.url),
|
||||
status_code=405)
|
||||
return route.handler, args, kwargs
|
||||
else:
|
||||
raise NotFound("Requested URL {} not found".format(request.url))
|
||||
|
||||
|
||||
class SimpleRouter:
|
||||
"""
|
||||
Simple router records and reads all routes from a dictionary
|
||||
@@ -110,14 +114,15 @@ class SimpleRouter:
|
||||
|
||||
def add(self, uri, methods, handler):
|
||||
# Dict for faster lookups of method allowed
|
||||
methods_dict = { method: True for method in methods } if methods else None
|
||||
methods_dict = {method: True for method in methods} if methods else None
|
||||
self.routes[uri] = Route(handler=handler, methods=methods_dict, pattern=uri, parameters=None)
|
||||
|
||||
def get(self, request):
|
||||
route = self.routes.get(request.url)
|
||||
if route:
|
||||
if route.methods and not request.method in route.methods:
|
||||
raise InvalidUsage("Method {} not allowed for URL {}".format(request.method, request.url), status_code=405)
|
||||
raise InvalidUsage("Method {} not allowed for URL {}".format(request.method, request.url),
|
||||
status_code=405)
|
||||
return route.handler, [], {}
|
||||
else:
|
||||
raise NotFound("Requested URL {} not found".format(request.url))
|
||||
raise NotFound("Requested URL {} not found".format(request.url))
|
||||
|
||||
@@ -12,6 +12,7 @@ from .router import Router
|
||||
from .server import serve
|
||||
from .exceptions import ServerError
|
||||
|
||||
|
||||
class Sanic:
|
||||
def __init__(self, name, router=None, error_handler=None):
|
||||
self.name = name
|
||||
@@ -34,6 +35,7 @@ class Sanic:
|
||||
:param methods: list or tuple of methods allowed
|
||||
:return: decorated function
|
||||
"""
|
||||
|
||||
def response(handler):
|
||||
self.router.add(uri=uri, methods=methods, handler=handler)
|
||||
return handler
|
||||
@@ -48,6 +50,7 @@ class Sanic:
|
||||
:param methods: list or tuple of methods allowed
|
||||
:return: decorated function
|
||||
"""
|
||||
|
||||
def response(handler):
|
||||
for exception in exceptions:
|
||||
self.error_handler.add(exception, handler)
|
||||
@@ -63,6 +66,7 @@ class Sanic:
|
||||
"""
|
||||
middleware = None
|
||||
attach_to = 'request'
|
||||
|
||||
def register_middleware(middleware):
|
||||
if attach_to == 'request':
|
||||
self.request_middleware.append(middleware)
|
||||
@@ -156,7 +160,7 @@ class Sanic:
|
||||
:param before_stop: Function to be executed when a stop signal is received before it is respected
|
||||
:return: Nothing
|
||||
"""
|
||||
self.error_handler.debug=True
|
||||
self.error_handler.debug = True
|
||||
self.debug = debug
|
||||
|
||||
if debug:
|
||||
|
||||
@@ -3,6 +3,7 @@ from inspect import isawaitable
|
||||
from signal import SIGINT, SIGTERM
|
||||
|
||||
import httptools
|
||||
|
||||
try:
|
||||
import uvloop as async_loop
|
||||
except:
|
||||
@@ -11,17 +12,19 @@ except:
|
||||
from .log import log
|
||||
from .request import Request
|
||||
|
||||
|
||||
class Signal:
|
||||
stopped = False
|
||||
|
||||
|
||||
class HttpProtocol(asyncio.Protocol):
|
||||
__slots__ = ('loop', 'transport', 'connections', 'signal', # event loop, connection
|
||||
'parser', 'request', 'url', 'headers', # request params
|
||||
'request_handler', 'request_timeout', 'request_max_size', # request config
|
||||
'_total_request_size', '_timeout_handler') # connection management
|
||||
|
||||
__slots__ = ('loop', 'transport', 'connections', 'signal', # event loop, connection
|
||||
'parser', 'request', 'url', 'headers', # request params
|
||||
'request_handler', 'request_timeout', 'request_max_size', # request config
|
||||
'_total_request_size', '_timeout_handler') # connection management
|
||||
|
||||
def __init__(self, *, loop, request_handler, signal=Signal(), connections={}, request_timeout=60, request_max_size=None):
|
||||
def __init__(self, *, loop, request_handler, signal=Signal(), connections={}, request_timeout=60,
|
||||
request_max_size=None):
|
||||
self.loop = loop
|
||||
self.transport = None
|
||||
self.request = None
|
||||
@@ -37,6 +40,7 @@ class HttpProtocol(asyncio.Protocol):
|
||||
self._timeout_handler = None
|
||||
|
||||
# -------------------------------------------- #
|
||||
|
||||
# Connection
|
||||
# -------------------------------------------- #
|
||||
|
||||
@@ -51,9 +55,10 @@ class HttpProtocol(asyncio.Protocol):
|
||||
self.cleanup()
|
||||
|
||||
def connection_timeout(self):
|
||||
self.bail_out("Request timed out, connection closed")
|
||||
self.bail_out("Request timed out, connection closed")
|
||||
|
||||
# -------------------------------------------- #
|
||||
|
||||
# -------------------------------------------- #
|
||||
# Parsing
|
||||
# -------------------------------------------- #
|
||||
|
||||
@@ -86,7 +91,7 @@ class HttpProtocol(asyncio.Protocol):
|
||||
|
||||
def on_headers_complete(self):
|
||||
self.request = Request(
|
||||
url_bytes=self.url,
|
||||
url_bytes=self.url,
|
||||
headers=dict(self.headers),
|
||||
version=self.parser.get_http_version(),
|
||||
method=self.parser.get_method().decode()
|
||||
@@ -94,6 +99,7 @@ class HttpProtocol(asyncio.Protocol):
|
||||
|
||||
def on_body(self, body):
|
||||
self.request.body = body
|
||||
|
||||
def on_message_complete(self):
|
||||
self.loop.create_task(self.request_handler(self.request, self.write_response))
|
||||
|
||||
@@ -133,20 +139,22 @@ class HttpProtocol(asyncio.Protocol):
|
||||
return True
|
||||
return False
|
||||
|
||||
def serve(host, port, request_handler, after_start=None, before_stop=None, debug=False, request_timeout=60, request_max_size=None):
|
||||
|
||||
def serve(host, port, request_handler, after_start=None, before_stop=None, debug=False, request_timeout=60,
|
||||
request_max_size=None):
|
||||
# Create Event Loop
|
||||
loop = async_loop.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
# I don't think we take advantage of this
|
||||
# And it slows everything waaayyy down
|
||||
#loop.set_debug(debug)
|
||||
# loop.set_debug(debug)
|
||||
|
||||
connections = {}
|
||||
signal = Signal()
|
||||
server_coroutine = loop.create_server(lambda: HttpProtocol(
|
||||
loop=loop,
|
||||
connections = connections,
|
||||
signal = signal,
|
||||
connections=connections,
|
||||
signal=signal,
|
||||
request_handler=request_handler,
|
||||
request_timeout=request_timeout,
|
||||
request_max_size=request_max_size,
|
||||
|
||||
Reference in New Issue
Block a user