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())
mystyle.css
body {
    background-color: #d0d000;
}

table.sensor th,td {
    text-align: right;
    padding-right: 5px;
}

.left {
    text-align:left;
}