uhttpd — micro HTTP server

This module provides micro HTTP server with automatic file serving, file uploading, basic authentication handing, REPL over a websocket, etc.

The server runs as a separate thread (task), custom url handlers can be registered. The python function, provided via the callback argument during the url registration, will be called when the request arrives.

The callback function will be executed in the HTTP thread (not the micropython thread). The atomicity is guaranteed by the GIL.

The callback function can only be executed when the HTTP thread aquires the GIL. Therefore the main code must “idle” on a regular basis to give the HTTP thread an opportunity to execute pending tasks.

Currently, the module is implemented only for the ESP32 port.

Example:

# import the module
import uhttpd

# setup the network connection
def setup_network():
    pass

# Custom url handler
def handle_my_url(req):
    req.write('Hello World', mime='text/plain')

# Setup the server
httpd = uhttpd.Server()
#  - register the custom handler
httpd.route('/my', uhttpd.GET, handle_my_url)
#  - initialize REPL
httpd.ws('/ws', None)
#  - initialize file server
httpd.serve()

class Server

The server is configured via calling the corresponding class methods, however, there is no runner function or an async task - the server does all the work in a separate thread (created internally inside the constructor). Therefore, it is necessary to keep the class instance from being garbage-collected.

Constructors

class uhttpd.Server(port=80, ...)

Construct a http server object and start the server on the given port (defaults to 80). Additional parameters are:

  • acl. Defines the access control mode. Can be uhttpd.AUTH_OPEN, uhttpd.AUTH_BASIC, or uhttpd.AUTH_CUSTOM.

  • cors. A boolean parameter controlling the CORS policy. If set to True, the Access-Control-Allow-Origin: * header will be set automatically for all the server replies. If set to False (the default), the CORS header will not be present, unless it is explicitly added by the callback function.

  • routes. Can be an integer (sets the maximum number of URL handlers. Defaults to 8. Please see the note below.) or an iterable. If an iterable is provided, each item must itself be an iterable of 3 elements: the url, the method, and the callback - essentialy the same arguments, that are passed to the uhttpd.Server.route() call. Please note, that in this case, no further calls to the route() method should be made.

  • auth. If uhttpd.AUTH_BASIC has been selected, provide the authorization header value (as a bytes-like object). Please note, that the complete value should be provided. In case of basic authorization with the answer:42 login-password, the auth value should be b'Basic YW5zd2VyOjQy'. If uhttpd.AUTH_CUSTOM has been selected, provide a function to be called for each incoming request. The handler must take exactly one argument which is the Request instance.

  • events - the maximum number of the Server-sent event sockets, that this server can serve simultaneously. Please note, that the board has a limited number of sockets available (globally), therefore 3, at most 4 is a resonable maximum value for this parameter. Default valus is 0.

Note

About calculating the number of routes.

  • one slot is reserved for the OPTIONS request

  • file server occupies one slot

  • file uploader occupies one slot if the DELETE verb is not supported, and two if it is.

  • websocket occupies one slot

  • custom handler occupies one to three slots (depends on the number of verbs supported).

Note

About routes regiteration order.

  • The routes support wildcards (*)

  • The routes are matched in the order of registration (the first registered is the first probed)

  • As a general rule, the wildcard routes must be the last registered (including the file server and uploader).

Methods

Server.port()

Returns the port number that this server instance is using

Server.route(url, methods, callback)

Note

If an list of routed has been passed to the constructor, do not call this method.

Registers the handler for url (may be a wildcard). The handler will accept the requests with methods verbs (may be any combination of uhttpd.GET, uhttpd.POST, and uhttpd.DELETE). The callback function must accept exactly one argument which is the Request instance

Server.serve(prefix, base=none, dirs=True, ext=False)

Initialize this file server. The file server will respond to GET requests to <prefix>/*.

The prefix must start with a slash (/). If the prefix is not specified, an empty prefix will be used (i.e. the url wildcard will be /*).

The file path resolution is as follows:

The url /prefix/path/to/file will be first resolved to /path/to/file, then base will be prepended (if not None). For example if prefix='/fs' and base=/www/data, the request /fs/subdir/hello.txt will try to read the /www/data/subdir/hello.txt file. If the resolved path ends with a slash and dirs=True, a JSON document describing the requested directory contents will be returned. The base value (if not None) must start with a slash (/).

Directory document example:

{
    "path" : "/var/",
    "list" : [
        {
            "path": "..",
            "type": "directory",
            "size": 0
        },
        {
            "path": "my_folder",
            "type": "directory",
            "size": 0
        },
        {
            "path": "hello.txt",
            "type": "file",
            "size": 42
        }
    ]
}

If ext=True, the external storage mount path will be prepended to the resolved filename, and if the resulting file (or directory) exists, it will be served. If no such file or directory exists, the prepended path will be removed and the original resolved path will be processed.

Server.receive(dynamic, prefix=None, extonly=False, delete=False)

Initialize the file receiver. The prefix (if not None) must start with a slash (/). If extonly=True, the file receiver will refuse to upload files to the internal storage.

If dynamic=True (the default variant):

The file receive will respond to the following POST requests (and DELETE requests if delete=True): <prefix>/* The url /prefix/path/to/file will be resolved to /path/to/file. The server will try to open this file for writing (the parent directory will be created automatically) and forward all data received to this file (verbatim). If the resolved path ends with a slash (/), the corresponding directory will be created, the success or failure code returned. The request body will be ignored (do not transmit one). If the verb is DELETE, the resolved path will be deleted (if it ends with a slash, the rmdir command will be executed, if not - the corresponding file will be removed). If force=1 is present in the query string, the directory will be removed recursively.

If dynamic==False, the server behaviour is as described above with the following differences:

  • The prefix is treated as the endpoint location, and therefore it must not be None.

  • The file path is taken from the Content-Disposition header which is mandatory in this case.

The Content-Disposition header must be of the following format:

Content-Disposition: attachement; filename="/path/to/file"
Server.event(url)

A GET request to this url will return a text/event-stream to be used by the EventSource (see, for example, MDN). The url must not be a wildcard.

Note

At most one such url can be registered per server. Not to be confused with the maximum number of EvenSource clients being served simultaneously (see the events parameter of the constructor).

Server.dispatch(data, event)

Send data to all EventSource clients currently listening. The optional parameter event can be used to set the name of the event. Returns False if noone is listening at this time.

Server.file_acl(cb)

Sets the callback function to be called when a file is read or written or a directory is accessed or created. The callback must take exactly one argument which is the path (the path will end with a slash for directories). The callback must return an integer. The return value can be one of uhttpd.ACL_RO (if only read access should be granted), uhttpd.ACL_RW (if only write access should be granted), and uhttpd.ACL_RW (if read and write access is allowed) or a HTTP error code (greater o equal to 400). If an invalid value is returned, the default 403 HTTP code is used. The access to the file is denied, the request aborted.

Server.ws(url, pwd)

Note

May be unavailable in some firmwares.

A GET request to url will be upgraded to the WebSocket connection, that gives access to the micropython REPL. An optional pwd argument password protects the access to the REPL (indepenent of the Server authentication).

At most one handler can be registered (not only per Server object instance, but globally).

class Request

When a custom request arrives, the server calls the corresponding handler (registered via the Server.route method). The handler receives the Request object as the only argument.

Constructors

No public constructor is available. An instance is created internally and passed to the handler.

Methods

Request.server()

Returns the server object associated with this request.

Request.method()

Returns the HTTP verb for this request. The returned value is an integer - one of the uhttpd.GET, uhttpd.POST, uhttpd.DELETE

Request.uri()

Returns the full uri of the request including the query and fragment, if present.

Request.length()

Returns the content length of the request (as reported by the Content-Length header)

Request.path()

Returns the path of the request (the URI without the query and fragment parts).

Request.query(key)

Search the query string for the key entry and return its value. If multiple entries with this key exist in the query string, the value of the first one is returned. If the key is found an has a value, the value is returned (as bytes). If only the key is found (without a value), True is returned. In any other case, None is returned.

Request.parse_qs()

Parses the query string and returns it as a dict. Every key is a bytes object. Values are either bytes (if the value is present) or True (if the value is missing in the query string). If multiple entries with the same key exist in the query string, the last one will be returned.

Request.read(len=- 1)

Reads the body of the request (at most len bytes if len > 0 or all available bytes if len is negative). Returns the result as a bytes object.

Request.readinto(buffer)

Reads at most len(buffer) bytes of the request body and puts then in the buffer. Returns the number of bytes received.

Request.write(data, status=None, mime=None, headers=None, allow_cors=False)

Sends data as the response to the request. This method can be called only once per request and is not compatible with the Request.send_chunk and the Request.send methods (i.e. cannot be called after one of those methods have been called).

Optional arguments:

  • status. If not None, the HTTP status is set to this value (a string or a bytes-like object is expected, no HTTP validity check). The default is 200 OK.

  • mime. IF not None, sets the Content-Type header to this value (a string or a bytes-like object is expected, no HTTP validity check). The default is text/html

  • headers - an optional dict with auxiliary headers. Each key and value must be a string or a bytes-like object.

  • allow_cors - is set to True, overrides the global parameter cors of the server object. Do not set to True if already enabled by the global config.

Request.send_chunk(data, status=None, mime=None, headers=None, allow_cors=False)

Sends data as chunks (ref. Chunked transfer encoding). If this is the first call for this request, optional argument can be provided. For their meaning, refer to the Request.write method description. If this is not the first call fot this request, do not provide additional arguments - they will be ignored.

If data=None, finalizes the request (no further Request.send_chunk calls are permitted). After one or more send_chunk(data) calls, the final send_chunk(None) call is mandatory.

Request.send(data)

Sends data to the HTTP socket verbatim. When using this api, the callee is responsible for formatting the data accoding to the HTTP protocol (i.e the status lines, all the headers, etc).

Constants

uhttpd.AUTH_OPEN

Open access server

uhttpd.AUTH_BASIC

Basic authentication (user and password)

uhttpd.AUTH_CUSTOM

Custom authentication (via a callback function)

uhttpd.ACL_RW

File/Directory read-write access granted

uhttpd.ACL_RO

File/Directory read-only access

uhttpd.ACL_WO

File/Directory write-only access