blob: 474ca3d5640756921b75077252278d4d29ac114b [file] [log] [blame] [raw]
/*
Copyright: Boaz segev, 2016-2017
License: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#ifndef HTTP_RESPONSE
/**
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).
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).
Also, the response object will link itself to a libsock buffer packet, so it
should be created and dispatched during the same event - `sock_packet_s` objects
shouldn't be held across events or for a period of time... In other words:
**Create the response object only when you are ready to send a response**.
---
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.
*/
#define HTTP_RESPONSE
#include "http.h"
#include "http_request.h"
typedef struct {
/**
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).
Set the value to -1 to force the HttpResponse not to write nor automate the
`Content-Length` header.
*/
ssize_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 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 last_modified;
/**
The response status
*/
uint16_t status;
/**
Metadata about the response's state - don't edit this data (except the opaque
data, if needed).
*/
struct {
/**
The request object to which this response is "responding".
*/
http_request_s *request;
/**
The libsock fd UUID.
*/
intptr_t fd;
/**
A `libsock` buffer packet used for header data (to avoid double copy).
*/
sock_packet_s *packet;
/**
A pointer to the header's writing position.
*/
char *headers_pos;
/**
Internally used by the logging API.
*/
clock_t clock_start;
/**
HTTP protocol version identifier.
*/
uint8_t version;
/**
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;
/**
Set to true when the "Content-Length" header is written to the buffer.
*/
unsigned content_length_written : 1;
/**
Set to true in order to close the connection once the response was sent.
*/
unsigned should_close : 1;
/**
Internally used by the logging API.
*/
unsigned logged : 1;
/**
Reserved for future use.
*/
unsigned rsrv : 2;
} metadata;
} http_response_s;
/**
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"
});
*/
typedef struct {
/** 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;
} http_cookie_s;
/**
Initializes a response object with the request object. This function assumes the
response object memory is garbage and might have been stack-allocated.
Notice that the `http_request_s` pointer must point at a valid request object
and that the request object must remain valid until the response had been
completed.
Hangs on failuer (waits for available resources).
*/
http_response_s http_response_init(http_request_s *request);
/**
Releases any resources held by the response object (doesn't release the response
object itself, which might have been allocated on the stack).
This function assumes the response object might have been stack-allocated.
*/
void http_response_destroy(http_response_s *response);
/** Gets a response status, as a string. */
const char *http_response_status_str(uint16_t status);
/** Gets the mime-type string (C string) associated with the file extension. */
const char *http_response_ext2mime(const char *ext);
/**
Writes a header to the response. This function writes only the requested
number of bytes from the header name and the requested number of bytes from
the header value. It can be used even when the header name and value don't
contain NULL terminating bytes by passing the `.name_len` or `.value_len` data
in the `http_headers_s` structure.
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 http_response_write_header(http_response_s *, http_headers_s header);
#define http_response_write_header(response, ...) \
http_response_write_header(response, (http_headers_s){__VA_ARGS__})
/**
Set / Delete a cookie using this helper function.
To set a cookie, use (in this example, a session cookie):
http_response_set_cookie(response,
.name = "my_cookie",
.value = "data");
To delete a cookie, use:
http_response_set_cookie(response,
.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 http_response_set_cookie(http_response_s *, http_cookie_s);
#define http_response_set_cookie(response, ...) \
http_response_set_cookie(response, (http_cookie_s){__VA_ARGS__})
/**
Indicates that any pending data (i.e. unsent headers) should be sent and that no
more use of the response object will be made. This will also release any
resources aquired when the response object was initialized, similar to the
`http_response_destroy` function.
If logging was initiated and hadn't been performed, it will be performed.
If the connection was already closed, the function will return -1. On success,
the function returns 0.
*/
void http_response_finish(http_response_s *);
/**
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 http_response_write_body(http_response_s *, const char *body,
size_t length);
// /**
// REVIEW: IS THIS APPLICABLE FOR HTTP/2 AS WELL? this must be a unified API.
//
// 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 http_response_write_body_move(http_response_s*,
// 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 file and close it
using `fclose` once the data was sent.
If the connection was already closed, the function will return -1. On success,
the function returns 0.
*/
int http_response_sendfile(http_response_s *, int source_fd, off_t offset,
size_t length);
/**
Attempts to send the file requested using an **optional** response object (if no
response object is pointed to, a temporary response object will be created).
If a `file_path_unsafe` is provided, it will be appended to the `file_path_safe`
(if any) and URL decoded before attempting to locate and open the file. Any
insecure path manipulations in the `file_path_unsafe` (i.e. `..` or `//`) will
cause the function to fail.
`file_path_unsafe` MUST begine with a `/`, or it will be appended to
`file_path_safe` as part of the last folder's name. if `file_path_safe` ends
with a `/`, it will be trancated.
If the `log` flag is set, response logging will be performed.
If the path ends with a backslash ('/'), the string `"index.html"` will be
appended (a default file name for when serving a folder). No automatic folder
indexing is supported.
This function will honor Ranged requests by setting the byte range
appropriately.
On failure, the function will return -1 (no response will be sent).
On success, the function returns 0.
*/
int http_response_sendfile2(http_response_s *response, http_request_s *request,
const char *file_path_safe, size_t path_safe_len,
const char *file_path_unsafe,
size_t path_unsafe_len, uint8_t log);
/**
Starts counting miliseconds for log results.
*/
void http_response_log_start(http_response_s *);
/**
prints out the log to stderr.
*/
void http_response_log_finish(http_response_s *);
#endif