blob: 06e2d2bf4f9274bc9a66250813fb6f168079f83a [file] [log] [blame] [raw]
#include "http1.h"
#include "http1_simple_parser.h"
#include <string.h>
#include "errno.h"
#include <sys/mman.h>
#include <stdatomic.h>
/* *****************************************************************************
HTTP/1.1 data structure
*/
typedef struct {
protocol_s protocol;
http_settings_s* settings;
char buffer[HTTP1_MAX_HEADER_SIZE];
size_t buffer_pos;
void (*on_request)(http_request_s* request);
http_request_s
request; /* MUST be last, as it has memory extensions for the headers*/
} http1_protocol_s; /* ~ 8416 bytes for (8 * 1024) buffer size and 64 headers */
#define HTTP1_PROTOCOL_SIZE \
(sizeof(http1_protocol_s) + HTTP_REQUEST_SIZE(HTTP1_MAX_HEADER_COUNT))
char* HTTP1 = "http1";
/* *****************************************************************************
HTTP/1.1 callbacks
*/
static void http1_on_data(intptr_t uuid, http1_protocol_s* protocol);
/* *****************************************************************************
HTTP/1.1 protocol pooling
*/
#define HTTP1_POOL_MEMORY_SIZE (HTTP1_PROTOCOL_SIZE * HTTP1_POOL_SIZE)
struct {
void* memory;
http1_protocol_s* pool;
atomic_flag lock;
} http1_pool = {0};
inline static http1_protocol_s* pool_pop() {
http1_protocol_s* prot;
while (atomic_flag_test_and_set(&http1_pool.lock))
sched_yield();
prot = http1_pool.pool;
if (prot)
http1_pool.pool = prot->request.metadata.next;
atomic_flag_clear(&http1_pool.lock);
return prot;
}
inline static void pool_push(http1_protocol_s* protocol) {
if (protocol == NULL)
return;
while (atomic_flag_test_and_set(&http1_pool.lock))
sched_yield();
protocol->request.metadata.next = http1_pool.pool;
http1_pool.pool = protocol;
atomic_flag_clear(&http1_pool.lock);
}
static void http1_cleanup(void) {
// free memory
if (http1_pool.memory) {
munmap(http1_pool.memory, HTTP1_POOL_MEMORY_SIZE);
}
}
static void http1_init(void) {
pthread_mutex_t inner_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&inner_lock);
if (http1_pool.memory == NULL) {
// Allocate the memory
http1_pool.memory =
mmap(NULL, HTTP1_POOL_MEMORY_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (http1_pool.memory == NULL)
return;
// setup `atexit` cleanup rutine
atexit(http1_cleanup);
// initialize pool
void* pos = http1_pool.memory;
while (pos <
http1_pool.memory + (HTTP1_POOL_MEMORY_SIZE - HTTP1_PROTOCOL_SIZE)) {
pool_push(pos);
pos += HTTP1_PROTOCOL_SIZE;
}
}
pthread_mutex_unlock(&inner_lock);
}
/** initializes the library if it wasn't already initialized. */
#define validate_mem() \
{ \
if (http1_pool.memory == NULL) \
http1_init(); \
}
inline static void http1_free(http1_protocol_s* http) {
http_request_clear(&http->request);
validate_mem();
if (((void*)http) >= http1_pool.memory &&
((void*)http) <= (http1_pool.memory + HTTP1_POOL_MEMORY_SIZE)) {
pool_push(http);
} else
free(http);
}
protocol_s* http1_alloc(intptr_t fd, http_settings_s* settings) {
validate_mem();
// HTTP/1.1 should send a busy response
// if there aren't enough available file descriptors.
if (sock_uuid2fd(fd) >= (sock_max_capacity() - HTTP_BUSY_UNLESS_HAS_FDS)) {
sock_write(fd, "HTTP/1.1 503 Service Unavailable\r\n\r\nServer Busy.", 48);
return NULL;
}
// get an http object from the pool
http1_protocol_s* http = pool_pop();
// of malloc one
if (http == NULL)
http = malloc(HTTP1_PROTOCOL_SIZE);
// review allocation
if (http == NULL)
return NULL;
// we shouldn't update the `http` protocol as a struct, as this will waste
// time as the whole buffer will be zeroed out when there is no need.
// setup parsing state
http->buffer_pos = 0;
// setup protocol callbacks
http->protocol = (protocol_s){
.service = HTTP1,
.on_data = (void (*)(intptr_t, protocol_s*))http1_on_data,
.on_close = (void (*)(protocol_s*))http1_free,
};
// setup request data
http->request = (http_request_s){
.metadata.max_headers = HTTP1_MAX_HEADER_COUNT,
.metadata.fd = fd,
.metadata.owner = http,
};
// update settings
http->settings = settings;
http->on_request = settings->on_request;
// set the timeout
server_set_timeout(fd, settings->timeout);
return (protocol_s*)http;
}
/* *****************************************************************************
HTTP/1.1 protocol bare-bones implementation
*/
#define HTTP_BODY_CHUNK_SIZE 3072 // 4096
/* parse and call callback */
static void http1_on_data(intptr_t uuid, http1_protocol_s* protocol) {
ssize_t len = 0;
ssize_t result;
char buff[HTTP_BODY_CHUNK_SIZE];
char* buffer;
http_request_s* request = &protocol->request;
for (;;) {
// handle requests with no file data
if (request->body_file <= 0) {
// request headers parsing
if (len == 0) {
buffer = protocol->buffer;
// make sure headers don't overflow
len = sock_read(uuid, buffer + protocol->buffer_pos,
HTTP1_MAX_HEADER_SIZE - protocol->buffer_pos);
// update buffer read position.
protocol->buffer_pos += len;
}
if (len <= 0) {
return;
}
// parse headers
result =
http1_parse_request_headers(buffer, protocol->buffer_pos, request);
// review result
if (result >= 0) { // headers comeplete
// mark buffer position, for HTTP pipelining
protocol->buffer_pos = result;
// are we done?
if (request->content_length == 0 || request->body_str) {
goto handle_request;
}
// do we still have more data?
if (result < len) {
result =
http1_parse_request_body(buffer + result, len - result, request);
if (result >= 0) {
protocol->buffer_pos += result;
goto handle_request;
} else if (result == -1) // parser error
goto parser_error;
goto parse_body;
}
} else if (result == -1) // parser error
goto parser_error;
// assume incomplete (result == -2), even if wrong, we're right.
len = 0;
continue;
}
if (request->body_file > 0) {
parse_body:
buffer = buff;
// request body parsing
len = sock_read(uuid, buffer, HTTP_BODY_CHUNK_SIZE);
if (len <= 0)
return;
result = http1_parse_request_body(buffer, len, request);
if (result >= 0) {
// set buffer pos for piplining support
protocol->buffer_pos = result;
goto handle_request;
} else if (result == -1) // parser error
goto parser_error;
goto parse_body;
}
continue;
handle_request:
// review required headers / data
if (request->host == NULL)
goto bad_request;
http_settings_s* settings = protocol->settings;
// call request callback
if (protocol && settings &&
(protocol->settings->public_folder == NULL ||
http_response_sendfile2(NULL, request, settings->public_folder,
settings->public_folder_length, request->path,
request->path_len, settings->log_static))) {
protocol->on_request(request);
}
// clear request state
http_request_clear(request);
// rotate buffer for HTTP pipelining
if (result >= len) {
len = 0;
} else {
memmove(protocol->buffer, buffer + protocol->buffer_pos, len - result);
len -= result;
}
// restart buffer position
protocol->buffer_pos = 0;
buffer = protocol->buffer;
}
// no routes lead here.
fprintf(stderr,
"I am lost on a deserted island, no code can reach me here :-)\n");
return; // How did we get here?
parser_error:
if (request->headers_count == request->metadata.max_headers)
goto too_big;
bad_request : {
http_response_s response = http_response_init(request);
response.status = 400;
http_response_write_body(&response, "Bad Request", 11);
http_response_finish(&response);
sock_close(uuid);
protocol->buffer_pos = 0;
return;
}
too_big : {
http_response_s response = http_response_init(request);
response.status = 431;
http_response_write_body(&response, "Request Header Fields Too Large", 31);
http_response_finish(&response);
sock_close(uuid);
protocol->buffer_pos = 0;
return;
}
}
/* *****************************************************************************
HTTP/1.1 listenning API implementation
*/
#undef http1_listen
static void http1_on_init(http_settings_s* settings) {
if (settings->timeout == 0)
settings->timeout = 5;
if (settings->public_folder) {
settings->public_folder_length = strlen(settings->public_folder);
if (settings->public_folder[0] == '~' &&
settings->public_folder[1] == '/' && getenv("HOME")) {
char* home = getenv("HOME");
size_t home_len = strlen(home);
char* tmp = malloc(settings->public_folder_length + home_len + 1);
memcpy(tmp, home, home_len);
if (home[home_len - 1] == '/')
--home_len;
memcpy(tmp + home_len, settings->public_folder + 1,
settings->public_folder_length); // copy also the NULL
settings->public_folder = tmp;
settings->private_metaflags |= 1;
settings->public_folder_length = strlen(settings->public_folder);
}
}
}
static void http1_on_finish(http_settings_s* settings) {
if (settings->private_metaflags & 1)
free((void*)settings->public_folder);
if (settings->private_metaflags & 2)
free(settings);
}
int http1_listen(const char* port,
const char* address,
http_settings_s settings) {
if (settings.on_request == NULL) {
fprintf(
stderr,
"ERROR: http1_listen requires the .on_request parameter to be set\n");
exit(11);
}
http_settings_s* settings_copy = malloc(sizeof(*settings_copy));
*settings_copy = settings;
settings_copy->private_metaflags = 2;
return server_listen(.port = port, .address = address,
.on_start = (void*)http1_on_init,
.on_finish = (void*)http1_on_finish,
.on_open = (void*)http1_alloc, .udata = settings_copy);
}