6 Commits

Author SHA1 Message Date
Miguel Grinberg
5f7efcc3f8 Release 0.8.2 2022-04-20 10:15:17 +01:00
Mark Blakeney
0f278321c8 Remove stray/debug remnant print() (#38) 2022-04-20 10:13:38 +01:00
Miguel Grinberg
acf20cc20c Version 0.8.2.dev0 2022-03-18 23:51:38 +00:00
Miguel Grinberg
453e133cc2 Release 0.8.1 2022-03-18 23:51:28 +00:00
Miguel Grinberg
29a9f6f46c Optimizations for request streams and bodies 2022-02-21 18:11:19 +01:00
Miguel Grinberg
9d3222ae4b Version 0.8.1.dev0 2022-02-18 17:41:16 +00:00
5 changed files with 62 additions and 29 deletions

View File

@@ -1,5 +1,13 @@
# Microdot change log
**Release 0.8.2** - 2022-04-20
- Remove debugging print statement [#38](https://github.com/miguelgrinberg/microdot/issues/38) ([commit](https://github.com/miguelgrinberg/microdot/commit/0f278321c8bd65c5cb67425eb837e6581cbb0054)) (thanks **Mark Blakeney**!)
**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))

View File

@@ -1,6 +1,6 @@
[metadata]
name = microdot
version = 0.8.0
version = 0.8.2
author = Miguel Grinberg
author_email = miguel.grinberg@gmail.com
description = The impossibly small web framework for MicroPython

View File

@@ -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:

View File

@@ -81,12 +81,19 @@ class Request(BaseRequest):
body = b''
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):

View File

@@ -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