Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions Modules/http-module/include/http/server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <http/types.h>
#include <tactility/error.h>

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#include "types.h"

#ifdef __cplusplus
extern "C" {
#endif

/** Opaque per-request handle; a handler only ever holds a pointer to one of these. */
struct HttpServerRequest;

/** @return ERROR_NONE if the request was handled; any other value is only logged, the response
* (or its absence) is entirely up to the handler having already sent one via the
* http_server_request_send*() functions below. */
typedef error_t (*HttpServerHandlerFn)(struct HttpServerRequest* request, void* user_ctx);

/**
* One route: @a uri is matched against the request path (not the query string) together with
* @a method. A trailing wildcard character matches by prefix (e.g. "/fs/" + wildcard matches
* "/fs/list" and "/fs/x/y"); anything else must match exactly, same wildcard convention as
* ESP-IDF's httpd_uri_match_wildcard(). @a uri is caller-owned and must outlive the server, same
* contract as ESP-IDF's httpd_uri_t: a string literal is the usual case.
*/
struct HttpServerRequestHandler {
const char* uri;
enum HttpMethod method;
HttpServerHandlerFn callback;
void* user_ctx;
};

/** @a handlers is copied into the server at http_server_alloc() time; each handler's own `uri`
* pointer is not, so it must still outlive the server. */
struct HttpServerConfig {
uint16_t port;
/** Bind address, e.g. "0.0.0.0". Caller-owned; only read during http_server_alloc(). */
const char* address;
/** Stack size in bytes for the server's own task, where the platform backend needs one. */
uint32_t stack_size;
const struct HttpServerRequestHandler* handlers;
size_t handler_count;
};

struct HttpServer;

/**
* Allocates a server for @a config; does not start listening yet, see http_server_start().
* @return NULL on allocation failure
*/
struct HttpServer* http_server_alloc(const struct HttpServerConfig* config);

/** Stops the server if still running (see http_server_stop()) and frees it. */
void http_server_free(struct HttpServer* server);

/**
* Starts listening and serving requests.
* A request whose method+uri matches no registered handler gets a 404 response automatically.
* @retval ERROR_NONE on success, including if the server was already started
* @retval ERROR_RESOURCE the listening socket could not be created/bound
*/
error_t http_server_start(struct HttpServer* server);

/** Stops listening and blocks until any in-flight request has finished. Safe to call when not started. */
void http_server_stop(struct HttpServer* server);

bool http_server_is_started(struct HttpServer* server);

/** @return the bound port, e.g. to read back the OS-assigned port after starting with port 0. 0 if not started. */
uint16_t http_server_get_port(struct HttpServer* server);

// region Request

enum HttpMethod http_server_request_get_method(struct HttpServerRequest* request);

/**
* Copies the request's path (not including the query string, e.g. "/fs/list") into @a buffer.
* Useful from a handler registered against a wildcard route to see which concrete path matched.
* @return the path's actual length, same truncation convention as http_server_request_get_query().
*/
size_t http_server_request_get_uri(struct HttpServerRequest* request, char* buffer, size_t buffer_size);

/**
* Copies the request's raw query string (the part after '?', still URL-encoded, empty if none) into @a buffer.
* @return the query string's actual length, regardless of @a buffer_size. Same truncation
* convention as snprintf(): a return value >= @a buffer_size means the copy was truncated.
*/
size_t http_server_request_get_query(struct HttpServerRequest* request, char* buffer, size_t buffer_size);

/**
* Copies the named header's value into @a buffer, case-insensitively.
* @return the header value's actual length, same truncation convention as http_server_request_get_query(); 0 (with @a buffer left untouched) if the header is absent.
*/
size_t http_server_request_get_header(struct HttpServerRequest* request, const char* name, char* buffer, size_t buffer_size);

/** The request body's declared length (the "Content-Length" header), or 0 if absent. */
uint64_t http_server_request_get_content_length(struct HttpServerRequest* request);

/**
* Reads up to @a buffer_size currently-available body bytes. Blocking: waits for at least one byte, up to an internal per-call timeout.
* @return bytes read; 0 at end of body; negative on error or timeout
*/
int http_server_request_receive(struct HttpServerRequest* request, void* buffer, size_t buffer_size);

/** Must be called before the first http_server_request_send*() call on this request, if at all.
* Defaults to 200. Has no effect once a response has started sending. */
void http_server_request_set_status(struct HttpServerRequest* request, status_code_t status_code);

/** Same timing as http_server_request_set_status(); defaults to "text/plain". */
void http_server_request_set_content_type(struct HttpServerRequest* request, const char* content_type);

/** Same timing as http_server_request_set_status(): adds one arbitrary response header.
* e.g. "Location", "Content-Disposition".
* Both @a name and @a value are copied.
*/
void http_server_request_set_header(struct HttpServerRequest* request, const char* name, const char* value);

/**
* Sends the full response: status line, headers, then @a data as the entire body in one shot.
* Only the first call to any http_server_request_send*()/send_chunk_start() for a given request has any effect.
* @param[in] data may be NULL if @a length is 0
*/
error_t http_server_request_send(struct HttpServerRequest* request, const void* data, size_t length);

/** Same as http_server_request_send() with @a text's length and content type "text/plain". */
error_t http_server_request_send_string(struct HttpServerRequest* request, const char* text);

/** Sets @a status_code, then sends @a message as a plain-text body. */
error_t http_server_request_send_error(struct HttpServerRequest* request, int status_code, const char* message);

/**
* Starts a chunked response: sends the status line and headers (no Content-Length; chunked
* transfer instead) without a body yet. Follow with zero or more http_server_request_send_chunk() calls,
* then exactly one http_server_request_send_chunk_end(). Useful for streaming a file whose size you don't
* want to (or can't cheaply) compute up front. Only the first call to any
* http_server_request_send*()/send_chunk_start() for a given request has any effect.
*/
error_t http_server_request_send_chunk_start(struct HttpServerRequest* request);

/** Sends one chunk of a response started with http_server_request_send_chunk_start(). */
error_t http_server_request_send_chunk(struct HttpServerRequest* request, const void* data, size_t length);

/** Terminates a chunked response started with http_server_request_send_chunk_start(). */
error_t http_server_request_send_chunk_end(struct HttpServerRequest* request);

// endregion

#ifdef __cplusplus
}
#endif
53 changes: 53 additions & 0 deletions Modules/http-module/include/http/types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <tactility/error.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// esp_http_client.h defines the same unscoped HTTP_METHOD_GET/POST/PUT/DELETE names.
// module.cpp avoids the clash by forward-declaring this enum instead of including this header.
// The fixed underlying type (C++ only) is what makes that forward declaration legal.
#ifdef __cplusplus
enum HttpMethod : int {
#else
enum HttpMethod {
#endif
HTTP_METHOD_CONNECT,
HTTP_METHOD_DELETE,
HTTP_METHOD_GET,
HTTP_METHOD_HEAD,
HTTP_METHOD_OPTIONS,
HTTP_METHOD_POST,
HTTP_METHOD_PATCH,
HTTP_METHOD_PUT,
HTTP_METHOD_TRACE,
};

/** An HTTP response status code, e.g. 200 or 404. */
typedef uint16_t status_code_t;

/** @return @a method's wire form, e.g. "GET" for HTTP_METHOD_GET. */
const char* http_method_to_string(enum HttpMethod method);

/**
* Parses @a text (e.g. the method token off a request line) into @a out_method.
* @retval ERROR_NONE on success
* @retval ERROR_NOT_FOUND @a text does not match any HttpMethod
*/
error_t http_method_from_string(const char* text, enum HttpMethod* out_method);

/**
* @warning Not all status codes are implemented, so check the return value
* @param[out] text @a code's standard reason phrase, e.g. "OK" for 200; only set on success
* @retval ERROR_NONE on success
* @retval ERROR_NOT_FOUND @a code has no known reason phrase
*/
error_t status_code_to_string(status_code_t code, const char** text);

#ifdef __cplusplus
}
#endif
67 changes: 65 additions & 2 deletions Modules/http-module/source/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
#include <http/download.h>
#include <http/module.h>

#include <tactility/error.h>

#include <stddef.h>
#include <stdint.h>

#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#include <esp_http_client.h>
Expand All @@ -14,16 +19,74 @@
#endif
#endif


#include <sys/select.h>
extern "C" {

enum HttpMethod : int;
typedef uint16_t status_code_t;
struct HttpServerRequest;
struct HttpServer;
struct HttpServerConfig;

// Deliberately not #include <http/server.h> or <http/types.h>: their HttpMethod enum shares
// enumerator names with esp_http_client.h's own, so this file forward-declares exactly the
// functions it needs instead of including either header.
struct HttpServer* http_server_alloc(const struct HttpServerConfig* config);
void http_server_free(struct HttpServer* server);
error_t http_server_start(struct HttpServer* server);
void http_server_stop(struct HttpServer* server);
bool http_server_is_started(struct HttpServer* server);
uint16_t http_server_get_port(struct HttpServer* server);
enum HttpMethod http_server_request_get_method(struct HttpServerRequest* request);
size_t http_server_request_get_uri(struct HttpServerRequest* request, char* buffer, size_t buffer_size);
size_t http_server_request_get_query(struct HttpServerRequest* request, char* buffer, size_t buffer_size);
size_t http_server_request_get_header(struct HttpServerRequest* request, const char* name, char* buffer, size_t buffer_size);
uint64_t http_server_request_get_content_length(struct HttpServerRequest* request);
int http_server_request_receive(struct HttpServerRequest* request, void* buffer, size_t buffer_size);
void http_server_request_set_status(struct HttpServerRequest* request, status_code_t status_code);
void http_server_request_set_content_type(struct HttpServerRequest* request, const char* content_type);
void http_server_request_set_header(struct HttpServerRequest* request, const char* name, const char* value);
error_t http_server_request_send(struct HttpServerRequest* request, const void* data, size_t length);
error_t http_server_request_send_string(struct HttpServerRequest* request, const char* text);
error_t http_server_request_send_error(struct HttpServerRequest* request, int status_code, const char* message);
error_t http_server_request_send_chunk_start(struct HttpServerRequest* request);
error_t http_server_request_send_chunk(struct HttpServerRequest* request, const void* data, size_t length);
error_t http_server_request_send_chunk_end(struct HttpServerRequest* request);
const char* http_method_to_string(enum HttpMethod method);
error_t http_method_from_string(const char* text, enum HttpMethod* out_method);
error_t status_code_to_string(status_code_t code, const char** text);

static const ModuleSymbol SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(http_download_subscribe),
DEFINE_MODULE_SYMBOL(http_download_unsubscribe),
DEFINE_MODULE_SYMBOL(http_download_poll),
DEFINE_MODULE_SYMBOL(http_download_start),
DEFINE_MODULE_SYMBOL(http_download_cancel),
DEFINE_MODULE_SYMBOL(http_server_alloc),
DEFINE_MODULE_SYMBOL(http_server_free),
DEFINE_MODULE_SYMBOL(http_server_start),
DEFINE_MODULE_SYMBOL(http_server_stop),
DEFINE_MODULE_SYMBOL(http_server_is_started),
DEFINE_MODULE_SYMBOL(http_server_get_port),
DEFINE_MODULE_SYMBOL(http_server_request_get_method),
DEFINE_MODULE_SYMBOL(http_server_request_get_query),
DEFINE_MODULE_SYMBOL(http_server_request_get_header),
DEFINE_MODULE_SYMBOL(http_server_request_get_content_length),
DEFINE_MODULE_SYMBOL(http_server_request_receive),
DEFINE_MODULE_SYMBOL(http_server_request_set_status),
DEFINE_MODULE_SYMBOL(http_server_request_set_content_type),
DEFINE_MODULE_SYMBOL(http_server_request_send),
DEFINE_MODULE_SYMBOL(http_server_request_send_string),
DEFINE_MODULE_SYMBOL(http_server_request_send_error),
DEFINE_MODULE_SYMBOL(http_server_request_get_uri),
DEFINE_MODULE_SYMBOL(http_server_request_set_header),
DEFINE_MODULE_SYMBOL(http_server_request_send_chunk_start),
DEFINE_MODULE_SYMBOL(http_server_request_send_chunk),
DEFINE_MODULE_SYMBOL(http_server_request_send_chunk_end),
// types
DEFINE_MODULE_SYMBOL(http_method_to_string),
DEFINE_MODULE_SYMBOL(http_method_from_string),
DEFINE_MODULE_SYMBOL(status_code_to_string),
// posix
DEFINE_MODULE_SYMBOL(select),
#ifdef ESP_PLATFORM
Expand Down Expand Up @@ -94,7 +157,7 @@ static const ModuleSymbol SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(esp_http_client_get_url),
DEFINE_MODULE_SYMBOL(esp_http_client_get_chunk_length),
#endif
MODULE_SYMBOL_TERMINATOR
MODULE_SYMBOL_TERMINATOR,
};

Module http_module = {
Expand Down
Loading
Loading