http_server_async: add blueprints (sub-apps)

This commit is contained in:
Izalia Mae 2021-11-07 15:43:34 -05:00
parent 7023f4fa40
commit 0dd58fc06e
5 changed files with 130 additions and 69 deletions

View file

@ -20,3 +20,7 @@ class MethodNotHandledException(Exception):
def __init__(self, method): def __init__(self, method):
super().__init__(f'HTTP method not handled by handler: {method}') super().__init__(f'HTTP method not handled by handler: {method}')
self.method = method self.method = method
class NoBlueprintForPath(Exception):
'raise when no blueprint is found for a specific path'

View file

@ -1,7 +1,7 @@
http_methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE'] http_methods = ['CONNECT', 'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'TRACE']
from .application import Application from .application import Application, Blueprint
from .misc import Cookies, Headers from .misc import Cookies, Headers
from .request import Request from .request import Request
from .response import Response from .response import Response

View file

@ -12,7 +12,7 @@ from .view import Static, Manifest, Robots, Style
from .. import logging from .. import logging
from ..dotdict import DotDict from ..dotdict import DotDict
from ..exceptions import MethodNotHandledException from ..exceptions import MethodNotHandledException, NoBlueprintForPath
from ..misc import signal_handler from ..misc import signal_handler
from ..path import Path from ..path import Path
from ..template import Template from ..template import Template
@ -21,26 +21,12 @@ from ..template import Template
frontend = Path(__file__).resolve().parent.parent.join('http_frontend') frontend = Path(__file__).resolve().parent.parent.join('http_frontend')
class Application: class ApplicationBase:
ctx = DotDict() ctx = DotDict()
def __init__(self, loop=None, views=[], middleware=[], **kwargs): def __init__(self, views=[], middleware=[], **kwargs):
if loop:
self.loop = loop
else:
try:
self.loop = asyncio.get_running_loop()
except RuntimeError:
self.loop = asyncio.new_event_loop()
self._server = None
self._tasks = []
self.cfg = Config(**kwargs) self.cfg = Config(**kwargs)
self.router = Router(trim_last_slash=True) self.router = Router(trim_last_slash=True)
#self.router = Router()
self.middleware = DotDict({'request': [], 'response': []}) self.middleware = DotDict({'request': [], 'response': []})
for view in views: for view in views:
@ -49,27 +35,6 @@ class Application:
for mw in middleware: for mw in middleware:
self.add_middleware(mw) self.add_middleware(mw)
if self.cfg.tpl_default:
self.template = Template(
self.cfg.tpl_search,
self.cfg.tpl_globals,
self.cfg.tpl_context,
self.cfg.tpl_autoescape
)
self.template.add_env('app', self)
self.template.add_env('cfg', self.cfg)
self.template.add_env('len', len)
self.template.add_search_path(frontend)
self.add_view(Manifest)
#self.add_view(Robots)
self.add_view(Style)
self.add_static('/framework/static/', frontend.join('static'))
else:
self.template = None
def __getitem__(self, key): def __getitem__(self, key):
return self.ctx[key] return self.ctx[key]
@ -137,6 +102,99 @@ class Application:
mwlist.append(handler) mwlist.append(handler)
async def handle_request(self, request, response, path=None):
handler = self.get_route(path or request.path, request.method)
await self.handle_middleware(request)
if handler.params:
handler_response = await handler.target(request, response, **handler.params)
else:
handler_response = await handler.target(request, response)
if isinstance(handler_response, dict):
response = self.cfg.response_class(**handler_response)
elif isinstance(handler_response, Response):
response = handler_response
elif not handler_response:
pass
else:
raise error.InternalServerError()
await self.handle_middleware(request, response)
return response
async def handle_middleware(self, request, response=None):
for middleware in self.middleware['response' if response else 'request']:
if response:
await middleware(request, response)
else:
await middleware(request)
class Application(ApplicationBase):
ctx = DotDict()
def __init__(self, loop=None, **kwargs):
super().__init__(**kwargs)
if loop:
self.loop = loop
else:
try:
self.loop = asyncio.get_running_loop()
except RuntimeError:
self.loop = asyncio.new_event_loop()
self._blueprints = {}
self._server = None
self._tasks = []
if self.cfg.tpl_default:
self.template = Template(
self.cfg.tpl_search,
self.cfg.tpl_globals,
self.cfg.tpl_context,
self.cfg.tpl_autoescape
)
self.template.add_env('app', self)
self.template.add_env('cfg', self.cfg)
self.template.add_env('len', len)
self.template.add_search_path(frontend)
self.add_view(Manifest)
#self.add_view(Robots)
self.add_view(Style)
self.add_static('/framework/static/', frontend.join('static'))
else:
self.template = None
def add_blueprint(self, bp):
assert bp.prefix not in self._blueprints.values()
self._blueprints[bp.prefix] = bp
def get_blueprint_for_path(self, path):
for bppath, bp in self._blueprints.items():
if path.startswith(bppath):
return bp
raise NoBlueprintForPath(path)
def render(self, *args, **kwargs): def render(self, *args, **kwargs):
return self.template.render(*args, **kwargs) return self.template.render(*args, **kwargs)
@ -207,29 +265,16 @@ class Application:
response = self.cfg.response_class(request=request) response = self.cfg.response_class(request=request)
await request.parse_headers() await request.parse_headers()
handler = self.get_route(request.path, request.method) try:
# this doesn't work all the time for some reason
blueprint = self.get_blueprint_for_path(request.path)
response = await blueprint.handle_request(request, response, blueprint.prefix)
await self.handle_middleware(request) except NoBlueprintForPath:
response = await self.handle_request(request, response)
if handler.params: except Exception as e:
handler_response = await handler.target(request, response, **handler.params) traceback.print_exc()
else:
handler_response = await handler.target(request, response)
if isinstance(handler_response, dict):
response = self.cfg.response_class(**handler_response)
elif isinstance(handler_response, Response):
response = handler_response
elif not handler_response:
pass
else:
raise error.InternalServerError()
await self.handle_middleware(request, response)
except NotFound: except NotFound:
response = self.cfg.response_class(request=request).set_error('Not Found', 404) response = self.cfg.response_class(request=request).set_error('Not Found', 404)
@ -255,7 +300,7 @@ class Application:
writer.write(response.compile()) writer.write(response.compile())
await writer.drain() await writer.drain()
if request: if request and not request.path.startswith('/framework'):
logging.info(f'{request.remote} {request.method} {request.path} {response.status} {len(response.body)} {request.agent}') logging.info(f'{request.remote} {request.method} {request.path} {response.status} {len(response.body)} {request.agent}')
except: except:
@ -265,10 +310,8 @@ class Application:
await writer.wait_closed() await writer.wait_closed()
async def handle_middleware(self, request, response=None): class Blueprint(ApplicationBase):
for middleware in self.middleware['response' if response else 'request']: def __init__(self, prefix, **kwargs):
if response: super().__init__(**kwargs)
await middleware(request, response)
else: self.prefix = prefix
await middleware(request)

View file

@ -18,6 +18,11 @@ cookie_params = {
'HttpOnly': 'httponly' 'HttpOnly': 'httponly'
} }
request_methods = [
'GET', 'HEAD', 'POST', 'PUT', 'DELETE',
'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'
]
class Cookies(DotDict): class Cookies(DotDict):
def __setitem__(self, key, value): def __setitem__(self, key, value):

View file

@ -1,4 +1,4 @@
from sqlalchemy import text from sqlalchemy import text, or_
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.session import Session as sqlalchemy_session from sqlalchemy.orm.session import Session as sqlalchemy_session
@ -80,7 +80,10 @@ class Session(sqlalchemy_session):
def count(self, table_name, **kwargs): def count(self, table_name, **kwargs):
return self.query(self.table[table_name]).filter_by(**kwargs).count() if kwargs:
return self.query(self.table[table_name]).filter_by(**kwargs).count()
return self.query(self.table[table_name]).count()
def fetch(self, table, single=True, orderby=None, orderdir='asc', **kwargs): def fetch(self, table, single=True, orderby=None, orderdir='asc', **kwargs):
@ -112,6 +115,12 @@ class Session(sqlalchemy_session):
return self.fetch(*args, single=False, **kwargs) return self.fetch(*args, single=False, **kwargs)
# not finished
def like(self, table, orderby=None, orderdir='asc', **kwargs):
assert len(kwargs)
query = self.query(self.table[table]).filter(or_(self.table[table].filename.like(f'')))
def insert(self, table, return_row=False, **kwargs): def insert(self, table, return_row=False, **kwargs):
row = self.fetch(table, **kwargs) row = self.fetch(table, **kwargs)