Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
453e133cc2 | ||
|
|
29a9f6f46c | ||
|
|
9d3222ae4b |
@@ -1,5 +1,9 @@
|
||||
# Microdot change log
|
||||
|
||||
**Release 0.8.1** - 2022-03-18
|
||||
|
||||
- Optimizations for request streams and bodies ([commit](https://github.com/miguelgrinberg/microdot/commit/29a9f6f46c737aa0fd452766c23bd83008594ac4))
|
||||
|
||||
**Release 0.8.0** - 2022-02-18
|
||||
|
||||
- Support streamed request payloads [#26](https://github.com/miguelgrinberg/microdot/issues/26) ([commit](https://github.com/miguelgrinberg/microdot/commit/992fa722c1312c0ac0ee9fbd5e23ad7b52d3caca))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = microdot
|
||||
version = 0.8.0
|
||||
version = 0.8.1
|
||||
author = Miguel Grinberg
|
||||
author_email = miguel.grinberg@gmail.com
|
||||
description = The impossibly small web framework for MicroPython
|
||||
|
||||
@@ -18,11 +18,6 @@ try:
|
||||
except ImportError:
|
||||
import errno
|
||||
|
||||
try:
|
||||
import uio as io
|
||||
except ImportError:
|
||||
import io
|
||||
|
||||
concurrency_mode = 'threaded'
|
||||
|
||||
try: # pragma: no cover
|
||||
@@ -228,7 +223,7 @@ class Request():
|
||||
pass
|
||||
|
||||
def __init__(self, app, client_addr, method, url, http_version, headers,
|
||||
body, stream):
|
||||
body=None, stream=None):
|
||||
self.app = app
|
||||
self.client_addr = client_addr
|
||||
self.method = method
|
||||
@@ -254,8 +249,10 @@ class Request():
|
||||
for cookie in value.split(';'):
|
||||
name, value = cookie.strip().split('=', 1)
|
||||
self.cookies[name] = value
|
||||
self.body = body
|
||||
self.stream = stream
|
||||
self._body = body
|
||||
self.body_used = False
|
||||
self._stream = stream
|
||||
self.stream_used = False
|
||||
self._json = None
|
||||
self._form = None
|
||||
self.g = Request.G()
|
||||
@@ -280,7 +277,6 @@ class Request():
|
||||
|
||||
# headers
|
||||
headers = {}
|
||||
content_length = 0
|
||||
while True:
|
||||
line = Request._safe_readline(client_stream).strip().decode()
|
||||
if line == '':
|
||||
@@ -288,23 +284,9 @@ class Request():
|
||||
header, value = line.split(':', 1)
|
||||
value = value.strip()
|
||||
headers[header] = value
|
||||
if header.lower() == 'content-length':
|
||||
content_length = int(value)
|
||||
|
||||
# body
|
||||
body = b''
|
||||
if content_length and content_length <= Request.max_body_length:
|
||||
while len(body) < content_length:
|
||||
data = client_stream.read(content_length - len(body))
|
||||
if len(data) == 0: # pragma: no cover
|
||||
raise EOFError()
|
||||
body += data
|
||||
stream = io.BytesIO(body)
|
||||
else:
|
||||
stream = client_stream
|
||||
|
||||
return Request(app, client_addr, method, url, http_version, headers,
|
||||
body, stream)
|
||||
stream=client_stream)
|
||||
|
||||
def _parse_urlencoded(self, urlencoded):
|
||||
data = MultiDict()
|
||||
@@ -312,6 +294,30 @@ class Request():
|
||||
data[urldecode(k)] = urldecode(v)
|
||||
return data
|
||||
|
||||
@property
|
||||
def body(self):
|
||||
if self.stream_used:
|
||||
raise RuntimeError('Cannot use both stream and body')
|
||||
if self._body is None:
|
||||
self._body = b''
|
||||
if self.content_length and \
|
||||
self.content_length <= Request.max_body_length:
|
||||
while len(self._body) < self.content_length:
|
||||
data = self._stream.read(
|
||||
self.content_length - len(self._body))
|
||||
if len(data) == 0: # pragma: no cover
|
||||
raise EOFError()
|
||||
self._body += data
|
||||
self.body_used = True
|
||||
return self._body
|
||||
|
||||
@property
|
||||
def stream(self):
|
||||
if self.body_used:
|
||||
raise RuntimeError('Cannot use both stream and body')
|
||||
self.stream_used = True
|
||||
return self._stream
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
if self._json is None:
|
||||
|
||||
@@ -79,14 +79,22 @@ class Request(BaseRequest):
|
||||
|
||||
# body
|
||||
body = b''
|
||||
print(Request.max_body_length)
|
||||
if content_length and content_length <= Request.max_body_length:
|
||||
body = await client_stream.readexactly(content_length)
|
||||
stream = _AsyncBytesIO(body)
|
||||
stream = None
|
||||
else:
|
||||
body = b''
|
||||
stream = client_stream
|
||||
|
||||
return Request(app, client_addr, method, url, http_version, headers,
|
||||
body, stream)
|
||||
body=body, stream=stream)
|
||||
|
||||
@property
|
||||
def stream(self):
|
||||
if self._stream is None:
|
||||
self._stream = _AsyncBytesIO(self._body)
|
||||
return self._stream
|
||||
|
||||
@staticmethod
|
||||
async def _safe_readline(stream):
|
||||
|
||||
@@ -93,11 +93,23 @@ class TestRequest(unittest.TestCase):
|
||||
|
||||
def test_stream(self):
|
||||
fd = get_request_fd('GET', '/foo', headers={
|
||||
'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': '19'},
|
||||
body='foo=bar&abc=def&x=y')
|
||||
req = Request.create('app', fd, 'addr')
|
||||
self.assertEqual(req.stream.read(), b'foo=bar&abc=def&x=y')
|
||||
with self.assertRaises(RuntimeError):
|
||||
req.body
|
||||
|
||||
def test_body(self):
|
||||
fd = get_request_fd('GET', '/foo', headers={
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': '19'},
|
||||
body='foo=bar&abc=def&x=y')
|
||||
req = Request.create('app', fd, 'addr')
|
||||
self.assertEqual(req.body, b'foo=bar&abc=def&x=y')
|
||||
self.assertEqual(req.stream.read(), b'foo=bar&abc=def&x=y')
|
||||
with self.assertRaises(RuntimeError):
|
||||
req.stream
|
||||
|
||||
def test_large_payload(self):
|
||||
saved_max_content_length = Request.max_content_length
|
||||
|
||||
Reference in New Issue
Block a user