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 # 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 **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)) - 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] [metadata]
name = microdot name = microdot
version = 0.8.0 version = 0.8.2
author = Miguel Grinberg author = Miguel Grinberg
author_email = miguel.grinberg@gmail.com author_email = miguel.grinberg@gmail.com
description = The impossibly small web framework for MicroPython description = The impossibly small web framework for MicroPython

View File

@@ -18,11 +18,6 @@ try:
except ImportError: except ImportError:
import errno import errno
try:
import uio as io
except ImportError:
import io
concurrency_mode = 'threaded' concurrency_mode = 'threaded'
try: # pragma: no cover try: # pragma: no cover
@@ -228,7 +223,7 @@ class Request():
pass pass
def __init__(self, app, client_addr, method, url, http_version, headers, def __init__(self, app, client_addr, method, url, http_version, headers,
body, stream): body=None, stream=None):
self.app = app self.app = app
self.client_addr = client_addr self.client_addr = client_addr
self.method = method self.method = method
@@ -254,8 +249,10 @@ class Request():
for cookie in value.split(';'): for cookie in value.split(';'):
name, value = cookie.strip().split('=', 1) name, value = cookie.strip().split('=', 1)
self.cookies[name] = value self.cookies[name] = value
self.body = body self._body = body
self.stream = stream self.body_used = False
self._stream = stream
self.stream_used = False
self._json = None self._json = None
self._form = None self._form = None
self.g = Request.G() self.g = Request.G()
@@ -280,7 +277,6 @@ class Request():
# headers # headers
headers = {} headers = {}
content_length = 0
while True: while True:
line = Request._safe_readline(client_stream).strip().decode() line = Request._safe_readline(client_stream).strip().decode()
if line == '': if line == '':
@@ -288,23 +284,9 @@ class Request():
header, value = line.split(':', 1) header, value = line.split(':', 1)
value = value.strip() value = value.strip()
headers[header] = value 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, return Request(app, client_addr, method, url, http_version, headers,
body, stream) stream=client_stream)
def _parse_urlencoded(self, urlencoded): def _parse_urlencoded(self, urlencoded):
data = MultiDict() data = MultiDict()
@@ -312,6 +294,30 @@ class Request():
data[urldecode(k)] = urldecode(v) data[urldecode(k)] = urldecode(v)
return data 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 @property
def json(self): def json(self):
if self._json is None: if self._json is None:

View File

@@ -81,12 +81,19 @@ class Request(BaseRequest):
body = b'' body = b''
if content_length and content_length <= Request.max_body_length: if content_length and content_length <= Request.max_body_length:
body = await client_stream.readexactly(content_length) body = await client_stream.readexactly(content_length)
stream = _AsyncBytesIO(body) stream = None
else: else:
body = b''
stream = client_stream stream = client_stream
return Request(app, client_addr, method, url, http_version, headers, 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 @staticmethod
async def _safe_readline(stream): async def _safe_readline(stream):

View File

@@ -93,11 +93,23 @@ class TestRequest(unittest.TestCase):
def test_stream(self): def test_stream(self):
fd = get_request_fd('GET', '/foo', headers={ 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') body='foo=bar&abc=def&x=y')
req = Request.create('app', fd, 'addr') req = Request.create('app', fd, 'addr')
self.assertEqual(req.body, b'foo=bar&abc=def&x=y') 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): def test_large_payload(self):
saved_max_content_length = Request.max_content_length saved_max_content_length = Request.max_content_length