Skip to content

Commit

Permalink
optional asyncio support
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed May 4, 2019
1 parent 03efe46 commit 3d9b5d7
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 1 deletion.
148 changes: 148 additions & 0 deletions microdot_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
try:
import uasyncio as asyncio
except ImportError:
import asyncio
from microdot import Microdot as BaseMicrodot
from microdot import print_exception
from microdot import Request as BaseRequest
from microdot import Response as BaseResponse


class Request(BaseRequest):
@staticmethod
async def create(stream, client_addr):
# request line
line = (await stream.readline()).strip().decode()
method, url, http_version = line.split()
http_version = http_version.split('/', 1)[1]

# headers
headers = {}
content_length = 0
while True:
line = (await stream.readline()).strip().decode()
if line == '':
break
header, value = line.split(':', 1)
value = value.strip()
headers[header] = value
if header == 'Content-Length':
content_length = int(value)

# body
body = await stream.read(content_length) \
if content_length else b''

return Request(client_addr, method, url, http_version, headers, body)


class Response(BaseResponse):
async def write(self, stream):
self.complete()

# status code
await stream.awrite('HTTP/1.0 {status_code} {reason}\r\n'.format(
status_code=self.status_code,
reason='OK' if self.status_code == 200 else 'N/A').encode())

# headers
for header, value in self.headers.items():
values = value if isinstance(value, list) else [value]
for value in values:
await stream.awrite('{header}: {value}\r\n'.format(
header=header, value=value).encode())
await stream.awrite(b'\r\n')

# body
if self.body:
await stream.awrite(self.body)


class Microdot(BaseMicrodot):
def run(self, host='0.0.0.0', port=5000, debug=False):
async def serve(reader, writer):
req = await Request.create(reader,
writer.get_extra_info('peername'))
res = await self.dispatch_request(req)
if debug: # pragma: no cover
print('{method} {path} {status_code}'.format(
method=req.method, path=req.path,
status_code=res.status_code))

if not hasattr(writer, 'awrite'):
class AWriter:
def __init__(self, writer):
self.writer = writer

async def awrite(self, data):
self.writer.write(data)
await self.writer.drain()

async def aclose(self):
self.writer.close()

writer = AWriter(writer)
await res.write(writer)
await writer.aclose()

if debug: # pragma: no cover
print('Listening on {host}:{port}...'.format(host=host, port=port))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.start_server(serve, host, port))
loop.run_forever()
loop.close()

async def dispatch_request(self, req):
f = self.find_route(req)
try:
res = None
if f:
for handler in self.before_request_handlers:
res = await self._invoke_handler(handler, req)
if res:
break
if res is None:
res = await self._invoke_handler(f, req, **req.url_args)
if isinstance(res, tuple):
res = Response(*res)
elif not isinstance(res, Response):
res = Response(res)
for handler in self.after_request_handlers:
res = await self._invoke_handler(handler, req, res) or res
elif 404 in self.error_handlers:
res = self._invoke_handler(self.error_handlers[404], req)
else:
res = 'Not found', 404
except Exception as exc:
print_exception(exc)
res = None
if exc.__class__ in self.error_handlers:
try:
res = self._invoke_handler(
self.error_handlers[exc.__class__], req, exc)
except Exception as exc2: # pragma: no cover
print_exception(exc2)
if res is None:
if 500 in self.error_handlers:
res = await self._invoke_handler(self.error_handlers[500],
req)
else:
res = 'Internal server error', 500
if isinstance(res, tuple):
res = Response(*res)
elif not isinstance(res, Response):
res = Response(res)
return res

async def _invoke_handler(self, f_or_coro, *args, **kwargs):
r = f_or_coro(*args, **kwargs)
try:
r = await r
except TypeError:
# not a coroutine
pass
return r


redirect = Response.redirect
send_file = Response.send_file
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ skip_missing_interpreters=True

[testenv]
commands=
coverage run --branch --include="microdot.py" -m unittest tests
coverage run --branch --include="microdot*.py" -m unittest tests
coverage report --show-missing
coverage erase
deps=coverage
Expand Down

0 comments on commit 3d9b5d7

Please sign in to comment.