*********************************** :mod:`uhttpd` --- micro HTTP server *********************************** .. module:: uhttpd :synopsis: 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:: 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 :meth:`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 :ref:`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 ------- .. method:: Server.port() Returns the port number that this server instance is using .. method:: 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 :ref:`Request ` instance .. method:: Server.serve(prefix, base=none, dirs=True, ext=False) Initialize this file server. The file server will respond to GET requests to ``/*``. 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. .. method:: 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``): ``/*`` 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" .. method:: 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). .. method:: 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. .. method:: 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. .. method:: 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). .. _uhttpd.Request: class Request ============= When a custom request arrives, the server calls the corresponding handler (registered via the `Server.route` method). The handler receives the :ref:`Request ` object as the only argument. Constructors ------------ No public constructor is available. An instance is created internally and passed to the handler. Methods ------- .. method:: Request.server() Returns the server object associated with this request. .. method:: Request.method() Returns the HTTP verb for this request. The returned value is an integer - one of the ``uhttpd.GET``, ``uhttpd.POST``, ``uhttpd.DELETE`` .. method:: Request.uri() Returns the full uri of the request including the query and fragment, if present. .. method:: Request.length() Returns the content length of the request (as reported by the Content-Length header) .. method:: Request.path() Returns the path of the request (the URI without the query and fragment parts). .. method:: 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. .. method:: 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. .. method:: 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. .. method:: 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. .. method:: 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. .. method:: 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. .. method:: 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 --------- .. data:: uhttpd.AUTH_OPEN Open access server .. data:: uhttpd.AUTH_BASIC Basic authentication (user and password) .. data:: uhttpd.AUTH_CUSTOM Custom authentication (via a callback function) .. data:: uhttpd.ACL_RW File/Directory read-write access granted .. data:: uhttpd.ACL_RO File/Directory read-only access .. data:: uhttpd.ACL_WO File/Directory write-only access