4
4
(C) Konstantin Belyalov 2017-2018
5
5
"""
6
6
import logging
7
- import uasyncio as asyncio
8
- import uasyncio .core
7
+ import asyncio
9
8
import ujson as json
10
9
import gc
11
10
import uos as os
16
15
17
16
log = logging .getLogger ('WEB' )
18
17
19
- type_gen = type ((lambda : (yield ))()) # noqa: E275
18
+ type_gen = type ((lambda : (yield ))())
20
19
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
22
23
# 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 ,)
24
25
25
26
26
27
def urldecode_plus (s ):
27
28
"""Decode urlencoded string (including '+' char).
29
+
28
30
Returns decoded string
29
31
"""
30
32
s = s .replace ('+' , ' ' )
@@ -42,6 +44,7 @@ def urldecode_plus(s):
42
44
43
45
def parse_query_string (s ):
44
46
"""Parse urlencoded string into dict.
47
+
45
48
Returns dict
46
49
"""
47
50
res = {}
@@ -75,6 +78,7 @@ def __init__(self, _reader):
75
78
async def read_request_line (self ):
76
79
"""Read and parse first line (AKA HTTP Request Line).
77
80
Function is generator.
81
+
78
82
Request line is something like:
79
83
GET /something/script?param1=val1 HTTP/1.1
80
84
"""
@@ -97,7 +101,9 @@ async def read_headers(self, save_headers=[]):
97
101
"""Read and parse HTTP headers until \r \n \r \n :
98
102
Optional argument 'save_headers' controls which headers to save.
99
103
This is done mostly to deal with memory constrains.
104
+
100
105
Function is generator.
106
+
101
107
HTTP headers could be like:
102
108
Host: google.com
103
109
Content-Type: blah
@@ -111,12 +117,13 @@ async def read_headers(self, save_headers=[]):
111
117
frags = line .split (b':' , 1 )
112
118
if len (frags ) != 2 :
113
119
raise HTTPException (400 )
114
- if frags [0 ] in save_headers :
120
+ if frags [0 ]. lower () in save_headers :
115
121
self .headers [frags [0 ]] = frags [1 ].strip ()
116
122
117
123
async def read_parse_form_data (self ):
118
124
"""Read HTTP form data (payload), if any.
119
125
Function is generator.
126
+
120
127
Returns:
121
128
- dict of key / value pairs
122
129
- None in case of no form data present
@@ -163,6 +170,7 @@ async def _send_headers(self):
163
170
- HTTP request line
164
171
- HTTP headers following by \r \n .
165
172
This function is generator.
173
+
166
174
P.S.
167
175
Because of usually we have only a few HTTP headers (2-5) it doesn't make sense
168
176
to send them separately - sometimes it could increase latency.
@@ -181,8 +189,10 @@ async def _send_headers(self):
181
189
async def error (self , code , msg = None ):
182
190
"""Generate HTTP error response
183
191
This function is generator.
192
+
184
193
Arguments:
185
194
code - HTTP response code
195
+
186
196
Example:
187
197
# Not enough permissions. Send HTTP 403 - Forbidden
188
198
await resp.error(403)
@@ -197,8 +207,10 @@ async def error(self, code, msg=None):
197
207
async def redirect (self , location , msg = None ):
198
208
"""Generate HTTP redirect response to 'location'.
199
209
Basically it will generate HTTP 302 with 'Location' header
210
+
200
211
Arguments:
201
212
location - URL to redirect to
213
+
202
214
Example:
203
215
# Redirect to /something
204
216
await resp.redirect('/something')
@@ -213,9 +225,11 @@ async def redirect(self, location, msg=None):
213
225
214
226
def add_header (self , key , value ):
215
227
"""Add HTTP response header
228
+
216
229
Arguments:
217
230
key - header name
218
231
value - header value
232
+
219
233
Example:
220
234
resp.add_header('Content-Encoding', 'gzip')
221
235
"""
@@ -232,6 +246,7 @@ def add_access_control_headers(self):
232
246
async def start_html (self ):
233
247
"""Start response with HTML content type.
234
248
This function is generator.
249
+
235
250
Example:
236
251
await resp.start_html()
237
252
await resp.send('<html><h1>Hello, world!</h1></html>')
@@ -242,17 +257,21 @@ async def start_html(self):
242
257
async def send_file (self , filename , content_type = None , content_encoding = None , max_age = 2592000 , buf_size = 128 ):
243
258
"""Send local file as HTTP response.
244
259
This function is generator.
260
+
245
261
Arguments:
246
262
filename - Name of file which exists in local filesystem
247
263
Keyword arguments:
248
264
content_type - Filetype. By default - None means auto-detect.
249
265
max_age - Cache control. How long browser can keep this file on disk.
250
266
By default - 30 days
251
267
Set to 0 - to disable caching.
268
+
252
269
Example 1: Default use case:
253
270
await resp.send_file('images/cat.jpg')
271
+
254
272
Example 2: Disable caching:
255
273
await resp.send_file('static/index.html', max_age=0)
274
+
256
275
Example 3: Override content type:
257
276
await resp.send_file('static/file.bin', content_type='application/octet-stream')
258
277
"""
@@ -331,7 +350,7 @@ async def restful_resource_handler(req, resp, param=None):
331
350
gc .collect ()
332
351
await resp .send ('0\r \n \r \n ' )
333
352
else :
334
- if type (res ) is tuple :
353
+ if type (res ) == tuple :
335
354
resp .code = res [1 ]
336
355
res = res [0 ]
337
356
elif res is None :
@@ -457,22 +476,22 @@ async def _handler(self, reader, writer):
457
476
try :
458
477
await resp .error (500 )
459
478
except Exception as e :
460
- log .exc ( e , " " )
479
+ log .exception ( f"Failed to send 500 error after OSError. Original error: { e } " )
461
480
except HTTPException as e :
462
481
try :
463
482
await resp .error (e .code )
464
483
except Exception as e :
465
- log .exc ( e )
484
+ log .exception ( f"Failed to send error after HTTPException. Original error: { e } " )
466
485
except Exception as e :
467
486
# Unhandled expection in user's method
468
487
log .error (req .path .decode ())
469
- log .exc ( e , " " )
488
+ log .exception ( f"Unhandled exception in user's method. Original error: { e } " )
470
489
try :
471
490
await resp .error (500 )
472
491
# Send exception info if desired
473
492
if self .debug :
474
493
sys .print_exception (e , resp .writer .s )
475
- except Exception :
494
+ except Exception as e :
476
495
pass
477
496
finally :
478
497
await writer .aclose ()
@@ -485,9 +504,11 @@ async def _handler(self, reader, writer):
485
504
486
505
def add_route (self , url , f , ** kwargs ):
487
506
"""Add URL to function mapping.
507
+
488
508
Arguments:
489
509
url - url to map function with
490
510
f - function to map
511
+
491
512
Keyword arguments:
492
513
methods - list of allowed methods. Defaults to ['GET', 'POST']
493
514
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):
507
528
params .update (kwargs )
508
529
params ['allowed_access_control_methods' ] = ', ' .join (params ['methods' ])
509
530
# 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' ]]
512
533
# If URL has a parameter
513
534
if url .endswith ('>' ):
514
535
idx = url .rfind ('<' )
@@ -526,14 +547,18 @@ def add_route(self, url, f, **kwargs):
526
547
527
548
def add_resource (self , cls , url , ** kwargs ):
528
549
"""Map resource (RestAPI) to URL
550
+
529
551
Arguments:
530
552
cls - Resource class to map to
531
553
url - url to map to class
532
554
kwargs - User defined key args to pass to the handler.
555
+
533
556
Example:
534
557
class myres():
535
558
def get(self, data):
536
559
return {'hello': 'world'}
560
+
561
+
537
562
app.add_resource(myres, '/api/myres')
538
563
"""
539
564
methods = []
@@ -556,6 +581,7 @@ def get(self, data):
556
581
557
582
def catchall (self ):
558
583
"""Decorator for catchall()
584
+
559
585
Example:
560
586
@app.catchall()
561
587
def catchall_handler(req, resp):
@@ -572,6 +598,7 @@ def _route(f):
572
598
573
599
def route (self , url , ** kwargs ):
574
600
"""Decorator for add_route()
601
+
575
602
Example:
576
603
@app.route('/')
577
604
def index(req, resp):
@@ -585,10 +612,12 @@ def _route(f):
585
612
586
613
def resource (self , url , method = 'GET' , ** kwargs ):
587
614
"""Decorator for add_resource() method
615
+
588
616
Examples:
589
617
@app.resource('/users')
590
618
def users(data):
591
619
return {'a': 1}
620
+
592
621
@app.resource('/messages/<topic_id>')
593
622
async def index(data, topic_id):
594
623
yield '{'
@@ -617,8 +646,8 @@ async def _tcp_server(self, host, port, backlog):
617
646
sock .listen (backlog )
618
647
try :
619
648
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 )
622
651
else :
623
652
yield asyncio .IORead (sock )
624
653
csock , caddr = sock .accept ()
@@ -645,6 +674,7 @@ async def _tcp_server(self, host, port, backlog):
645
674
646
675
def run (self , host = "127.0.0.1" , port = 8081 , loop_forever = True ):
647
676
"""Run Web Server. By default it runs forever.
677
+
648
678
Keyword arguments:
649
679
host - host to listen on. By default - localhost (127.0.0.1)
650
680
port - port to listen on. By default - 8081
0 commit comments