Skip to content

Commit cbb869b

Browse files
committed
adding repo contents from micropython-uasyncio-webexample
0 parents  commit cbb869b

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

README.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Tiny Asyncio HTTP server for micropython
2+
========================================
3+
4+
Who DOESN'T need to serve some files over http on their micropython device?
5+
6+
This does it with asyncio and with extremely limited functionality.
7+
8+
The goal here is not to be fully featured, but to aid in settings up basic services on a device
9+
which can then host small things like configuring a wifi connection, basic diagnostics, etc.
10+
11+
Example
12+
-------
13+
If you want to see it in action, it's part of a worked example here:
14+
http://github.com/marcidy/micropython-uasyncio-webexample.
15+
16+
http/web.py
17+
-----------
18+
`server(reader, writer)` is used with `uasyncio.start_server(server, host, port)` and is
19+
called on new connections.
20+
21+
The server is really chatty at the moment with lots of print statements so you can see what's
22+
going on. I'll quiet those with a debug flag at some point.
23+
24+
The `route(location, resource)` function is used to match the requested route (say 'static/jquery-min.js`)
25+
with the resource on the filesytem that will be served (say '/www/jquery-3.5.1.js'). `route` is synchronous,
26+
but turns the resource into an async coroutine with the `pre_route` function. This is a bit convoluted,
27+
but will allow expansion over time. The result of calling `route` is that the module dict `routes` is
28+
population with pairs like::
29+
30+
routes = {
31+
b'/': b'/www/page.htm',
32+
b'/style.css': b'/www/style.css',
33+
b'/static/jquery-min.js': b'/www/jquery-3.5.1.js'
34+
}
35+
36+
Note that the entries are all decoded strings (b""). This works better with the raw data coming over
37+
the socket. Note we don't serve right off the filesystem. This lets you control what can be accessed.
38+
39+
`send_file(writer, file)` serves the item located at `file` via a chunked transfer. This can be html, js,
40+
anything. It's quite simple, however. It's sufficient to send over an html page and the jquery library
41+
and do some fancy stuff client side instead of server side.
42+
43+
Enhancements welcome, but we're trying to keep it small, so it realy ought to be something you can't do
44+
but is realy useful.
45+
46+
Or ever better, make it modular to add handlers on the fly and have a repo of handlers.

http/web.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import gc
2+
import re
3+
import os
4+
5+
6+
url_pat = re.compile(
7+
r'^(([^:/\\?#]+):)?' + # scheme # NOQA
8+
r'(//([^/\\?#]*))?' + # user:pass@host:port # NOQA
9+
r'([^\\?#]*)' + # route # NOQA
10+
r'(\\?([^#]*))?' + # query # NOQA
11+
r'(#(.*))?') # fragment # NOQA
12+
13+
14+
def pre_route(file):
15+
16+
async def _func(writer):
17+
await send_file(writer, file)
18+
19+
return _func
20+
21+
22+
routes = {}
23+
24+
# b'/': pre_route('/www/page.htm'),
25+
# b'/static/jquery.js': pre_route('/www/jquery-3.5.1.min.js')}
26+
27+
28+
def route(location, resource):
29+
global routes
30+
routes[location] = pre_route(resource)
31+
32+
33+
async def send_file(writer, file):
34+
fstat = os.stat(file)
35+
fsize = fstat[6]
36+
37+
writer.write(b'HTTP/1.1 200 OK\r\n')
38+
writer.write(b'Content-Type: text/html\r\n')
39+
writer.write('Content-Length: {}\r\n'.format(fsize).encode('utf-8'))
40+
writer.write(b'Accept-Ranges: none\r\n')
41+
writer.write(b'Transfer-Encoding: chunked\r\n')
42+
writer.write(b'\r\n')
43+
await writer.drain()
44+
gc.collect()
45+
max_chunk_size = 1024
46+
with open(file, 'rb') as f:
47+
for x in range(0, fsize, max_chunk_size):
48+
chunk_size = min(max_chunk_size, fsize-x)
49+
chunk_header = "{:x}\r\n".format(chunk_size).encode('utf-8')
50+
writer.write(chunk_header)
51+
writer.write(f.read(chunk_size))
52+
writer.write(b'\r\n')
53+
await writer.drain()
54+
gc.collect()
55+
writer.write(b"0\r\n")
56+
writer.write(b"\r\n")
57+
await writer.drain()
58+
writer.close()
59+
await writer.wait_closed()
60+
gc.collect()
61+
62+
63+
async def server(reader, writer):
64+
req = await reader.readline()
65+
print(req)
66+
method, uri, proto = req.split(b" ")
67+
m = re.match(url_pat, uri)
68+
route_req = m.group(5)
69+
70+
while True:
71+
h = await reader.readline()
72+
if h == b"" or h == b"\r\n":
73+
break
74+
print(h)
75+
76+
print("route: {}".format(route_req.decode('utf-8')))
77+
test = route_req in routes
78+
print("Route found?: {}".format(test))
79+
80+
if route_req in routes:
81+
await routes[route_req](writer)
82+
else:
83+
writer.write(b'HTTP/1.0 404 Not Found\r\n')
84+
writer.write(b'\r\n')
85+
await writer.drain()
86+
writer.close()
87+
await writer.wait_closed()
88+
gc.collect()

0 commit comments

Comments
 (0)