An asyncio web-server (Solution)
WebServer.py
##################################################################################
import network # mqtt needs network connection #
from utils import Config # to read a configurattion file #
from utils import wifi_connect # a routine to connect to a wifi #
import time # standard package for timing #
import json # we publish our data in json format #
import uasyncio # to write the web server #
from random import uniform # in this example we displan random numbers#
##################################################################################
##################################################################################
# The following function simply creates a html webpage and puts it into a
# String variable. The webpage contains a small table for sensor values.
# In a later example we will insert the values read from the sensor. This
# is why this functions takes sensor values as parameters.
def webpage( temp, pressure, humidity ):
##################################################################################
html = f"""<!DOCTYPE html>
<html lan='en'>
<head>
<title>A sensor simulation</title>
<link rel="stylesheet" href="mystyle.css">
</head>
<body>
<h1>Sensor values</h1>
<p></p>
<table class="sensor">
<thead>
</thead>
<tbody>
<tr><th>Temperature</th><th>:</th><td>{temp:.1f}</td><td class="left">C</td></tr>
<tr><th>Pressure</th><th>:</th><td>{pressure:.1f}</td><td class="left">mb</td></tr>
<tr><th>Humidity</th><th>:</th><td>{humidity:.0f}</td><td class="left">%</td></tr>
</tbody>
</table>
</body>
</html>
"""
return html
##################################################################################
##################################################################################
# We define a co-routine to send a file. The following parameters are given to the
# function:
# fn : the filename of the file to send
# write : The stream to which the contents of the file needs to be written
# This object comes from the asyncio library
# ctype : The mime-type of the webpage (needs to be specified in the http header)
#
# This routine is a coroutine since it uses the writer object from the asyncio
# webserver to send out data to the webclient. The writer has some coroutines which
# yield until all the data is really written to the network and it yields a second
# time when the connection to the client is closed (this takes usually some time)
async def sendfile( fn, writer, ctype ):
##################################################################################
# read the file
content = open( fn, 'r' ).read()
# send the header to the HTTP client
writer.write( ('HTTP/1.0 200 OK\r\nContent-type: ' + ctype + '\r\n\r\n').encode() )
# send the contents to the HTTP client
writer.write(content.encode())
# Now wait until everything is written
await writer.drain()
# Finally close the stream and wait until it is closed
writer.close()
await writer.wait_closed()
##################################################################################
# This co-routine is called when a HTTP request comes in. The arguments are
# asyncio streams which can be used for reading the request and sending back the
# reply (e.g. the requested web page)
async def handle_request(reader, writer):
##################################################################################
# read the header from the reader. readline() is a coroutine and as long
# as there is not request coming the EventLoop schedules other tasks.
print("in handle request")
request_line = await reader.readline()
# skip all other header lines (until the empty line)
stop = False
while not stop:
li = await reader.readline()
if li != b"\r\n":
pass
else:
stop=True
# Now take the second word of the request, which should contain the filename
# we want to send (in our example either a style sheet file (.css) or a html
# page)
request = str(request_line, 'utf-8').split()[1]
# for debugging:
print("request: %s" % request )
# Check for the extension of the file to send back. If it is the css file then
# send it back. If it is the html file then send back the webpage created with
# the function above. For now we take random numbers instead of real sensor
# values
# Note that this is a super simplified example: We do not even check the name
# of the files here.
if request[-3:] == "css":
await sendfile( request[1:], writer, "text/css" )
elif request[-4:] == "html":
temp = uniform(0,38)
humidity=uniform(30,80)
pressure=uniform(950,1050)
response = webpage( temp,pressure,humidity )
writer.write( 'HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n'.encode())
writer.write(response.encode())
await writer.drain()
writer.close()
await writer.wait_closed()
else:
# If we cannot serve the request we send back an eror message
writer.write( 'HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n'.encode())
writer.write(("<html><body><h2>Unknown request : %s</h2></body></html>" % request).encode())
await writer.drain()
writer.close()
await writer.wait_closed()
##################################################################################
# The main co-routine which is setting up the network connection and then starts
# the webserver in three lines.
async def main():
##################################################################################
# connect to the network (the same as in the previous exercise)
ssid = config.get( "ssid" )
password = config.get( "password" )
wifi_connect( ssid, password )
# Now start the web server with uasyncio
server = uasyncio.start_server( handle_request, "0.0.0.0", 8888 )
servertask = uasyncio.create_task( server )
# In a more complex program here other things can be done. But at least from
# time to time you have to yield so that also the webserver gives some cpu-
# time.
while True:
await uasyncio.sleep(10)
####################### Here the main programme starts ###########################
# read the configuration file. We want "config" to be in the global scope
# therefore we create it here and not in the "main()" function.
config = Config( "config_network.json" )
uasyncio.run(main())