SLWEB_HTTP(7)

NAME
slweb_http - slweb HTTP connection handling and utilities

SYNOPSIS

#include <slweb/http.h>

DESCRIPTION
As one of its key features, slweb provides a HTTP/1.1-compatible server implementation that can be embedded into applications as a library. While not a complete HTTP/1.1 server implementation, the following features are supported:

•   GET.

•   POST.

•   multipart/form-data-encoded data. An optional payload size limit can be defined (see section HTTP server configuration).

•   Cookies.

  Utility functions
The functions listed below are meant for library users:

•   http_response_add_header(3).

•   http_cookie_create(3).

•   http_encode_url(3).

•   http_decode_url(3).

  HTTP connection-related functions
The functions listed below are meant for internal use by slweb:

•   http_alloc(3).

•   http_free(3).

•   http_update(3). However, this component alone does not provide a working web server. For example, a list of endpoints is required to define its behaviour, and struct http objects must be stored somewhere as long as the connections are active. slweb_handler(7) is the component meant to provide the missing pieces that conform a working web server.

  HTTP server configuration
A HTTP server is contained into a struct http_ctx, and can be allocated by calling http_alloc(3). This function requires a valid pointer to a struct http_cfg object. This flexible configuration allows the library to run on top of any reliable transport layer, including TCP. struct http_cfg is defined as:

    struct http_cfg
    {
        int (*read)(void *buf, size_t n, void *user);
        int (*write)(const void *buf, size_t n, void *user);
        int (*payload)(const struct http_payload *p, struct http_response *r, void *user);
        int (*length)(unsigned long long len, const struct http_cookie *c, struct http_response *r, void *user);
        const char *tmpdir;
        void *user;
    };

All of the function pointers listed above define user as a parameter, an opaque pointer to user-defined data previously defined by member user (see definition below). Unless noted otherwise, all pointers must be valid. read is a function pointer to a read(2)-like function that must read up to n bytes from the client into a buffer pointed to by buf. The function pointed to by read returns the number of bytes that could be read from the client, which could be from zero up to n. On error, a negative integer is returned. write is a function pointer to a write(2)-like function that must write up to n bytes to the client from a buffer pointed to by buf. It returns the number of bytes that could be written to the client, which could be from zero to n. On error, a negative integer is returned. payload is a function pointer called by slweb when a new HTTP request has been received. p is a read-only pointer to a struct http_payload object, which describes the HTTP request (see section HTTP payload). r is a pointer to a struct http_response object that must be initialized by the function pointed to by payload, which includes the HTTP response parameters to be returned to the client. The function pointed to by read returns the number of bytes that could be read from the client, which could be from zero to n. This function returns zero on success. On error, a negative integer is returned. length is a function pointer called by slweb when an incoming HTTP request from a client requires to store one or more files on the server, encoded as multipart/form-data. len defines the length of the multipart/form-data (see section Content-Length design limitations for multipart/form-data). c is a read-only pointer to a struct http_cookie object, containing at most one (see section Limitations on the number of HTTP cookies) HTTP cookie. If no cookies are defined, its members shall contain null pointers. r is a pointer to a struct http_response object that must be initialized by the function pointed to by payload only when the function returns a positive integer. This function returns zero on success, a negative integer in case of a fatal error or a positive integer in case of a non-fatal error caused by a malformed request, or to indicate a lack of support for this feature. When a positive integer is returned, the connection against the client shall be closed. tmpdir is a null-terminated string defining the path to a directory where files uploaded by clients shall be stored temporarily. tmpdir can be a null pointer if this feature is not supported by the application. user is an opaque pointer to a user-defined object that shall be passed to other function pointers defined by struct http_cfg. user can be a null pointer.

  HTTP payload
When a client submits a request to the server, slweb prepares a high-level data structure, called struct http_payload, and passes it to the function pointer defined by struct http_cfg member payload. struct http_payload is defined as:

    struct http_payload
    {
        enum http_op op;
        const char *resource;
        struct http_cookie cookie;
        union
        {
            struct http_post post;
        } u;
        const struct http_arg *args;
        size_t n_args;
    };

op describes the HTTP/1.1 operation. See the definition for enum http_op for an exhaustive list of supported operations. resource describes which resource is being requested by the client. For example: /index.html. cookie contains at most one HTTP cookie, defined as a key-value pair. Its members shall be null pointers if no cookie is present. Also, see section Limitations on the number of HTTP cookies. u defines a tagged union with operation-specific data. For example, post refers to data sent by a client on a POST request (see section HTTP POST payload). Also, see section Future supported HTTP/1.1 operations. args defines a list of key-value pairs containing URL parameters. Its length is defined by n_args.

  HTTP POST payload
As opposed to payload-less HTTP/1.1 operations, such as GET, POST operations might or might not include payload data. Moreover, such payload can be encoded in two different ways, which slcl handles differently:

•   application/x-www-form-urlencoded: suggested for smaller payloads. slweb shall store the payload in memory, limiting its maximum size to 7999 octets.

•   multipart/form-data: suggested for larger and/or binary payloads. slweb shall store each non-file name-value pair in memory, limiting the value length to 8000 octets. On the other hand, slweb shall store each file into the temporary directory defined by struct http_cfg member tmpdir. This information is contained into a struct http_post object, defined as:

    struct http_post
    {
        bool expect_continue;
        const char *data;
        size_t nfiles, npairs;
        const struct http_post_pair
        {
            const char *name, *value;
        } *pairs;
        const struct http_post_file
        {
            const char *name, *tmpname, *filename;
        } *files;
    };

expect_continue shall be set to true if an Expect: 100-continue HTTP header is received, false otherwise (see section Handling of 100-continue requests in BUGS). When application/x-www-form-urlencoded-data is included, data shall contain a null-terminated string with the user payload. Data must be decoded by applications (see section Handling application/x-www-form-urlencoded data). Otherwise, data shall be a null pointer. In the case of multipart/form-data, files shall contain a list of files that were uploaded by the client, each one stored by the server to a temporary file, defined by tmpname. The final name for the uploaded file is defined by filename. The key name used for each requested file is defined by name. The length of this list is defined by nfiles. If no files are defined, files shall be a null pointer. In the case of multipart/form-data, pairs shall contain a list of name-value pairs that were uploaded by the client, defined by name and value, respectively. The length of this list is defined by npairs. If no name-value pairs are defined, pairs shall be a null pointer.

  HTTP responses
Some function pointers used by slweb require to initialize a struct http_response object that defines the response that must be sent to the client. This structure is defined as:

    struct http_response
    {
        enum http_status status;
        struct http_header
        {
            char *header, *value;
        } *headers;
        union
        {
            const void *ro;
            void *rw;
        } buf;
        FILE *f;
        unsigned long long n;
        size_t n_headers;
        void (*free)(void *);
    };

status is the response code to be returned to the client. A list of possible values is defined by enum http_status. headers is a pointer to an array of struct http_header whose length is defined by n_headers, containing the HTTP headers to be included into the response. Note that headers is not meant to be modified directly by library users. Instead, the http_response_add_header(3) utility function shall update the struct http_response object accordingly. buf is a union containing two possible values, with minor semantic differences: ro is a read-only opaque pointer to a buffer in memory, whose length is defined by n (see definition below). slweb shall select ro as the output payload if both f and free are null pointers, and n is non-zero. rw is an opaque pointer to a buffer in memory, whose length is defined by n (see definition below). slweb shall select rw as the output payload if both f is a null pointer and free is a valid pointer to a function that frees the memory used by rw, and n is non-zero. f is a FILE pointer opened for reading that defines the payload to be sent to the client, whose length is defined by n. slweb shall select f as the output payload if ro, rw and free are null pointers, and n is non-zero. n is the length of the output payload, which can be either a buffer in memory (see definitions for ro and rw) or a file (see definition for f). If n equals zero, no payload shall be sent. n_headers defines the number of HTTP headers contained in the response. This field is not meant to be manipulated directly. Instead, the http_response_add_header(3) utility function shall update the struct http_response object accordingly. free is a pointer to a function that frees the memory used by rw only if rw is a valid pointer. Otherwise, free must be a null pointer.

  Transport Layer Security (TLS)
By design, slweb does not implement TLS (Transport Layer Security). It is assumed this should be provided by a reverse proxy instead, a kind of project that is usually maintained by a larger community than slweb and audited for security vulnerabilities.

NOTES

  Comparing against other HTTP server implementations
While it is well understood that other solutions provide fully-fledged server implementations as standalone executables, slweb strives to be as small and easy to use as possible, intentionally limiting its scope while covering a good range of use cases.

  Content-Length design limitations for multipart/form-data
HTTP/1.1 defines the Content-Length for a multipart/form-data POST request as the sum of:

•   The length of all files.

•   The length of all boundaries.

•   The length of all headers included on each part.

•   All separator tokens, such as LFCR or --. This means it is not possible for slweb to determine the number of files or their lengths in a HTTP request unless the whole request is read, which not might be possible for large requests. Therefore, the Content-Length is the only rough estimation slweb can rely on, and therefore is the value passed to the length function pointer in struct http_cfg.

BUGS

  Handling of 100-continue requests
The handling of 100-continue requests is not done correctly: slweb calls the function pointed to by struct http_cfg member payload as soon as it encounters the Expected: header. However, a response should only be sent to the client once all headers are processed.

FUTURE DIRECTIONS

  Limitations on the number of HTTP cookies
So far, slweb shall only append at most one HTTP cookie to a struct http_payload object. This is due to arbitrary design limitations on the library. Future versions of this library shall replace the struct http_cookie object inside struct http_payload with a pointer to an array of struct http_cookie, plus a size_t object containing the number of HTTP cookies in the request.

  Handling application/x-www-form-urlencoded data
Due to historical reasons, slweb treated application/x-www-form-urlencoded-data as a binary blob. While this was changed to a null-terminated string in order to allow applications to avoid unnecessary memory allocations, slweb still does not decode the data, instead forcing applications to do so. Future versions of this library shall replace struct http_post member data with an array of structures containing key-value pairs, so that applications no longer need to decode payload data by themselves.

  Future supported HTTP/1.1 operations
So far, struct http_payload defines u as a union that only holds one possible data type. While this might look counterintuitive, this is because POST is the only HTTP/1.1 operation slweb supports that requires to store a payload. However, future versions of this library might extend its support for other HTTP/1.1 operations that could require to store a payload, while keeping the memory footprint for struct http_payload small.

SEE ALSO
handler_alloc(3), http_alloc(3), http_free(3), http_update(3), http_response_add_header(3), http_cookie_create(3), http_encode_url(3), http_decode_url(3).

COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.