threaded mode

This commit is contained in:
Miguel Grinberg
2019-05-05 03:55:18 +00:00
parent ba986a89ff
commit 494800ff9f
3 changed files with 76 additions and 42 deletions

View File

@@ -1,3 +1,35 @@
try:
from sys import print_exception
except ImportError: # pragma: no cover
import traceback
def print_exception(exc):
traceback.print_exc()
concurrency_mode = 'threaded'
try: # pragma: no cover
import threading
def create_thread(f, *args, **kwargs):
"""Use the threading module."""
threading.Thread(target=f, args=args, kwargs=kwargs).start()
except ImportError: # pragma: no cover
try:
import _thread
def create_thread(f, *args, **kwargs):
"""Use MicroPython's _thread module."""
def run():
f(*args, **kwargs)
_thread.start_new_thread(run, ())
except ImportError:
def create_thread(f, *args, **kwargs):
"""No threads available, call function synchronously."""
f(*args, **kwargs)
concurrency_mode = 'sync'
try: try:
import ujson as json import ujson as json
except ImportError: except ImportError:
@@ -8,14 +40,6 @@ try:
except ImportError: except ImportError:
import re import re
try:
from sys import print_exception
except ImportError: # pragma: no cover
import traceback
def print_exception(exc):
traceback.print_exc()
try: try:
import usocket as socket import usocket as socket
except ImportError: except ImportError:
@@ -268,6 +292,7 @@ class Microdot():
self.before_request_handlers = [] self.before_request_handlers = []
self.after_request_handlers = [] self.after_request_handlers = []
self.error_handlers = {} self.error_handlers = {}
self.debug = False
def route(self, url_pattern, methods=None): def route(self, url_pattern, methods=None):
def decorated(f): def decorated(f):
@@ -291,33 +316,22 @@ class Microdot():
return decorated return decorated
def run(self, host='0.0.0.0', port=5000, debug=False): def run(self, host='0.0.0.0', port=5000, debug=False):
self.debug = debug
s = socket.socket() s = socket.socket()
ai = socket.getaddrinfo(host, port) ai = socket.getaddrinfo(host, port)
addr = ai[0][-1] addr = ai[0][-1]
if debug: # pragma: no cover if self.debug: # pragma: no cover
print('Listening on {host}:{port}...'.format(host=host, port=port)) print('Starting {mode} server on {host}:{port}...'.format(
mode=concurrency_mode, host=host, port=port))
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr) s.bind(addr)
s.listen(5) s.listen(5)
while True: while True:
sock, addr = s.accept() sock, addr = s.accept()
if not hasattr(sock, 'readline'): # pragma: no cover create_thread(self.dispatch_request, sock, addr)
stream = sock.makefile("rwb")
else:
stream = sock
req = Request.create(stream, addr)
res = 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))
res.write(stream)
stream.close()
if stream != sock: # pragma: no cover
sock.close()
def find_route(self, req): def find_route(self, req):
f = None f = None
@@ -329,7 +343,13 @@ class Microdot():
break break
return f return f
def dispatch_request(self, req): def dispatch_request(self, sock, addr):
if not hasattr(sock, 'readline'): # pragma: no cover
stream = sock.makefile("rwb")
else:
stream = sock
req = Request.create(stream, addr)
f = self.find_route(req) f = self.find_route(req)
try: try:
res = None res = None
@@ -367,7 +387,14 @@ class Microdot():
res = Response(*res) res = Response(*res)
elif not isinstance(res, Response): elif not isinstance(res, Response):
res = Response(res) res = Response(res)
return res res.write(stream)
stream.close()
if stream != sock: # pragma: no cover
sock.close()
if self.debug: # pragma: no cover
print('{method} {path} {status_code}'.format(
method=req.method, path=req.path,
status_code=res.status_code))
redirect = Response.redirect redirect = Response.redirect

View File

@@ -64,17 +64,11 @@ class Response(BaseResponse):
class Microdot(BaseMicrodot): class Microdot(BaseMicrodot):
def run(self, host='0.0.0.0', port=5000, debug=False): def run(self, host='0.0.0.0', port=5000, debug=False):
async def serve(reader, writer): self.debug = debug
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))
async def serve(reader, writer):
if not hasattr(writer, 'awrite'): # pragma: no cover if not hasattr(writer, 'awrite'): # pragma: no cover
# CPython adds the awrite and aclose methods in 3.8 # CPython provides the awrite and aclose methods in 3.8+
async def awrite(self, data): async def awrite(self, data):
self.write(data) self.write(data)
await self.drain() await self.drain()
@@ -87,17 +81,18 @@ class Microdot(BaseMicrodot):
writer.awrite = MethodType(awrite, writer) writer.awrite = MethodType(awrite, writer)
writer.aclose = MethodType(aclose, writer) writer.aclose = MethodType(aclose, writer)
await res.write(writer) await self.dispatch_request(reader, writer)
await writer.aclose()
if debug: # pragma: no cover if self.debug: # pragma: no cover
print('Listening on {host}:{port}...'.format(host=host, port=port)) print('Starting async server on {host}:{port}...'.format(
host=host, port=port))
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.start_server(serve, host, port)) loop.run_until_complete(asyncio.start_server(serve, host, port))
loop.run_forever() loop.run_forever()
loop.close() # pragma: no cover loop.close() # pragma: no cover
async def dispatch_request(self, req): async def dispatch_request(self, reader, writer):
req = await Request.create(reader, writer.get_extra_info('peername'))
f = self.find_route(req) f = self.find_route(req)
try: try:
res = None res = None
@@ -137,7 +132,12 @@ class Microdot(BaseMicrodot):
res = Response(*res) res = Response(*res)
elif not isinstance(res, Response): elif not isinstance(res, Response):
res = Response(res) res = Response(res)
return res await res.write(writer)
await writer.aclose()
if self.debug: # pragma: no cover
print('{method} {path} {status_code}'.format(
method=req.method, path=req.path,
status_code=res.status_code))
async def _invoke_handler(self, f_or_coro, *args, **kwargs): async def _invoke_handler(self, f_or_coro, *args, **kwargs):
ret = f_or_coro(*args, **kwargs) ret = f_or_coro(*args, **kwargs)

View File

@@ -4,15 +4,22 @@ from microdot import Microdot, Response
from tests import mock_socket from tests import mock_socket
def mock_create_thread(f, *args, **kwargs):
f(*args, **kwargs)
class TestMicrodot(unittest.TestCase): class TestMicrodot(unittest.TestCase):
def setUp(self): def setUp(self):
# mock socket module # mock socket module
self.original_socket = sys.modules['microdot'].socket self.original_socket = sys.modules['microdot'].socket
self.original_create_thread = sys.modules['microdot'].create_thread
sys.modules['microdot'].socket = mock_socket sys.modules['microdot'].socket = mock_socket
sys.modules['microdot'].create_thread = mock_create_thread
def tearDown(self): def tearDown(self):
# restore original socket module # restore original socket module
sys.modules['microdot'].socket = self.original_socket sys.modules['microdot'].socket = self.original_socket
sys.modules['microdot'].create_thread = self.original_create_thread
def test_get_request(self): def test_get_request(self):
app = Microdot() app = Microdot()