/* * Copyright (c) 2023, Emna Rekik * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #ifndef ZEPHYR_INCLUDE_NET_HTTP_SERVER_H_ #define ZEPHYR_INCLUDE_NET_HTTP_SERVER_H_ /** * @file server.h * * @brief HTTP server API * * @defgroup http_server HTTP server API * @since 3.7 * @version 0.1.0 * @ingroup networking * @{ */ #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** @cond INTERNAL_HIDDEN */ #if defined(CONFIG_HTTP_SERVER) #define HTTP_SERVER_CLIENT_BUFFER_SIZE CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE #define HTTP_SERVER_MAX_STREAMS CONFIG_HTTP_SERVER_MAX_STREAMS #define HTTP_SERVER_MAX_CONTENT_TYPE_LEN CONFIG_HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH #define HTTP_SERVER_MAX_URL_LENGTH CONFIG_HTTP_SERVER_MAX_URL_LENGTH #define HTTP_SERVER_MAX_HEADER_LEN CONFIG_HTTP_SERVER_MAX_HEADER_LEN #else #define HTTP_SERVER_CLIENT_BUFFER_SIZE 0 #define HTTP_SERVER_MAX_STREAMS 0 #define HTTP_SERVER_MAX_CONTENT_TYPE_LEN 0 #define HTTP_SERVER_MAX_URL_LENGTH 0 #define HTTP_SERVER_MAX_HEADER_LEN 0 #endif #if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) #define HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE #define HTTP_SERVER_CAPTURE_HEADER_COUNT CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT #else #define HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE 0 #define HTTP_SERVER_CAPTURE_HEADER_COUNT 0 #endif #define HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" /** @endcond */ /** * @brief HTTP server resource type. */ enum http_resource_type { /** Static resource, cannot be modified on runtime. */ HTTP_RESOURCE_TYPE_STATIC, /** serves static gzipped files from a filesystem */ HTTP_RESOURCE_TYPE_STATIC_FS, /** Dynamic resource, server interacts with the application via registered * @ref http_resource_dynamic_cb_t. */ HTTP_RESOURCE_TYPE_DYNAMIC, /** Websocket resource, application takes control over Websocket connection * after and upgrade. */ HTTP_RESOURCE_TYPE_WEBSOCKET, }; /** * @brief Representation of a server resource, common for all resource types. */ struct http_resource_detail { /** Bitmask of supported HTTP methods (@ref http_method). */ uint32_t bitmask_of_supported_http_methods; /** Resource type. */ enum http_resource_type type; /** Length of the URL path. */ int path_len; /** Content encoding of the resource. */ const char *content_encoding; /** Content type of the resource. */ const char *content_type; }; /** @cond INTERNAL_HIDDEN */ BUILD_ASSERT(NUM_BITS( sizeof(((struct http_resource_detail *)0)->bitmask_of_supported_http_methods)) >= (HTTP_METHOD_END_VALUE - 1)); /** @endcond */ /** * @brief Representation of a static server resource. */ struct http_resource_detail_static { /** Common resource details. */ struct http_resource_detail common; /** Content of the static resource. */ const void *static_data; /** Size of the static resource. */ size_t static_data_len; }; /** @cond INTERNAL_HIDDEN */ /* Make sure that the common is the first in the struct. */ BUILD_ASSERT(offsetof(struct http_resource_detail_static, common) == 0); /** @endcond */ /** * @brief Representation of a static filesystem server resource. */ struct http_resource_detail_static_fs { /** Common resource details. */ struct http_resource_detail common; /** Path in the local filesystem */ const char *fs_path; }; /** @cond INTERNAL_HIDDEN */ /* Make sure that the common is the first in the struct. */ BUILD_ASSERT(offsetof(struct http_resource_detail_static_fs, common) == 0); /** @endcond */ struct http_content_type { const char *extension; size_t extension_len; const char *content_type; }; #define HTTP_SERVER_CONTENT_TYPE(_extension, _content_type) \ const STRUCT_SECTION_ITERABLE(http_content_type, _extension) = { \ .extension = STRINGIFY(_extension), \ .extension_len = sizeof(STRINGIFY(_extension)) - 1, \ .content_type = _content_type, \ }; #define HTTP_SERVER_CONTENT_TYPE_FOREACH(_it) STRUCT_SECTION_FOREACH(http_content_type, _it) struct http_client_ctx; /** Indicates the status of the currently processed piece of data. */ enum http_data_status { /** Transaction aborted, data incomplete. */ HTTP_SERVER_DATA_ABORTED = -1, /** Transaction incomplete, more data expected. */ HTTP_SERVER_DATA_MORE = 0, /** Final data fragment in current transaction. */ HTTP_SERVER_DATA_FINAL = 1, }; /** @brief Status of captured request headers */ enum http_header_status { HTTP_HEADER_STATUS_OK, /**< All available headers were successfully captured. */ HTTP_HEADER_STATUS_DROPPED, /**< One or more headers were dropped due to lack of space. */ HTTP_HEADER_STATUS_NONE, /**< No header status is available. */ }; /** @brief HTTP header representation */ struct http_header { const char *name; /**< Pointer to header name NULL-terminated string. */ const char *value; /**< Pointer to header value NULL-terminated string. */ }; /** @brief HTTP request context */ struct http_request_ctx { uint8_t *data; /**< HTTP request data */ size_t data_len; /**< Length of HTTP request data */ struct http_header *headers; /**< Array of HTTP request headers */ size_t header_count; /**< Array length of HTTP request headers */ enum http_header_status headers_status; /**< Status of HTTP request headers */ }; /** @brief HTTP response context */ struct http_response_ctx { enum http_status status; /**< HTTP status code to include in response */ const struct http_header *headers; /**< Array of HTTP headers */ size_t header_count; /**< Length of headers array */ const uint8_t *body; /**< Pointer to body data */ size_t body_len; /**< Length of body data */ bool final_chunk; /**< Flag set to true when the application has no more data to send */ }; /** * @typedef http_resource_dynamic_cb_t * @brief Callback used when data is received. Data to be sent to client * can be specified. * * @param client HTTP context information for this client connection. * @param status HTTP data status, indicate whether more data is expected or not. * @param request_ctx Request context structure containing HTTP request data that was received. * @param response_ctx Response context structure for application to populate with response data. * @param user_data User specified data. * * @return 0 success, server can send any response data provided in the response_ctx. * <0 error, close the connection. */ typedef int (*http_resource_dynamic_cb_t)(struct http_client_ctx *client, enum http_data_status status, const struct http_request_ctx *request_ctx, struct http_response_ctx *response_ctx, void *user_data); /** * @brief Representation of a dynamic server resource. */ struct http_resource_detail_dynamic { /** Common resource details. */ struct http_resource_detail common; /** Resource callback used by the server to interact with the * application. */ http_resource_dynamic_cb_t cb; /** A pointer to the client currently processing resource, used to * prevent concurrent access to the resource from multiple clients. */ struct http_client_ctx *holder; /** A pointer to the user data registered by the application. */ void *user_data; }; /** @cond INTERNAL_HIDDEN */ BUILD_ASSERT(offsetof(struct http_resource_detail_dynamic, common) == 0); /** @endcond */ /** * @typedef http_resource_websocket_cb_t * @brief Callback used when a Websocket connection is setup. The application * will need to handle all functionality related to the connection like * reading and writing websocket data, and closing the connection. * * @param ws_socket A socket for the Websocket data. * @param user_data User specified data. * * @return 0 Accepting the connection, HTTP server library will no longer * handle data to/from the socket and it is application responsibility * to send and receive data to/from the supplied socket. * <0 error, close the connection. */ typedef int (*http_resource_websocket_cb_t)(int ws_socket, void *user_data); /** @brief Representation of a websocket server resource */ struct http_resource_detail_websocket { /** Common resource details. */ struct http_resource_detail common; /** Websocket socket value */ int ws_sock; /** Resource callback used by the server to interact with the * application. */ http_resource_websocket_cb_t cb; /** Data buffer used to exchanged data between server and the, * application. */ uint8_t *data_buffer; /** Length of the data in the data buffer. */ size_t data_buffer_len; /** A pointer to the user data registered by the application. */ void *user_data; }; /** @cond INTERNAL_HIDDEN */ BUILD_ASSERT(offsetof(struct http_resource_detail_websocket, common) == 0); /** @endcond */ /** @cond INTERNAL_HIDDEN */ enum http2_stream_state { HTTP2_STREAM_IDLE, HTTP2_STREAM_RESERVED_LOCAL, HTTP2_STREAM_RESERVED_REMOTE, HTTP2_STREAM_OPEN, HTTP2_STREAM_HALF_CLOSED_LOCAL, HTTP2_STREAM_HALF_CLOSED_REMOTE, HTTP2_STREAM_CLOSED }; enum http_server_state { HTTP_SERVER_FRAME_HEADER_STATE, HTTP_SERVER_PREFACE_STATE, HTTP_SERVER_REQUEST_STATE, HTTP_SERVER_FRAME_DATA_STATE, HTTP_SERVER_FRAME_HEADERS_STATE, HTTP_SERVER_FRAME_SETTINGS_STATE, HTTP_SERVER_FRAME_PRIORITY_STATE, HTTP_SERVER_FRAME_WINDOW_UPDATE_STATE, HTTP_SERVER_FRAME_CONTINUATION_STATE, HTTP_SERVER_FRAME_PING_STATE, HTTP_SERVER_FRAME_RST_STREAM_STATE, HTTP_SERVER_FRAME_GOAWAY_STATE, HTTP_SERVER_FRAME_PADDING_STATE, HTTP_SERVER_DONE_STATE, }; enum http1_parser_state { HTTP1_INIT_HEADER_STATE, HTTP1_WAITING_HEADER_STATE, HTTP1_RECEIVING_HEADER_STATE, HTTP1_RECEIVED_HEADER_STATE, HTTP1_RECEIVING_DATA_STATE, HTTP1_MESSAGE_COMPLETE_STATE, }; #define HTTP_SERVER_INITIAL_WINDOW_SIZE 65536 #define HTTP_SERVER_WS_MAX_SEC_KEY_LEN 32 /** @endcond */ /** @brief HTTP/2 stream representation. */ struct http2_stream_ctx { int stream_id; /**< Stream identifier. */ enum http2_stream_state stream_state; /**< Stream state. */ int window_size; /**< Stream-level window size. */ /** Currently processed resource detail. */ struct http_resource_detail *current_detail; /** Flag indicating that headers were sent in the reply. */ bool headers_sent : 1; /** Flag indicating that END_STREAM flag was sent. */ bool end_stream_sent : 1; }; /** @brief HTTP/2 frame representation. */ struct http2_frame { uint32_t length; /**< Frame payload length. */ uint32_t stream_identifier; /**< Stream ID the frame belongs to. */ uint8_t type; /**< Frame type. */ uint8_t flags; /**< Frame flags. */ uint8_t padding_len; /**< Frame padding length. */ }; /** @cond INTERNAL_HIDDEN */ /** @brief Context for capturing HTTP headers */ struct http_header_capture_ctx { /** Buffer for HTTP headers captured for application use */ unsigned char buffer[HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE]; /** Descriptor of each captured HTTP header */ struct http_header headers[HTTP_SERVER_CAPTURE_HEADER_COUNT]; /** Status of captured headers */ enum http_header_status status; /** Number of headers captured */ size_t count; /** Current position in buffer */ size_t cursor; /** The HTTP2 stream associated with the current headers */ struct http2_stream_ctx *current_stream; /** The next HTTP header value should be stored */ bool store_next_value; }; /** @endcond */ /** @brief HTTP header name representation */ struct http_header_name { const char *name; /**< Pointer to header name NULL-terminated string. */ }; /** * @brief Representation of an HTTP client connected to the server. */ struct http_client_ctx { /** Socket descriptor associated with the server. */ int fd; /** Client data buffer. */ unsigned char buffer[HTTP_SERVER_CLIENT_BUFFER_SIZE]; /** Cursor indicating currently processed byte. */ unsigned char *cursor; /** Data left to process in the buffer. */ size_t data_len; /** Connection-level window size. */ int window_size; /** Server state for the associated client. */ enum http_server_state server_state; /** Currently processed HTTP/2 frame. */ struct http2_frame current_frame; /** Currently processed resource detail. */ struct http_resource_detail *current_detail; /** Currently processed stream. */ struct http2_stream_ctx *current_stream; /** HTTP/2 header parser context. */ struct http_hpack_header_buf header_field; /** HTTP/2 streams context. */ struct http2_stream_ctx streams[HTTP_SERVER_MAX_STREAMS]; /** HTTP/1 parser configuration. */ struct http_parser_settings parser_settings; /** HTTP/1 parser context. */ struct http_parser parser; /** Header capture context */ struct http_header_capture_ctx header_capture_ctx; /** Request URL. */ unsigned char url_buffer[HTTP_SERVER_MAX_URL_LENGTH]; /** Request content type. */ unsigned char content_type[HTTP_SERVER_MAX_CONTENT_TYPE_LEN]; /** Temp buffer for currently processed header (HTTP/1 only). */ unsigned char header_buffer[HTTP_SERVER_MAX_HEADER_LEN]; /** Request content length. */ size_t content_len; /** Request method. */ enum http_method method; /** HTTP/1 parser state. */ enum http1_parser_state parser_state; /** Length of the payload length in the currently processed request * fragment (HTTP/1 only). */ int http1_frag_data_len; /** Client inactivity timer. The client connection is closed by the * server when it expires. */ struct k_work_delayable inactivity_timer; /** @cond INTERNAL_HIDDEN */ /** Websocket security key. */ IF_ENABLED(CONFIG_WEBSOCKET, (uint8_t ws_sec_key[HTTP_SERVER_WS_MAX_SEC_KEY_LEN])); /** @endcond */ /** Flag indicating that HTTP2 preface was sent. */ bool preface_sent : 1; /** Flag indicating that HTTP1 headers were sent. */ bool http1_headers_sent : 1; /** Flag indicating that upgrade header was present in the request. */ bool has_upgrade_header : 1; /** Flag indicating HTTP/2 upgrade takes place. */ bool http2_upgrade : 1; /** Flag indicating Websocket upgrade takes place. */ bool websocket_upgrade : 1; /** Flag indicating Websocket key is being processed. */ bool websocket_sec_key_next : 1; /** The next frame on the stream is expectd to be a continuation frame. */ bool expect_continuation : 1; }; /** * @brief Register an HTTP request header to be captured by the server * * @param _id variable name for the header capture instance * @param _header header to be captured, as literal string */ #define HTTP_SERVER_REGISTER_HEADER_CAPTURE(_id, _header) \ BUILD_ASSERT(sizeof(_header) <= CONFIG_HTTP_SERVER_MAX_HEADER_LEN, \ "Header is too long to be captured, try increasing " \ "CONFIG_HTTP_SERVER_MAX_HEADER_LEN"); \ static const char *const _id##_str = _header; \ static const STRUCT_SECTION_ITERABLE(http_header_name, _id) = { \ .name = _id##_str, \ } /** @brief Start the HTTP2 server. * * The server runs in a background thread. Once started, the server will create * a server socket for all HTTP services registered in the system and accept * connections from clients (see @ref HTTP_SERVICE_DEFINE). */ int http_server_start(void); /** @brief Stop the HTTP2 server. * * All server sockets are closed and the server thread is suspended. */ int http_server_stop(void); #ifdef __cplusplus } #endif /** * @} */ #endif