| #ifndef HTTP_RESPONSE_H |
| #define HTTP_RESPONSE_H |
| |
| /* this library requires the request object and extends it. */ |
| #include "lib-server.h" |
| #include "http-request.h" |
| #include "http-objpool.h" |
| #include "http-status.h" |
| #include "http-mime-types.h" |
| |
| /* defined in the request header, and used here: |
| HTTP_HEAD_MAX_SIZE |
| */ |
| |
| /* |
| Small enough responses can be sent together with the header, allowing for better |
| performance through better socket buffer utilization and minimizing the system |
| calls to `write`. |
| |
| These cannot be more then 56,320 Bytes , as the buffer packets will split |
| anything over 64Kb, also 24Kb is a reasonable upper limit for the actual |
| optimization, so using a higher limit will not really improve performance (this |
| is machine dependant). |
| |
| Also, this memory will remain in the pool for every pooled response object. |
| */ |
| #define SMALL_RESPONSE_LIMIT 16384 |
| |
| /** |
| The struct HttpResponse type will contain all the data required for handling the |
| response. |
| |
| Example use (excluding error checks): |
| |
| void on_request(struct HttpRequest request) { |
| struct HttpResponse* response = HttpResponse.create(req); // (initialize) |
| HttpResponse.write_header2(response, "X-Data", "my data"); |
| HttpResponse.set_cookie(response, (struct HttpCookie){ |
| .name = "my_cookie", |
| .value = "data" |
| }); |
| HttpResponse.write_body(response, "Hello World!\r\n", 14); |
| HttpResponse.destroy(response); // release/pool resources |
| } |
| |
| int main() |
| { |
| char * public_folder = NULL |
| start_http_server(on_request, public_folder, .threads = 16); |
| } |
| |
| |
| To set a response's content length, use `response->content_length` or, if |
| sending the body using a single write, it's possible to leave out the |
| content-length header (see the `HttpResponse.write_body` for more details). |
| |
| The response object and it's API are NOT thread-safe (it is assumed that no two |
| threads handle the same response at the same time). |
| */ |
| struct HttpResponse { |
| /** |
| The body's response length. |
| |
| If this isn't set manually, the first call to |
| `HttpResponse.write_body` (and friends) will set the length to the length |
| being written (which might be less then the total data sent, if the sending is |
| fragmented). |
| */ |
| size_t content_length; |
| /** |
| The HTTP date for the response (in seconds since epoche). |
| |
| Defaults to now (approximately, not exactly, uses cached data). |
| |
| The date will be automatically formatted to match the HTTP protocol |
| specifications. It is better to avoid setting the "Date" header manualy. |
| */ |
| time_t date; |
| /** |
| The actual header buffer - do not edit directly. |
| |
| The extra 248 bytes are for the status line and variable headers, such as the |
| date, content-length and connection status, that are requireed by some clients |
| and aren't always meaningful for a case-by-case consideration. |
| */ |
| char header_buffer[HTTP_HEAD_MAX_SIZE + SMALL_RESPONSE_LIMIT + 248]; |
| /** |
| The response status |
| */ |
| int status; |
| /** |
| Metadata about the response's state - don't edit this data (except the opaque |
| data, if needed). |
| */ |
| struct { |
| /** |
| an HttpResponse class object identifier, used to validate that the response |
| object pointer is actually pointing to a response object (only validated |
| before storing the object in the pool or freeing the object's memory). |
| */ |
| void* classUUID; |
| /** |
| The server through which the response will be sent. |
| */ |
| server_pt server; |
| /** |
| The socket's fd, for sending the response. |
| */ |
| uint64_t fd_uuid; |
| /** |
| A pointer to the header's writing position. |
| */ |
| char* headers_pos; |
| /** |
| Set to true once the headers were sent. |
| */ |
| unsigned headers_sent : 1; |
| /** |
| Set to true when the "Date" header is written to the buffer. |
| */ |
| unsigned date_written : 1; |
| /** |
| Set to true when the "Connection" header is written to the buffer. |
| */ |
| unsigned connection_written : 1; |
| /** |
| Reserved for future use. |
| */ |
| unsigned rsrv : 3; |
| /** |
| An opaque user data flag. |
| */ |
| unsigned opaque : 1; |
| |
| } metadata; |
| }; |
| |
| /** |
| The struct HttpCookie is a helper for seting cookie data. |
| |
| This struct is used together with the `HttpResponse.set_cookie`. i.e.: |
| |
| HttpResponse.set_cookie(response, (struct HttpCookie){ |
| .name = "my_cookie", |
| .value = "data" |
| }); |
| |
| */ |
| struct HttpCookie { |
| /** The cookie's name (key). */ |
| char* name; |
| /** The cookie's value (leave blank to delete cookie). */ |
| char* value; |
| /** The cookie's domain (optional). */ |
| char* domain; |
| /** The cookie's path (optional). */ |
| char* path; |
| /** The cookie name's size in bytes or a terminating NULL will be assumed.*/ |
| size_t name_len; |
| /** The cookie value's size in bytes or a terminating NULL will be assumed.*/ |
| size_t value_len; |
| /** The cookie domain's size in bytes or a terminating NULL will be assumed.*/ |
| size_t domain_len; |
| /** The cookie path's size in bytes or a terminating NULL will be assumed.*/ |
| size_t path_len; |
| /** Max Age (how long should the cookie persist), in seconds (0 == session).*/ |
| int max_age; |
| /** Limit cookie to secure connections.*/ |
| unsigned secure : 1; |
| /** Limit cookie to HTTP (intended to prevent javascript access/hijacking).*/ |
| unsigned http_only : 1; |
| }; |
| |
| /** |
| The HttpResponse library |
| ======================== |
| |
| This library helps us to write HTTP valid responses, even when we do not know |
| the internals of the HTTP protocol. |
| |
| The response object allows us to easily update the response status (all |
| responses start with the default 200 "OK" status code), write headers and cookie |
| data to the header buffer and send the response's body. |
| |
| The response object also allows us to easily update the body size and send body |
| data or open files (which will be automatically closed once sending is done). |
| |
| Before using any response object (usually performed before the server starts), |
| it is important to inialize the response object pool: |
| |
| HttpResponse.create_pool() |
| |
| To destroy the pool (usually after the server is done), use: |
| |
| HttpResponse.destroy_pool() |
| |
| As example flow for the response could be: |
| |
| ; // get an initialized HttpRequest object |
| struct HttpRequest * response = HttpResponse.create(request); |
| ; // ... write headers and body, i.e. |
| HttpResponse.write_header_cstr(response, "X-Data", "my data"); |
| HttpResponse.write_body(response, "Hello World!\r\n", 14); |
| ; // release the object |
| HttpResponse.destroy(response); |
| |
| |
| -- |
| Thread-safety: |
| |
| The response object and it's API are NOT thread-safe (it is assumed that no two |
| threads handle the same response at the same time). |
| |
| Initializing and destroying the request object pool is NOT thread-safe. |
| |
| --- |
| Misc notes: |
| The response header's buffer size is limited and too many headers will fail the |
| response. |
| |
| The response object allows us to easily update the response status (all |
| responses start with the default 200 "OK" status code), write headers and write |
| cookie data to the header buffer. |
| |
| The response object also allows us to easily update the body size and send body |
| data or open files (which will be automatically closed once sending is done). |
| |
| The response does NOT support chuncked encoding. |
| |
| The following is the response API container, use: |
| |
| struct HttpRequest * response = HttpResponse.create(request); |
| |
| |
| --- |
| Performance: |
| |
| A note about using this library with the HTTP/1 protocol family (if this library |
| supports HTTP/2, in the future, the use of the response object will be required, |
| as it might not be possible to handle the response manually): |
| |
| Since this library safeguards against certain mistakes and manages an |
| internal header buffer, it comes at a performance cost (it adds a layer of data |
| copying to the headers). |
| |
| This cost is mitigated by the optional use of a response object pool, so that it |
| actually saves us from using `malloc` for the headers - for some cases this is |
| faster. |
| |
| In my performance tests, the greatest issue is this: spliting the headers from |
| the body means that the socket's buffer is under-utilized on the first call to |
| `send`, while sending the headers. While other operations incure minor costs, |
| this is the actual reason for degraded performance when using this library. |
| |
| The order of performance should be considered as follows: |
| |
| 1. Destructive: Overwriting the request's header buffer with both the response |
| headers and the response data (small responses). Sending the data through the |
| socket using the `Server.write` function. |
| |
| 2. Using malloc to allocate enough memory for both the response's headers AND |
| it's body. Sending the data through the socket using the `Server.write_move` |
| function. |
| |
| 3. Using the HttpResponse object to send the response. |
| |
| Network issues and response properties might influence the order of performant |
| solutions. |
| */ |
| struct HttpResponseClass { |
| /** |
| Destroys the response object pool. This function ISN'T thread-safe. |
| */ |
| void (*destroy_pool)(void); |
| /** |
| Creates the response object pool (unless it already exists). This function |
| ISN'T thread-safe. |
| */ |
| void (*init_pool)(void); |
| /** |
| Creates a new response object or recycles a response object from the response |
| pool. |
| |
| returns NULL on failuer, or a pointer to a valid response object. |
| */ |
| struct HttpResponse* (*create)(struct HttpRequest*); |
| /** |
| Destroys the response object or places it in the response pool for recycling. |
| */ |
| void (*destroy)(struct HttpResponse*); |
| /** |
| The pool limit property (defaults to 64) sets the limit of the pool storage, |
| making sure that excess memory used is cleared rather then recycled. |
| */ |
| int pool_limit; |
| /** |
| Clears the HttpResponse object, linking it with an HttpRequest object (which |
| will be used to set the server's pointer and socket fd). |
| */ |
| void (*reset)(struct HttpResponse*, struct HttpRequest*); |
| /** Gets a response status, as a string */ |
| char* (*status_str)(struct HttpResponse*); |
| /** |
| Writes a header to the response. This function writes only the requested |
| number of bytes from the header value and can be used even when the header |
| value doesn't contain a NULL terminating byte. |
| |
| If the header buffer is full or the headers were already sent (new headers |
| cannot be sent), the function will return -1. |
| |
| On success, the function returns 0. |
| */ |
| int (*write_header)(struct HttpResponse*, |
| const char* header, |
| const char* value, |
| size_t value_len); |
| /** |
| Writes a header to the response. |
| |
| This is equivelent to writing: |
| |
| HttpResponse.write_header(* response, header, value, strlen(value)); |
| |
| If the header buffer is full or the headers were already sent (new |
| headers |
| cannot be sent), the function will return -1. |
| |
| On success, the function returns 0. |
| */ |
| int (*write_header2)(struct HttpResponse*, |
| const char* header, |
| const char* value); |
| /** |
| Set / Delete a cookie using this helper function. |
| |
| To set a cookie, use (in this example, a session cookie): |
| |
| HttpResponse.set_cookie(response, (struct HttpCookie){ |
| .name = "my_cookie", |
| .value = "data" }); |
| |
| To delete a cookie, use: |
| |
| HttpResponse.set_cookie(response, (struct HttpCookie){ |
| .name = "my_cookie", |
| .value = NULL }); |
| |
| This function writes a cookie header to the response. Only the requested |
| number of bytes from the cookie value and name are written (if none are |
| provided, a terminating NULL byte is assumed). |
| |
| Both the name and the value of the cookie are checked for validity (legal |
| characters), but other properties aren't reviewed (domain/path) - please make |
| sure to use only valid data, as HTTP imposes restrictions on these things. |
| |
| If the header buffer is full or the headers were already sent (new headers |
| cannot be sent), the function will return -1. |
| |
| On success, the function returns 0. |
| */ |
| int (*set_cookie)(struct HttpResponse*, struct HttpCookie); |
| /** |
| Prints a string directly to the header's buffer, appending the header |
| seperator (the new line marker '\r\n' should NOT be printed to the headers |
| buffer). |
| |
| If the header buffer is full or the headers were already sent (new headers |
| cannot be sent), the function will return -1. |
| |
| On success, the function returns 0. |
| */ |
| int (*printf)(struct HttpResponse*, const char* format, ...); |
| /** |
| Sends the headers (if they weren't previously sent). |
| |
| If the connection was already closed, the function will return -1. On success, |
| the function returns 0. |
| */ |
| int (*send)(struct HttpResponse*); |
| /** |
| Sends the headers (if they weren't previously sent) and writes the data to the |
| underlying socket. |
| |
| The body will be copied to the server's outgoing buffer. |
| |
| If the connection was already closed, the function will return -1. On success, |
| the function returns 0. |
| */ |
| int (*write_body)(struct HttpResponse*, const char* body, size_t length); |
| /** |
| Sends the headers (if they weren't previously sent) and writes the data to the |
| underlying socket. |
| |
| The server's outgoing buffer will take ownership of the body and free it's |
| memory using `free` once the data was sent. |
| |
| If the connection was already closed, the function will return -1. On success, |
| the function returns 0. |
| */ |
| int (*write_body_move)(struct HttpResponse*, const char* body, size_t length); |
| /** |
| Sends the headers (if they weren't previously sent) and writes the data to the |
| underlying socket. |
| |
| The server's outgoing buffer will take ownership of the body and free it's |
| memory using `free` once the data was sent. |
| |
| If the connection was already closed, the function will return -1. On success, |
| the function returns 0. |
| */ |
| int (*sendfile)(struct HttpResponse*, FILE* pf, size_t length); |
| /** |
| Closes the connection. |
| */ |
| void (*close)(struct HttpResponse*); |
| } HttpResponse; |
| |
| /* end include guard */ |
| #endif /* HTTP_RESPONSE_H */ |