kopia lustrzana https://github.com/pimoroni/pimoroni-pico
Tinyweb: Sync with upstream.
Fixes #978. Includes the following changes: Adjust log.exc to log.exceptionpull/986/head7669f03cdc
Logging module dropped support for exc. These adjustments use the exception method instead. force lowercase headers and force uppercase methodb4393ac65a
add compatibility for micropython above 1.19.0d067b98dfd
* uasyncio is renamed to asyncio * directly use core from asyncio Co-authored-by: Stephen Jefferson <stephen@sjefferson.co.uk> Co-authored-by: eyJhb <eyjhb@eyjhb.dk> Co-authored-by: Fabian Clemenz <fclemenz@outlook.com>
rodzic
c6384ce9d5
commit
65983ec6d8
Plik binarny nie jest wyświetlany.
|
@ -4,8 +4,7 @@ MIT license
|
|||
(C) Konstantin Belyalov 2017-2018
|
||||
"""
|
||||
import logging
|
||||
import uasyncio as asyncio
|
||||
import uasyncio.core
|
||||
import asyncio
|
||||
import ujson as json
|
||||
import gc
|
||||
import uos as os
|
||||
|
@ -16,15 +15,18 @@ import usocket as socket
|
|||
|
||||
log = logging.getLogger('WEB')
|
||||
|
||||
type_gen = type((lambda: (yield))()) # noqa: E275
|
||||
type_gen = type((lambda: (yield))())
|
||||
|
||||
# uasyncio v3 is shipped with MicroPython 1.13, and contains some subtle
|
||||
# with v1.21.0 release all u-modules where renamend without the u prefix
|
||||
# -> uasyncio no named asyncio
|
||||
# asyncio v3 is shipped with MicroPython 1.13, and contains some subtle
|
||||
# but breaking changes. See also https://github.com/peterhinch/micropython-async/blob/master/v3/README.md
|
||||
IS_UASYNCIO_V3 = hasattr(asyncio, "__version__") and asyncio.__version__ >= (3,)
|
||||
IS_ASYNCIO_V3 = hasattr(asyncio, "__version__") and asyncio.__version__ >= (3,)
|
||||
|
||||
|
||||
def urldecode_plus(s):
|
||||
"""Decode urlencoded string (including '+' char).
|
||||
|
||||
Returns decoded string
|
||||
"""
|
||||
s = s.replace('+', ' ')
|
||||
|
@ -42,6 +44,7 @@ def urldecode_plus(s):
|
|||
|
||||
def parse_query_string(s):
|
||||
"""Parse urlencoded string into dict.
|
||||
|
||||
Returns dict
|
||||
"""
|
||||
res = {}
|
||||
|
@ -75,6 +78,7 @@ class request:
|
|||
async def read_request_line(self):
|
||||
"""Read and parse first line (AKA HTTP Request Line).
|
||||
Function is generator.
|
||||
|
||||
Request line is something like:
|
||||
GET /something/script?param1=val1 HTTP/1.1
|
||||
"""
|
||||
|
@ -97,7 +101,9 @@ class request:
|
|||
"""Read and parse HTTP headers until \r\n\r\n:
|
||||
Optional argument 'save_headers' controls which headers to save.
|
||||
This is done mostly to deal with memory constrains.
|
||||
|
||||
Function is generator.
|
||||
|
||||
HTTP headers could be like:
|
||||
Host: google.com
|
||||
Content-Type: blah
|
||||
|
@ -111,12 +117,13 @@ class request:
|
|||
frags = line.split(b':', 1)
|
||||
if len(frags) != 2:
|
||||
raise HTTPException(400)
|
||||
if frags[0] in save_headers:
|
||||
if frags[0].lower() in save_headers:
|
||||
self.headers[frags[0]] = frags[1].strip()
|
||||
|
||||
async def read_parse_form_data(self):
|
||||
"""Read HTTP form data (payload), if any.
|
||||
Function is generator.
|
||||
|
||||
Returns:
|
||||
- dict of key / value pairs
|
||||
- None in case of no form data present
|
||||
|
@ -163,6 +170,7 @@ class response:
|
|||
- HTTP request line
|
||||
- HTTP headers following by \r\n.
|
||||
This function is generator.
|
||||
|
||||
P.S.
|
||||
Because of usually we have only a few HTTP headers (2-5) it doesn't make sense
|
||||
to send them separately - sometimes it could increase latency.
|
||||
|
@ -181,8 +189,10 @@ class response:
|
|||
async def error(self, code, msg=None):
|
||||
"""Generate HTTP error response
|
||||
This function is generator.
|
||||
|
||||
Arguments:
|
||||
code - HTTP response code
|
||||
|
||||
Example:
|
||||
# Not enough permissions. Send HTTP 403 - Forbidden
|
||||
await resp.error(403)
|
||||
|
@ -197,8 +207,10 @@ class response:
|
|||
async def redirect(self, location, msg=None):
|
||||
"""Generate HTTP redirect response to 'location'.
|
||||
Basically it will generate HTTP 302 with 'Location' header
|
||||
|
||||
Arguments:
|
||||
location - URL to redirect to
|
||||
|
||||
Example:
|
||||
# Redirect to /something
|
||||
await resp.redirect('/something')
|
||||
|
@ -213,9 +225,11 @@ class response:
|
|||
|
||||
def add_header(self, key, value):
|
||||
"""Add HTTP response header
|
||||
|
||||
Arguments:
|
||||
key - header name
|
||||
value - header value
|
||||
|
||||
Example:
|
||||
resp.add_header('Content-Encoding', 'gzip')
|
||||
"""
|
||||
|
@ -232,6 +246,7 @@ class response:
|
|||
async def start_html(self):
|
||||
"""Start response with HTML content type.
|
||||
This function is generator.
|
||||
|
||||
Example:
|
||||
await resp.start_html()
|
||||
await resp.send('<html><h1>Hello, world!</h1></html>')
|
||||
|
@ -242,6 +257,7 @@ class response:
|
|||
async def send_file(self, filename, content_type=None, content_encoding=None, max_age=2592000, buf_size=128):
|
||||
"""Send local file as HTTP response.
|
||||
This function is generator.
|
||||
|
||||
Arguments:
|
||||
filename - Name of file which exists in local filesystem
|
||||
Keyword arguments:
|
||||
|
@ -249,10 +265,13 @@ class response:
|
|||
max_age - Cache control. How long browser can keep this file on disk.
|
||||
By default - 30 days
|
||||
Set to 0 - to disable caching.
|
||||
|
||||
Example 1: Default use case:
|
||||
await resp.send_file('images/cat.jpg')
|
||||
|
||||
Example 2: Disable caching:
|
||||
await resp.send_file('static/index.html', max_age=0)
|
||||
|
||||
Example 3: Override content type:
|
||||
await resp.send_file('static/file.bin', content_type='application/octet-stream')
|
||||
"""
|
||||
|
@ -331,7 +350,7 @@ async def restful_resource_handler(req, resp, param=None):
|
|||
gc.collect()
|
||||
await resp.send('0\r\n\r\n')
|
||||
else:
|
||||
if type(res) is tuple:
|
||||
if type(res) == tuple:
|
||||
resp.code = res[1]
|
||||
res = res[0]
|
||||
elif res is None:
|
||||
|
@ -457,22 +476,22 @@ class webserver:
|
|||
try:
|
||||
await resp.error(500)
|
||||
except Exception as e:
|
||||
log.exc(e, "")
|
||||
log.exception(f"Failed to send 500 error after OSError. Original error: {e}")
|
||||
except HTTPException as e:
|
||||
try:
|
||||
await resp.error(e.code)
|
||||
except Exception as e:
|
||||
log.exc(e)
|
||||
log.exception(f"Failed to send error after HTTPException. Original error: {e}")
|
||||
except Exception as e:
|
||||
# Unhandled expection in user's method
|
||||
log.error(req.path.decode())
|
||||
log.exc(e, "")
|
||||
log.exception(f"Unhandled exception in user's method. Original error: {e}")
|
||||
try:
|
||||
await resp.error(500)
|
||||
# Send exception info if desired
|
||||
if self.debug:
|
||||
sys.print_exception(e, resp.writer.s)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
pass
|
||||
finally:
|
||||
await writer.aclose()
|
||||
|
@ -485,9 +504,11 @@ class webserver:
|
|||
|
||||
def add_route(self, url, f, **kwargs):
|
||||
"""Add URL to function mapping.
|
||||
|
||||
Arguments:
|
||||
url - url to map function with
|
||||
f - function to map
|
||||
|
||||
Keyword arguments:
|
||||
methods - list of allowed methods. Defaults to ['GET', 'POST']
|
||||
save_headers - contains list of HTTP headers to be saved. Case sensitive. Default - empty.
|
||||
|
@ -507,8 +528,8 @@ class webserver:
|
|||
params.update(kwargs)
|
||||
params['allowed_access_control_methods'] = ', '.join(params['methods'])
|
||||
# Convert methods/headers to bytestring
|
||||
params['methods'] = [x.encode() for x in params['methods']]
|
||||
params['save_headers'] = [x.encode() for x in params['save_headers']]
|
||||
params['methods'] = [x.encode().upper() for x in params['methods']]
|
||||
params['save_headers'] = [x.encode().lower() for x in params['save_headers']]
|
||||
# If URL has a parameter
|
||||
if url.endswith('>'):
|
||||
idx = url.rfind('<')
|
||||
|
@ -526,14 +547,18 @@ class webserver:
|
|||
|
||||
def add_resource(self, cls, url, **kwargs):
|
||||
"""Map resource (RestAPI) to URL
|
||||
|
||||
Arguments:
|
||||
cls - Resource class to map to
|
||||
url - url to map to class
|
||||
kwargs - User defined key args to pass to the handler.
|
||||
|
||||
Example:
|
||||
class myres():
|
||||
def get(self, data):
|
||||
return {'hello': 'world'}
|
||||
|
||||
|
||||
app.add_resource(myres, '/api/myres')
|
||||
"""
|
||||
methods = []
|
||||
|
@ -556,6 +581,7 @@ class webserver:
|
|||
|
||||
def catchall(self):
|
||||
"""Decorator for catchall()
|
||||
|
||||
Example:
|
||||
@app.catchall()
|
||||
def catchall_handler(req, resp):
|
||||
|
@ -572,6 +598,7 @@ class webserver:
|
|||
|
||||
def route(self, url, **kwargs):
|
||||
"""Decorator for add_route()
|
||||
|
||||
Example:
|
||||
@app.route('/')
|
||||
def index(req, resp):
|
||||
|
@ -585,10 +612,12 @@ class webserver:
|
|||
|
||||
def resource(self, url, method='GET', **kwargs):
|
||||
"""Decorator for add_resource() method
|
||||
|
||||
Examples:
|
||||
@app.resource('/users')
|
||||
def users(data):
|
||||
return {'a': 1}
|
||||
|
||||
@app.resource('/messages/<topic_id>')
|
||||
async def index(data, topic_id):
|
||||
yield '{'
|
||||
|
@ -617,8 +646,8 @@ class webserver:
|
|||
sock.listen(backlog)
|
||||
try:
|
||||
while True:
|
||||
if IS_UASYNCIO_V3:
|
||||
yield uasyncio.core._io_queue.queue_read(sock)
|
||||
if IS_ASYNCIO_V3:
|
||||
yield asyncio.core._io_queue.queue_read(sock)
|
||||
else:
|
||||
yield asyncio.IORead(sock)
|
||||
csock, caddr = sock.accept()
|
||||
|
@ -645,6 +674,7 @@ class webserver:
|
|||
|
||||
def run(self, host="127.0.0.1", port=8081, loop_forever=True):
|
||||
"""Run Web Server. By default it runs forever.
|
||||
|
||||
Keyword arguments:
|
||||
host - host to listen on. By default - localhost (127.0.0.1)
|
||||
port - port to listen on. By default - 8081
|
||||
|
|
Ładowanie…
Reference in New Issue