Skip to content

Commit 1fffc9c

Browse files
Tinyweb: Sync with upstream.
Fixes #978. Includes the following changes: Adjust log.exc to log.exception belyalov/tinyweb@7669f03 Logging module dropped support for exc. These adjustments use the exception method instead. Co-authored-by: Stephen Jefferson <stephen@sjefferson.co.uk> force lowercase headers and force uppercase method belyalov/tinyweb@b4393ac Co-authored-by: eyJhb <eyjhb@eyjhb.dk> add compatibility for micropython above 1.19.0 belyalov/tinyweb@d067b98 * uasyncio is renamed to asyncio * directly use core from asyncio Co-authored-by: Fabian Clemenz <fclemenz@outlook.com>
1 parent c6384ce commit 1fffc9c

File tree

2 files changed

+45
-15
lines changed

2 files changed

+45
-15
lines changed
211 Bytes
Binary file not shown.

micropython/examples/common/lib/tinyweb/server.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
(C) Konstantin Belyalov 2017-2018
55
"""
66
import logging
7-
import uasyncio as asyncio
8-
import uasyncio.core
7+
import asyncio
98
import ujson as json
109
import gc
1110
import uos as os
@@ -16,15 +15,18 @@
1615

1716
log = logging.getLogger('WEB')
1817

19-
type_gen = type((lambda: (yield))()) # noqa: E275
18+
type_gen = type((lambda: (yield))())
2019

21-
# uasyncio v3 is shipped with MicroPython 1.13, and contains some subtle
20+
# with v1.21.0 release all u-modules where renamend without the u prefix
21+
# -> uasyncio no named asyncio
22+
# asyncio v3 is shipped with MicroPython 1.13, and contains some subtle
2223
# but breaking changes. See also https://github.com/peterhinch/micropython-async/blob/master/v3/README.md
23-
IS_UASYNCIO_V3 = hasattr(asyncio, "__version__") and asyncio.__version__ >= (3,)
24+
IS_ASYNCIO_V3 = hasattr(asyncio, "__version__") and asyncio.__version__ >= (3,)
2425

2526

2627
def urldecode_plus(s):
2728
"""Decode urlencoded string (including '+' char).
29+
2830
Returns decoded string
2931
"""
3032
s = s.replace('+', ' ')
@@ -42,6 +44,7 @@ def urldecode_plus(s):
4244

4345
def parse_query_string(s):
4446
"""Parse urlencoded string into dict.
47+
4548
Returns dict
4649
"""
4750
res = {}
@@ -75,6 +78,7 @@ def __init__(self, _reader):
7578
async def read_request_line(self):
7679
"""Read and parse first line (AKA HTTP Request Line).
7780
Function is generator.
81+
7882
Request line is something like:
7983
GET /something/script?param1=val1 HTTP/1.1
8084
"""
@@ -97,7 +101,9 @@ async def read_headers(self, save_headers=[]):
97101
"""Read and parse HTTP headers until \r\n\r\n:
98102
Optional argument 'save_headers' controls which headers to save.
99103
This is done mostly to deal with memory constrains.
104+
100105
Function is generator.
106+
101107
HTTP headers could be like:
102108
Host: google.com
103109
Content-Type: blah
@@ -111,12 +117,13 @@ async def read_headers(self, save_headers=[]):
111117
frags = line.split(b':', 1)
112118
if len(frags) != 2:
113119
raise HTTPException(400)
114-
if frags[0] in save_headers:
120+
if frags[0].lower() in save_headers:
115121
self.headers[frags[0]] = frags[1].strip()
116122

117123
async def read_parse_form_data(self):
118124
"""Read HTTP form data (payload), if any.
119125
Function is generator.
126+
120127
Returns:
121128
- dict of key / value pairs
122129
- None in case of no form data present
@@ -163,6 +170,7 @@ async def _send_headers(self):
163170
- HTTP request line
164171
- HTTP headers following by \r\n.
165172
This function is generator.
173+
166174
P.S.
167175
Because of usually we have only a few HTTP headers (2-5) it doesn't make sense
168176
to send them separately - sometimes it could increase latency.
@@ -181,8 +189,10 @@ async def _send_headers(self):
181189
async def error(self, code, msg=None):
182190
"""Generate HTTP error response
183191
This function is generator.
192+
184193
Arguments:
185194
code - HTTP response code
195+
186196
Example:
187197
# Not enough permissions. Send HTTP 403 - Forbidden
188198
await resp.error(403)
@@ -197,8 +207,10 @@ async def error(self, code, msg=None):
197207
async def redirect(self, location, msg=None):
198208
"""Generate HTTP redirect response to 'location'.
199209
Basically it will generate HTTP 302 with 'Location' header
210+
200211
Arguments:
201212
location - URL to redirect to
213+
202214
Example:
203215
# Redirect to /something
204216
await resp.redirect('/something')
@@ -213,9 +225,11 @@ async def redirect(self, location, msg=None):
213225

214226
def add_header(self, key, value):
215227
"""Add HTTP response header
228+
216229
Arguments:
217230
key - header name
218231
value - header value
232+
219233
Example:
220234
resp.add_header('Content-Encoding', 'gzip')
221235
"""
@@ -232,6 +246,7 @@ def add_access_control_headers(self):
232246
async def start_html(self):
233247
"""Start response with HTML content type.
234248
This function is generator.
249+
235250
Example:
236251
await resp.start_html()
237252
await resp.send('<html><h1>Hello, world!</h1></html>')
@@ -242,17 +257,21 @@ async def start_html(self):
242257
async def send_file(self, filename, content_type=None, content_encoding=None, max_age=2592000, buf_size=128):
243258
"""Send local file as HTTP response.
244259
This function is generator.
260+
245261
Arguments:
246262
filename - Name of file which exists in local filesystem
247263
Keyword arguments:
248264
content_type - Filetype. By default - None means auto-detect.
249265
max_age - Cache control. How long browser can keep this file on disk.
250266
By default - 30 days
251267
Set to 0 - to disable caching.
268+
252269
Example 1: Default use case:
253270
await resp.send_file('images/cat.jpg')
271+
254272
Example 2: Disable caching:
255273
await resp.send_file('static/index.html', max_age=0)
274+
256275
Example 3: Override content type:
257276
await resp.send_file('static/file.bin', content_type='application/octet-stream')
258277
"""
@@ -331,7 +350,7 @@ async def restful_resource_handler(req, resp, param=None):
331350
gc.collect()
332351
await resp.send('0\r\n\r\n')
333352
else:
334-
if type(res) is tuple:
353+
if type(res) == tuple:
335354
resp.code = res[1]
336355
res = res[0]
337356
elif res is None:
@@ -457,22 +476,22 @@ async def _handler(self, reader, writer):
457476
try:
458477
await resp.error(500)
459478
except Exception as e:
460-
log.exc(e, "")
479+
log.exception(f"Failed to send 500 error after OSError. Original error: {e}")
461480
except HTTPException as e:
462481
try:
463482
await resp.error(e.code)
464483
except Exception as e:
465-
log.exc(e)
484+
log.exception(f"Failed to send error after HTTPException. Original error: {e}")
466485
except Exception as e:
467486
# Unhandled expection in user's method
468487
log.error(req.path.decode())
469-
log.exc(e, "")
488+
log.exception(f"Unhandled exception in user's method. Original error: {e}")
470489
try:
471490
await resp.error(500)
472491
# Send exception info if desired
473492
if self.debug:
474493
sys.print_exception(e, resp.writer.s)
475-
except Exception:
494+
except Exception as e:
476495
pass
477496
finally:
478497
await writer.aclose()
@@ -485,9 +504,11 @@ async def _handler(self, reader, writer):
485504

486505
def add_route(self, url, f, **kwargs):
487506
"""Add URL to function mapping.
507+
488508
Arguments:
489509
url - url to map function with
490510
f - function to map
511+
491512
Keyword arguments:
492513
methods - list of allowed methods. Defaults to ['GET', 'POST']
493514
save_headers - contains list of HTTP headers to be saved. Case sensitive. Default - empty.
@@ -507,8 +528,8 @@ def add_route(self, url, f, **kwargs):
507528
params.update(kwargs)
508529
params['allowed_access_control_methods'] = ', '.join(params['methods'])
509530
# Convert methods/headers to bytestring
510-
params['methods'] = [x.encode() for x in params['methods']]
511-
params['save_headers'] = [x.encode() for x in params['save_headers']]
531+
params['methods'] = [x.encode().upper() for x in params['methods']]
532+
params['save_headers'] = [x.encode().lower() for x in params['save_headers']]
512533
# If URL has a parameter
513534
if url.endswith('>'):
514535
idx = url.rfind('<')
@@ -526,14 +547,18 @@ def add_route(self, url, f, **kwargs):
526547

527548
def add_resource(self, cls, url, **kwargs):
528549
"""Map resource (RestAPI) to URL
550+
529551
Arguments:
530552
cls - Resource class to map to
531553
url - url to map to class
532554
kwargs - User defined key args to pass to the handler.
555+
533556
Example:
534557
class myres():
535558
def get(self, data):
536559
return {'hello': 'world'}
560+
561+
537562
app.add_resource(myres, '/api/myres')
538563
"""
539564
methods = []
@@ -556,6 +581,7 @@ def get(self, data):
556581

557582
def catchall(self):
558583
"""Decorator for catchall()
584+
559585
Example:
560586
@app.catchall()
561587
def catchall_handler(req, resp):
@@ -572,6 +598,7 @@ def _route(f):
572598

573599
def route(self, url, **kwargs):
574600
"""Decorator for add_route()
601+
575602
Example:
576603
@app.route('/')
577604
def index(req, resp):
@@ -585,10 +612,12 @@ def _route(f):
585612

586613
def resource(self, url, method='GET', **kwargs):
587614
"""Decorator for add_resource() method
615+
588616
Examples:
589617
@app.resource('/users')
590618
def users(data):
591619
return {'a': 1}
620+
592621
@app.resource('/messages/<topic_id>')
593622
async def index(data, topic_id):
594623
yield '{'
@@ -617,8 +646,8 @@ async def _tcp_server(self, host, port, backlog):
617646
sock.listen(backlog)
618647
try:
619648
while True:
620-
if IS_UASYNCIO_V3:
621-
yield uasyncio.core._io_queue.queue_read(sock)
649+
if IS_ASYNCIO_V3:
650+
yield asyncio.core._io_queue.queue_read(sock)
622651
else:
623652
yield asyncio.IORead(sock)
624653
csock, caddr = sock.accept()
@@ -645,6 +674,7 @@ async def _tcp_server(self, host, port, backlog):
645674

646675
def run(self, host="127.0.0.1", port=8081, loop_forever=True):
647676
"""Run Web Server. By default it runs forever.
677+
648678
Keyword arguments:
649679
host - host to listen on. By default - localhost (127.0.0.1)
650680
port - port to listen on. By default - 8081

0 commit comments

Comments
 (0)