/* * Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files * (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef CYTOPLASM_HTTPSERVER_H #define CYTOPLASM_HTTPSERVER_H /*** * @Nm HttpServer * @Nd Extremely simple HTTP server. * @Dd December 13 2022 * @Xr Http HttpClient * * .Nm * builds on the * .Xr Http 3 * API, and provides a very simple, yet very functional API for * creating an HTTP server. It aims at being easy to use and minimal, * yet also efficient. It uses non-blocking I/O, is fully * multi-threaded, and is very configurable. It can be set up in just * two function calls and minimal supporting code. * .Pp * This API should be familar to those that have dealt with the HTTP * server libraries of other programming languages, particularly Java. * In fact, much of the terminology used in this API came from Java, * and you'll notice that the way responses are sent and received very * closely resembles Java. */ #include #include #include "Http.h" #include "HashMap.h" #include "Stream.h" /** * The functions on this API operate on an opaque structure. */ typedef struct HttpServer HttpServer; /** * Each request receives a context structure. It is opaque, so the * functions defined in this API should be used to fetch data from * it. These functions allow the handler to figure out the context of * the request, which includes the path requested, any parameters, * and the headers and method used to make the request. The context * also provides the means by which the handler responds to the * request, allowing it to set the status code, headers, and body. */ typedef struct HttpServerContext HttpServerContext; /** * The request handler function is executed when an HTTP request is * received. It takes a request context, and a pointer as specified * in the server configuration. */ typedef void (HttpHandler) (HttpServerContext *, void *); /** * The number of arguments to * .Fn HttpServerCreate * has grown so large that arguments are now stuffed into a * configuration structure, which is in turn passed to * .Fn HttpServerCreate . * This configuration is copied by value into the internal * structures of the server. It is copied with very minimal * validation, so ensure that all values are sensible. It may * make sense to use * .Fn memset * to zero out everything in here before assigning values. */ typedef struct HttpServerConfig { unsigned short port; unsigned int threads; unsigned int maxConnections; int flags; /* Http(3) flags */ char *tlsCert; /* File path */ char *tlsKey; /* File path */ HttpHandler *handler; void *handlerArgs; } HttpServerConfig; /** * Create a new HTTP server using the specified configuration. * This will set up all internal structures used by the server, * and bind the socket and start listening for connections. However, * it will not start accepting connections. */ extern HttpServer * HttpServerCreate(HttpServerConfig *); /** * Retrieve the configuration that was used to instantiate the given * server. Note that this configuration is not necessarily the exact * one that was provided; even though its values are the same, it * should be treated as an entirely separate configuration with no * connection to the original. */ extern HttpServerConfig * HttpServerConfigGet(HttpServer *); /** * Free the resources associated with the given HTTP server. Note that * the server can only be freed after it has been stopped. Calling this * function while the server is still running results in undefined * behavior. */ extern void HttpServerFree(HttpServer *); /** * Attempt to start the HTTP server, and return immediately with the * status. This API is fully multi-threaded and asynchronous, so the * caller can continue working while the HTTP server is running in a * separate thread and managing a pool of threads to handle responses. */ extern bool HttpServerStart(HttpServer *); /** * Typically, at some point after calling * .Fn HttpServerStart , * the program will have no more work to do, so it will want to wait * for the HTTP server to finish. This is accomplished via this * function, which joins the HTTP worker thread to the calling thread, * pausing the calling thread until the HTTP server has stopped. */ extern void HttpServerJoin(HttpServer *); /** * Stop the HTTP server. Only the execution of this function will * cause the proper shutdown of the HTTP server. If the main program * is joined to the HTTP thread, then either another thread or a * signal handler will have to stop the server using this function. * The typical use case is to install a signal handler that executes * this function on a global HTTP server. */ extern void HttpServerStop(HttpServer *); /** * Get the request headers for the request represented by the given * context. The data in the returned hash map should be treated as * read only and should not be freed; it is managed entirely by the * server. */ extern HashMap * HttpRequestHeaders(HttpServerContext *); /** * Get the request method used to make the request represented by * the given context. */ extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *); /** * Get the request path for the request represented by the given * context. The return value of this function should be treated as * read-only, and should not be freed; it is managed entirely by the * server. */ extern char * HttpRequestPath(HttpServerContext *); /** * Retrieve the parsed GET parameters for the request represented by * the given context. The returned hash map should be treated as * read-only, and should not be freed; it is managed entirely by the * server. */ extern HashMap * HttpRequestParams(HttpServerContext *); /** * Set a response header to return to the client. The old value for * the given header is returned, if any, otherwise NULL is returned. */ extern char * HttpResponseHeader(HttpServerContext *, char *, char *); /** * Set the response status to return to the client. */ extern void HttpResponseStatus(HttpServerContext *, HttpStatus); /** * Get the current response status that will be sent to the client * making the request represented by the given context. */ extern HttpStatus HttpResponseStatusGet(HttpServerContext *); /** * Send the response headers to the client that made the request * represented by the specified context. This function must be called * before the response body can be written, otherwise a malformed * response will be sent. */ extern void HttpSendHeaders(HttpServerContext *); /** * Get a stream that is both readable and writable. Reading from the * stream reads the request body that the client sent, if there is one. * Note that the rquest headers have already been read, so the stream * is correctly positioned at the beginning of the body of the request. * .Fn HttpSendHeaders * must be called before the stream is written, otherwise a malformed * HTTP response will be sent. An HTTP handler should properly set all * the headers it itends to send, send those headers, and then write * the response body to this stream. * .Pp * Note that the stream does not need to be closed by the HTTP * handler; in fact doing so results in undefined behavior. The stream * is managed entirely by the server itself, so it will close it when * necessary. This allows the underlying protocol to differ: for * instance, an HTTP/1.1 connection may stay for multiple requests and * responses. */ extern Stream * HttpServerStream(HttpServerContext *); #endif /* CYTOPLASM_HTTPSERVER_H */