ASGI support
This commit is contained in:
37
examples/hello_asgi.py
Normal file
37
examples/hello_asgi.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
try:
|
||||||
|
import uasyncio as asyncio
|
||||||
|
except ImportError:
|
||||||
|
import asyncio
|
||||||
|
from microdot_asgi import Microdot, Response
|
||||||
|
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
htmldoc = '''<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Microdot Example Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h1>Microdot Example Page</h1>
|
||||||
|
<p>Hello from Microdot!</p>
|
||||||
|
<p><a href="/shutdown">Click to shutdown the server</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
async def hello(request):
|
||||||
|
return htmldoc, 200, {'Content-Type': 'text/html'}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/shutdown')
|
||||||
|
async def shutdown(request):
|
||||||
|
request.app.shutdown()
|
||||||
|
return 'The server is shutting down...'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
@@ -24,7 +24,7 @@ htmldoc = '''<!DOCTYPE html>
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def hello(request):
|
async def hello(request):
|
||||||
return Response(body=htmldoc, headers={'Content-Type': 'text/html'})
|
return htmldoc, 200, {'Content-Type': 'text/html'}
|
||||||
|
|
||||||
|
|
||||||
@app.route('/shutdown')
|
@app.route('/shutdown')
|
||||||
|
|||||||
@@ -440,6 +440,8 @@ class Response():
|
|||||||
stream.write('{header}: {value}\r\n'.format(
|
stream.write('{header}: {value}\r\n'.format(
|
||||||
header=header, value=value).encode())
|
header=header, value=value).encode())
|
||||||
stream.write(b'\r\n')
|
stream.write(b'\r\n')
|
||||||
|
|
||||||
|
# body
|
||||||
for body in self.body_iter():
|
for body in self.body_iter():
|
||||||
stream.write(body)
|
stream.write(body)
|
||||||
|
|
||||||
|
|||||||
126
src/microdot_asgi.py
Normal file
126
src/microdot_asgi.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
from microdot_asyncio import * # noqa: F401, F403
|
||||||
|
from microdot_asyncio import Microdot as BaseMicrodot
|
||||||
|
from microdot_asyncio import Request
|
||||||
|
|
||||||
|
|
||||||
|
class _BodyStream: # pragma: no cover
|
||||||
|
def __init__(self, receive):
|
||||||
|
self.receive = receive
|
||||||
|
self.data = b''
|
||||||
|
self.more = True
|
||||||
|
|
||||||
|
async def read_more(self):
|
||||||
|
if self.more:
|
||||||
|
packet = await self.receive()
|
||||||
|
self.data += packet.get('body', b'')
|
||||||
|
self.more = packet.get('more_body', False)
|
||||||
|
|
||||||
|
async def read(self, n=-1):
|
||||||
|
while self.more and len(self.data) < n:
|
||||||
|
self.read_more()
|
||||||
|
if len(self.data) < n:
|
||||||
|
data = self.data
|
||||||
|
self.data = b''
|
||||||
|
return data
|
||||||
|
|
||||||
|
data = self.data[:n]
|
||||||
|
self.data = self.data[n:]
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def readline(self):
|
||||||
|
return self.readuntil()
|
||||||
|
|
||||||
|
async def readexactly(self, n):
|
||||||
|
return self.read(n)
|
||||||
|
|
||||||
|
async def readuntil(self, separator=b'\n'):
|
||||||
|
if self.more and separator not in self.data:
|
||||||
|
self.read_more()
|
||||||
|
data, self.data = self.data.split(separator, 1)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class Microdot(BaseMicrodot):
|
||||||
|
async def asgi_app(self, scope, receive, send):
|
||||||
|
if scope['type'] != 'http': # pragma: no cover
|
||||||
|
return
|
||||||
|
path = scope['path']
|
||||||
|
if 'query_string' in scope and scope['query_string']:
|
||||||
|
path += '?' + scope['query_string'].decode()
|
||||||
|
headers = {}
|
||||||
|
content_length = 0
|
||||||
|
for key, value in scope.get('headers', []):
|
||||||
|
headers[key] = value
|
||||||
|
if key.lower() == 'content-length':
|
||||||
|
content_length = int(value)
|
||||||
|
|
||||||
|
body = b''
|
||||||
|
if content_length and content_length <= Request.max_body_length:
|
||||||
|
body = b''
|
||||||
|
more = True
|
||||||
|
while more:
|
||||||
|
packet = await receive()
|
||||||
|
body += packet.get('body', b'')
|
||||||
|
more = packet.get('more_body', False)
|
||||||
|
stream = None
|
||||||
|
else:
|
||||||
|
body = b''
|
||||||
|
stream = _BodyStream(receive)
|
||||||
|
|
||||||
|
req = Request(
|
||||||
|
self,
|
||||||
|
(scope['client'][0], scope['client'][1]),
|
||||||
|
scope['method'],
|
||||||
|
path,
|
||||||
|
'HTTP/' + scope['http_version'],
|
||||||
|
headers,
|
||||||
|
body=body,
|
||||||
|
stream=stream)
|
||||||
|
req.asgi_scope = scope
|
||||||
|
|
||||||
|
res = await self.dispatch_request(req)
|
||||||
|
res.complete()
|
||||||
|
|
||||||
|
await send({'type': 'http.response.start',
|
||||||
|
'status': res.status_code,
|
||||||
|
'headers': [(name, value)
|
||||||
|
for name, value in res.headers.items()]})
|
||||||
|
body_iter = res.body_iter()
|
||||||
|
body = b''
|
||||||
|
try:
|
||||||
|
body += await body_iter.__anext__()
|
||||||
|
while True:
|
||||||
|
next_body = await body_iter.__anext__()
|
||||||
|
await send({'type': 'http.response.body',
|
||||||
|
'body': body,
|
||||||
|
'more_body': True})
|
||||||
|
body = next_body
|
||||||
|
except StopAsyncIteration:
|
||||||
|
await send({'type': 'http.response.body',
|
||||||
|
'body': body,
|
||||||
|
'more_body': False})
|
||||||
|
|
||||||
|
async def __call__(self, scope, receive, send):
|
||||||
|
return await self.asgi_app(scope, receive, send)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pid = os.getpgrp() if hasattr(os, 'getpgrp') else os.getpid()
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
|
||||||
|
def run(self, host='0.0.0.0', port=5000, debug=False,
|
||||||
|
**options): # pragma: no cover
|
||||||
|
try:
|
||||||
|
from waitress import serve
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
raise RuntimeError('The run() method requires Waitress to be '
|
||||||
|
'installed (i.e. run "pip install waitress").')
|
||||||
|
|
||||||
|
self.debug = debug
|
||||||
|
if debug:
|
||||||
|
logger = logging.getLogger('waitress')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
serve(self, host=host, port=port, **options)
|
||||||
@@ -133,18 +133,27 @@ class Response(BaseResponse):
|
|||||||
await stream.awrite(b'\r\n')
|
await stream.awrite(b'\r\n')
|
||||||
|
|
||||||
# body
|
# body
|
||||||
|
async for body in self.body_iter():
|
||||||
|
await stream.awrite(body)
|
||||||
|
|
||||||
|
async def body_iter(self):
|
||||||
if self.body:
|
if self.body:
|
||||||
if hasattr(self.body, 'read'):
|
if hasattr(self.body, 'read'):
|
||||||
while True:
|
while True:
|
||||||
buf = self.body.read(self.send_file_buffer_size)
|
buf = self.body.read(self.send_file_buffer_size)
|
||||||
|
if _iscoroutine(buf): # pragma: no cover
|
||||||
|
buf = await buf
|
||||||
if len(buf):
|
if len(buf):
|
||||||
await stream.awrite(buf)
|
print('*', buf, self.send_file_buffer_size)
|
||||||
|
yield buf
|
||||||
if len(buf) < self.send_file_buffer_size:
|
if len(buf) < self.send_file_buffer_size:
|
||||||
break
|
break
|
||||||
if hasattr(self.body, 'close'): # pragma: no cover
|
if hasattr(self.body, 'close'): # pragma: no cover
|
||||||
self.body.close()
|
result = self.body.close()
|
||||||
|
if _iscoroutine(result):
|
||||||
|
await result
|
||||||
else:
|
else:
|
||||||
await stream.awrite(self.body)
|
yield self.body
|
||||||
|
|
||||||
|
|
||||||
class Microdot(BaseMicrodot):
|
class Microdot(BaseMicrodot):
|
||||||
@@ -201,7 +210,7 @@ class Microdot(BaseMicrodot):
|
|||||||
writer.awrite = MethodType(awrite, writer)
|
writer.awrite = MethodType(awrite, writer)
|
||||||
writer.aclose = MethodType(aclose, writer)
|
writer.aclose = MethodType(aclose, writer)
|
||||||
|
|
||||||
await self.dispatch_request(reader, writer)
|
await self.handle_request(reader, writer)
|
||||||
|
|
||||||
if self.debug: # pragma: no cover
|
if self.debug: # pragma: no cover
|
||||||
print('Starting async server on {host}:{port}...'.format(
|
print('Starting async server on {host}:{port}...'.format(
|
||||||
@@ -251,13 +260,23 @@ class Microdot(BaseMicrodot):
|
|||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.server.close()
|
self.server.close()
|
||||||
|
|
||||||
async def dispatch_request(self, reader, writer):
|
async def handle_request(self, reader, writer):
|
||||||
req = None
|
req = None
|
||||||
try:
|
try:
|
||||||
req = await Request.create(self, reader,
|
req = await Request.create(self, reader,
|
||||||
writer.get_extra_info('peername'))
|
writer.get_extra_info('peername'))
|
||||||
except Exception as exc: # pragma: no cover
|
except Exception as exc: # pragma: no cover
|
||||||
print_exception(exc)
|
print_exception(exc)
|
||||||
|
|
||||||
|
res = await self.dispatch_request(req)
|
||||||
|
await res.write(writer)
|
||||||
|
await writer.aclose()
|
||||||
|
if self.debug and req: # pragma: no cover
|
||||||
|
print('{method} {path} {status_code}'.format(
|
||||||
|
method=req.method, path=req.path,
|
||||||
|
status_code=res.status_code))
|
||||||
|
|
||||||
|
async def dispatch_request(self, req):
|
||||||
if req:
|
if req:
|
||||||
if req.content_length > req.max_content_length:
|
if req.content_length > req.max_content_length:
|
||||||
if 413 in self.error_handlers:
|
if 413 in self.error_handlers:
|
||||||
@@ -310,12 +329,7 @@ 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)
|
||||||
await res.write(writer)
|
return res
|
||||||
await writer.aclose()
|
|
||||||
if self.debug and req: # 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)
|
||||||
|
|||||||
@@ -6,32 +6,29 @@ from microdot import Microdot as BaseMicrodot
|
|||||||
from microdot import Request
|
from microdot import Request
|
||||||
|
|
||||||
|
|
||||||
class WSGIRequest(Request):
|
class Microdot(BaseMicrodot):
|
||||||
def __init__(self, app, environ):
|
def wsgi_app(self, environ, start_response):
|
||||||
url = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
|
path = environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')
|
||||||
if 'QUERY_STRING' in environ and environ['QUERY_STRING']:
|
if 'QUERY_STRING' in environ and environ['QUERY_STRING']:
|
||||||
url += '?' + environ['QUERY_STRING']
|
path += '?' + environ['QUERY_STRING']
|
||||||
headers = {}
|
headers = {}
|
||||||
for k, v in environ.items():
|
for k, v in environ.items():
|
||||||
if k.startswith('HTTP_'):
|
if k.startswith('HTTP_'):
|
||||||
h = '-'.join([p.title() for p in k[5:].split('_')])
|
h = '-'.join([p.title() for p in k[5:].split('_')])
|
||||||
headers[h] = v
|
headers[h] = v
|
||||||
super().__init__(
|
req = Request(
|
||||||
app,
|
self,
|
||||||
(environ['REMOTE_ADDR'], int(environ.get('REMOTE_PORT', '0'))),
|
(environ['REMOTE_ADDR'], int(environ.get('REMOTE_PORT', '0'))),
|
||||||
environ['REQUEST_METHOD'],
|
environ['REQUEST_METHOD'],
|
||||||
url,
|
path,
|
||||||
environ['SERVER_PROTOCOL'],
|
environ['SERVER_PROTOCOL'],
|
||||||
headers,
|
headers,
|
||||||
stream=environ['wsgi.input'])
|
stream=environ['wsgi.input'])
|
||||||
self.environ = environ
|
req.environ = environ
|
||||||
|
|
||||||
|
|
||||||
class Microdot(BaseMicrodot):
|
|
||||||
def wsgi_app(self, environ, start_response):
|
|
||||||
req = WSGIRequest(self, environ)
|
|
||||||
res = self.dispatch_request(req)
|
res = self.dispatch_request(req)
|
||||||
res.complete()
|
res.complete()
|
||||||
|
|
||||||
reason = res.reason or ('OK' if res.status_code == 200 else 'N/A')
|
reason = res.reason or ('OK' if res.status_code == 200 else 'N/A')
|
||||||
start_response(
|
start_response(
|
||||||
str(res.status_code) + ' ' + reason,
|
str(res.status_code) + ' ' + reason,
|
||||||
|
|||||||
133
tests/microdot_asgi/test_microdot_asgi.py
Normal file
133
tests/microdot_asgi/test_microdot_asgi.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
mock = None
|
||||||
|
|
||||||
|
from microdot_asgi import Microdot, Response
|
||||||
|
from tests import mock_asyncio
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.implementation.name == 'micropython',
|
||||||
|
'not supported under MicroPython')
|
||||||
|
class TestMicrodotASGI(unittest.TestCase):
|
||||||
|
def test_asgi_request_with_query_string(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.post('/foo/bar')
|
||||||
|
async def index(req):
|
||||||
|
self.assertEqual(req.app, app)
|
||||||
|
self.assertEqual(req.client_addr, ('1.2.3.4', 1234))
|
||||||
|
self.assertEqual(req.method, 'POST')
|
||||||
|
self.assertEqual(req.http_version, 'HTTP/1.1')
|
||||||
|
self.assertEqual(req.path, '/foo/bar')
|
||||||
|
self.assertEqual(req.args, {'baz': ['1']})
|
||||||
|
self.assertEqual(req.cookies, {'session': 'xyz'})
|
||||||
|
self.assertEqual(req.body, b'body')
|
||||||
|
|
||||||
|
class R:
|
||||||
|
def __init__(self):
|
||||||
|
self.i = 0
|
||||||
|
self.body = [b're', b'sp', b'on', b'se', b'']
|
||||||
|
|
||||||
|
async def read(self, n):
|
||||||
|
data = self.body[self.i]
|
||||||
|
self.i += 1
|
||||||
|
return data
|
||||||
|
|
||||||
|
return Response(body=R(), headers={'Content-Length': '8'})
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
'type': 'http',
|
||||||
|
'path': '/foo/bar',
|
||||||
|
'query_string': b'baz=1',
|
||||||
|
'headers': [('Authorization', 'Bearer 123'),
|
||||||
|
('Cookie', 'session=xyz'),
|
||||||
|
('Content-Length', 4)],
|
||||||
|
'client': ['1.2.3.4', 1234],
|
||||||
|
'method': 'POST',
|
||||||
|
'http_version': '1.1',
|
||||||
|
}
|
||||||
|
|
||||||
|
async def receive():
|
||||||
|
return {
|
||||||
|
'type': 'http.request',
|
||||||
|
'body': b'body',
|
||||||
|
'more_body': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def send(packet):
|
||||||
|
if packet['type'] == 'http.response.start':
|
||||||
|
self.assertEqual(packet['status'], 200)
|
||||||
|
self.assertEqual(
|
||||||
|
packet['headers'],
|
||||||
|
[('Content-Length', '8'), ('Content-Type', 'text/plain')])
|
||||||
|
elif packet['type'] == 'http.response.body':
|
||||||
|
self.assertIn(packet['body'], [b're', b'sp', b'on', b'se'])
|
||||||
|
|
||||||
|
original_buffer_size = Response.send_file_buffer_size
|
||||||
|
Response.send_file_buffer_size = 2
|
||||||
|
|
||||||
|
mock_asyncio.run(app(scope, receive, send))
|
||||||
|
|
||||||
|
Response.send_file_buffer_size = original_buffer_size
|
||||||
|
|
||||||
|
def test_wsgi_request_without_query_string(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/foo/bar')
|
||||||
|
async def index(req):
|
||||||
|
self.assertEqual(req.path, '/foo/bar')
|
||||||
|
self.assertEqual(req.args, {})
|
||||||
|
return 'response'
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
'type': 'http',
|
||||||
|
'path': '/foo/bar',
|
||||||
|
'headers': [('Authorization', 'Bearer 123'),
|
||||||
|
('Cookie', 'session=xyz'),
|
||||||
|
('Content-Length', 4)],
|
||||||
|
'client': ['1.2.3.4', 1234],
|
||||||
|
'method': 'POST',
|
||||||
|
'http_version': '1.1',
|
||||||
|
}
|
||||||
|
|
||||||
|
async def receive():
|
||||||
|
return {
|
||||||
|
'type': 'http.request',
|
||||||
|
'body': b'body',
|
||||||
|
'more_body': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def send(packet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
mock_asyncio.run(app(scope, receive, send))
|
||||||
|
|
||||||
|
def test_shutdown(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/shutdown')
|
||||||
|
async def shutdown(request):
|
||||||
|
request.app.shutdown()
|
||||||
|
|
||||||
|
scope = {
|
||||||
|
'type': 'http',
|
||||||
|
'path': '/shutdown',
|
||||||
|
'client': ['1.2.3.4', 1234],
|
||||||
|
'method': 'GET',
|
||||||
|
'http_version': '1.1',
|
||||||
|
}
|
||||||
|
|
||||||
|
async def receive():
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def send(packet):
|
||||||
|
pass
|
||||||
|
|
||||||
|
with mock.patch('microdot_asgi.os.kill') as kill:
|
||||||
|
mock_asyncio.run(app(scope, receive, send))
|
||||||
|
|
||||||
|
kill.assert_called()
|
||||||
@@ -11,13 +11,27 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
mock = None
|
mock = None
|
||||||
|
|
||||||
from microdot_wsgi import Microdot, WSGIRequest
|
from microdot_wsgi import Microdot
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.implementation.name == 'micropython',
|
@unittest.skipIf(sys.implementation.name == 'micropython',
|
||||||
'not supported under MicroPython')
|
'not supported under MicroPython')
|
||||||
class TestMicrodotWSGI(unittest.TestCase):
|
class TestMicrodotWSGI(unittest.TestCase):
|
||||||
def test_wsgi_request_with_query_string(self):
|
def test_wsgi_request_with_query_string(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.post('/foo/bar')
|
||||||
|
def index(req):
|
||||||
|
self.assertEqual(req.app, app)
|
||||||
|
self.assertEqual(req.client_addr, ('1.2.3.4', 1234))
|
||||||
|
self.assertEqual(req.method, 'POST')
|
||||||
|
self.assertEqual(req.http_version, 'HTTP/1.1')
|
||||||
|
self.assertEqual(req.path, '/foo/bar')
|
||||||
|
self.assertEqual(req.args, {'baz': ['1']})
|
||||||
|
self.assertEqual(req.cookies, {'session': 'xyz'})
|
||||||
|
self.assertEqual(req.body, b'body')
|
||||||
|
return 'response'
|
||||||
|
|
||||||
environ = {
|
environ = {
|
||||||
'SCRIPT_NAME': '/foo',
|
'SCRIPT_NAME': '/foo',
|
||||||
'PATH_INFO': '/bar',
|
'PATH_INFO': '/bar',
|
||||||
@@ -31,18 +45,25 @@ class TestMicrodotWSGI(unittest.TestCase):
|
|||||||
'SERVER_PROTOCOL': 'HTTP/1.1',
|
'SERVER_PROTOCOL': 'HTTP/1.1',
|
||||||
'wsgi.input': io.BytesIO(b'body'),
|
'wsgi.input': io.BytesIO(b'body'),
|
||||||
}
|
}
|
||||||
app = Microdot()
|
|
||||||
req = WSGIRequest(app, environ)
|
|
||||||
self.assertEqual(req.app, app)
|
|
||||||
self.assertEqual(req.client_addr, ('1.2.3.4', 1234))
|
|
||||||
self.assertEqual(req.method, 'POST')
|
|
||||||
self.assertEqual(req.http_version, 'HTTP/1.1')
|
|
||||||
self.assertEqual(req.path, '/foo/bar')
|
|
||||||
self.assertEqual(req.args, {'baz': ['1']})
|
|
||||||
self.assertEqual(req.cookies, {'session': 'xyz'})
|
|
||||||
self.assertEqual(req.body, b'body')
|
|
||||||
|
|
||||||
def test_wsgi_request_withiout_query_string(self):
|
def start_response(status, headers):
|
||||||
|
self.assertEqual(status, '200 OK')
|
||||||
|
self.assertEqual(
|
||||||
|
headers,
|
||||||
|
[('Content-Length', '8'), ('Content-Type', 'text/plain')])
|
||||||
|
|
||||||
|
r = app(environ, start_response)
|
||||||
|
self.assertEqual(next(r), b'response')
|
||||||
|
|
||||||
|
def test_wsgi_request_without_query_string(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/foo/bar')
|
||||||
|
def index(req):
|
||||||
|
self.assertEqual(req.path, '/foo/bar')
|
||||||
|
self.assertEqual(req.args, {})
|
||||||
|
return 'response'
|
||||||
|
|
||||||
environ = {
|
environ = {
|
||||||
'SCRIPT_NAME': '/foo',
|
'SCRIPT_NAME': '/foo',
|
||||||
'PATH_INFO': '/bar',
|
'PATH_INFO': '/bar',
|
||||||
@@ -52,37 +73,11 @@ class TestMicrodotWSGI(unittest.TestCase):
|
|||||||
'SERVER_PROTOCOL': 'HTTP/1.1',
|
'SERVER_PROTOCOL': 'HTTP/1.1',
|
||||||
'wsgi.input': io.BytesIO(b''),
|
'wsgi.input': io.BytesIO(b''),
|
||||||
}
|
}
|
||||||
app = Microdot()
|
|
||||||
req = WSGIRequest(app, environ)
|
|
||||||
self.assertEqual(req.path, '/foo/bar')
|
|
||||||
self.assertEqual(req.args, {})
|
|
||||||
|
|
||||||
def test_wsgi_app(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/foo')
|
|
||||||
def foo(request):
|
|
||||||
return 'bar'
|
|
||||||
|
|
||||||
environ = {
|
|
||||||
'PATH_INFO': '/foo',
|
|
||||||
'REMOTE_ADDR': '1.2.3.4',
|
|
||||||
'REMOTE_PORT': '1234',
|
|
||||||
'REQUEST_METHOD': 'GET',
|
|
||||||
'SERVER_PROTOCOL': 'HTTP/1.1',
|
|
||||||
'wsgi.input': io.BytesIO(b''),
|
|
||||||
}
|
|
||||||
|
|
||||||
def start_response(status, headers):
|
def start_response(status, headers):
|
||||||
self.assertEqual(status, '200 OK')
|
pass
|
||||||
self.assertEqual(headers, [('Content-Length', '3'),
|
|
||||||
('Content-Type', 'text/plain')])
|
|
||||||
|
|
||||||
res = app(environ, start_response)
|
app(environ, start_response)
|
||||||
body = b''
|
|
||||||
for b in res:
|
|
||||||
body += b
|
|
||||||
self.assertEqual(body, b'bar')
|
|
||||||
|
|
||||||
def test_shutdown(self):
|
def test_shutdown(self):
|
||||||
app = Microdot()
|
app = Microdot()
|
||||||
|
|||||||
Reference in New Issue
Block a user