An asyncio web-server (Exercise)

WebServer with asyncio
##################################################################################
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. In this
# example we will just fill these values with random numbers. In a later example
# we re-use this code and fill the measured sensor values. This is why this 
# functions takes sensor values as parameters. A very convenient way to put the
# values into the html string is offered in python by using so called "f-strings".
# It is a simple way to have placeholders with formatting instructions in the 
# string. (A lot of info is on the web in case you do not know this)
# We should also include a simple css style-sheet so we learn how to serve files
# with out webserver. Ask if you do not know what stylesheets are.
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>

                   ??? Put here the html string  to create the table with the sesor values ???

            </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 into the variable content
    content = ???

    # send the HTML 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 (use again the writer.write command)
    ???

    # Now wait until everything is written : use the apropriate coroutine of the writer
    await writer.???

    # Finally close the stream and wait until it is closed. again use the routines of 
    # the writer. Note that the function to wait for the closing to finish is a 
    # co-routine. So you have to await for it!


##################################################################################
# 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.
    # The first line of the header containe the request we are interested in.
    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":
        #Now for this example generate some reasonable random numbers for the 
        # three sensor values (temperature pressure and humidity) and then
        # generate the webpage with these values inserted. (use the routine
        # above)

        ???     

        # Finally send the webpage to the client. Do not forget to first write the header. 
        # (See the sendfile routine) 

        ???

        # Now wait until all data has been sent, close the connection and wait for it 
        # to be closed

        ???

    else:
        # If we cannot serve the request we send back an eror message
        # The concept is exactly the same as sending out sensor web page. Just the 
        # contents is the Unknonw request string and nothing else. But header 
        # and sending/closing connection is the same code as above.

        ???

        writer.write(("<html><body><h2>Unknown request : %s</h2></body></html>" % request).encode())

        ???


##################################################################################
# The main co-routine which is setting up the network connection and then starts
# the webserver in three lines. Look up the commands in the asyncio documentation
# for micropython. 
##################################################################################
async def main():

    # connect to the network (the same as in the previous exercise)
    ssid = config.get( "ssid" )
    passwd = config.get( "password" )
    wifi_connect( user, password )

    # Now start the web server with uasyncio. It should listen on all network interfaces
    # and on port 8888. You need two commands for this. To tell the server to listen on
    # all interfaces you use "0.0.0.0" for the host IP. In our case this is not so important
    # since we only have one Network interface in the controller, however using this
    # method is convenient since we do not need to care which IP address we have received
    # when starting up the network interface (you could also insert the IP address which
    # you got from the DHCP request when you do "wifi_connect").
    ???
    ???

    # 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_WebServer.json" )

# Start the event loop and the main coroutine.
uasyncio.run(main())
mystyle.css
body {
    background-color: #d0d000;
}

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

.left {
    text-align:left;
}