Initial commit

master
teknomunk 1 year ago
parent 0563ef6b77
commit 50565dcf4d

@ -0,0 +1,663 @@
#define _GNU_SOURCE
#include "http_request.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <ctype.h>
#include <fcntl.h>
#include "worker_pool.h"
enum {
recv_buffer_size = 512,
};
struct buffered_write
{
struct buffered_write* next;
char* buffer;
int size;
};
struct http_request
{
// Socket request was received on
int sock;
bool has_nonblocking_read;
// Status
bool complete;
struct {
bool (*callback)( void* data );
void* data;
} nonblock;
// HTTP Protocol request data
char* remote_addr;
char* method;
char* full_path;
int levels_to_root;
char* path;
struct header* request_headers;
// body
FILE* response_body;
bool chunked_body;
long response_size;
struct buffered_write* response_body_buffer;
struct header* response_headers;
bool sent_response_headers;
size_t unread_post_content;
// Memory holds
char* first_line;
// Fiber
ucontext_t context;
// Request handler callback
void (*handler)( struct http_request* req, void* handler_data );
void* handler_data;
// Linked list data
struct http_request* next;
};
struct header
{
struct header* next;
char* key;
char* value;
};
ssize_t getline_stripped( char** restrict lineptr, size_t* restrict n, FILE* restrict stream)
{
ssize_t res = getline( lineptr, n, stream );
if( res == -1 ) {
return -1;
}
if( res == 0 ) {
return 0;
}
while( res > 0 && isspace((*lineptr)[res-1]) ) {
(*lineptr)[res-1] = '\0';
res -= 1;
}
return res;
}
static __thread struct http_request* current_request = NULL;
static void request_fiber_entry()
{
// Pull off current_request so that other fibers can be created and we won't lose our data
struct http_request* req = current_request;
FILE* f = http_request_get_request_data( req );
// Read first line, should be something like: <METHOD> <PATH> HTTP/1.1\n
size_t n = 0;
getline_stripped( &req->first_line, &n, f );
char* ptr = NULL;
req->method = strtok_r( req->first_line, " ", &ptr );
req->path = req->full_path = strtok_r( NULL, " ", &ptr );
printf( "> %s %s\n", req->method, req->path );
int depth = -1;
int len = strlen(req->full_path);
for( int i = 0; i < len; ++i ) {
if( req->full_path[i] == '/' ) {
depth += 1;
}
}
req->levels_to_root = depth;
int length;
int content_length = 0;
do {
n = 0;
getline_stripped( &ptr, &n, f );
length = strlen(ptr);
if( length > 0 ) {
char* value;
char* key = strtok_r( ptr, ":", &value );
while( *value == ' ' ) { ++value; }
struct header* h = (struct header*)malloc(sizeof(struct header));
h->next = req->request_headers;
h->key = key;
h->value = value;
printf( "Header: %s: %s\n", h->key, h->value );
req->request_headers = h;
if( 0 == strcmp("Content-Length", key ) ) {
sscanf( value, "%u", &content_length );
}
}
} while( length > 0 );
req->unread_post_content = content_length;
req->handler( req, req->handler_data );
fflush( req->response_body );
if( req->chunked_body ) {
req->chunked_body = false;
fprintf( req->response_body, "0\n\n" );
}
fflush( req->response_body );
printf( "Request completed.\n" );
req->complete = true;
}
static bool ready( void* data )
{
return true;
}
struct io_bytes_args
{
int fd;
int size;
char* buffer;
int size_read;
bool failed;
};
static void read_bytes_async( void* data )
{
struct read_bytes_args* args = (struct read_bytes_args*)data;
}
static bool read_bytes_nonblock( void* data )
{
struct io_bytes_args* args = (struct io_bytes_args*)data;
ssize_t res = read( args->fd, args->buffer, args->size );
if( res == -1 ) {
if( errno == EAGAIN || errno == EWOULDBLOCK ) {
return false;
} else {
printf( "Error reading from %d: %s\n", args->fd, strerror(errno) );
args->failed = true;
return true;
}
} else {
args->size_read = res;
return true;
}
}
static bool write_bytes_nonblock( void* data )
{
struct io_bytes_args* args = (struct io_bytes_args*)data;
if( args->failed ) {
return true;
}
ssize_t res = write( args->fd, args->buffer, args->size );
if( res == -1 ) {
if( errno == EAGAIN || errno == EWOULDBLOCK ) {
return false;
} else {
printf( "Error writing to %d: %s\n", args->fd, strerror(errno) );
args->failed = true;
return true;
}
} else {
args->size_read += res;
if( res < args->size ) {
args->buffer = &args->buffer[res];
args->size -= res;
return false;
} else {
return true;
}
}
}
static ssize_t http_request_read( void* cookie, char* buf, size_t size )
{
struct http_request* req = (struct http_request*)cookie;
if( req->unread_post_content == -1 ) {
size = 1;
} else if( req->unread_post_content == 0 ) {
return 0;
} else {
if( size > req->unread_post_content ) {
size = req->unread_post_content;
}
}
struct io_bytes_args args;
args.fd = req->sock;
args.failed = false;
args.buffer = buf;
args.size = size;
if( req->has_nonblocking_read ) {
http_request_call_nonblock( read_bytes_nonblock, &args );
} else {
http_request_call_async( read_bytes_async, &args );
}
// Force the request to terminate
if( args.failed ) {
printf( "Read request failed\n" );
req->complete = true;
http_request_call_nonblock( ready, NULL );
}
if( req->unread_post_content != -1 ) {
req->unread_post_content -= args.size_read;
}
return args.size_read;
}
static ssize_t http_request_write_body( struct http_request* req, const char* buf, size_t size )
{
/*
printf( "> " );
for( int i = 0; i < size; ++i ) {
if( isprint(buf[i]) ) {
printf( "%.1s", &buf[i] );
} else if( buf[i] == '\n' ) {
printf( "\\n" );
} else {
printf( "\\x%02x", (int)buf[i] );
}
}
printf( " (%d)\n", size );
//*/
struct io_bytes_args args;
args.fd = req->sock;
args.failed = false;
args.buffer = (char*)buf;
args.size_read = 0;
args.size = size;
//if( req->has_nonblocking_read ) {
http_request_call_nonblock( write_bytes_nonblock, &args );
//} else {
// http_request_call_async( read_bytes_async, &args );
//}
// Force the request to terminate
if( args.failed ) {
printf( "Write request failed\n" );
req->complete = true;
http_request_call_nonblock( ready, NULL );
}
return args.size_read;
/*
int res = write( req->sock, buf, size );
if( res == -1 ) {
if( errno == EAGAIN || errno == EWOULDBLOCK ) {
printf( "TODO: handle non-blocking body write\n" );
exit(1);
} else {
printf( "Error while writing to socket: %s\n", strerror(errno) );
printf( "Was writing %.*s to %d\n", size, buf, req->sock );
req->complete = true;
http_request_call_nonblock( NULL, NULL );
}
}
*/
}
static ssize_t http_request_body_write( void* cookie, const char* buf, size_t size )
{
struct http_request* req = (struct http_request*)cookie;
if( req->sent_response_headers ) {
if( req->chunked_body ) {
char buffer[32];
int res = snprintf( buffer, 32, "%X\n", size );
http_request_write_body( req, buffer, res );
ssize_t written = http_request_write_body( req, buf, size );
http_request_write_body( req, "\n", 1 );
return written;
} else {
ssize_t written = http_request_write_body( req, buf, size );
if( written != size ) {
printf( "tried to write %d, but only wrote %d\n", size, written );
}
return written;
}
} else {
printf( "Response headers haven't been sent yet.\n" );
printf( "TODO: buffer response\n" );
}
return 0;
}
static int http_request_body_close( void* cookie )
{}
static int http_request_seek( void* cookie, off64_t*, int )
{
return 0;
}
static cookie_io_functions_t http_response_functions = {
.read = http_request_read,
.write = http_request_body_write,
.seek = http_request_seek,
.close = http_request_body_close,
};
struct http_request* http_request_new( ucontext_t* main, int sock, const char* remote_addr, http_server_handler handler, void* handler_data )
{
struct http_request* req = (struct http_request*)malloc(sizeof(struct http_request));
if( !req ) {
printf( "Failed to allocate memory for request\n" );
return NULL;
}
req->complete = false;
req->nonblock.callback = ready;
req->nonblock.data = NULL;
req->sock = sock;
req->remote_addr = strdup(remote_addr);
req->handler = handler;
req->handler_data = handler_data;
req->first_line = NULL;
req->request_headers = NULL;
req->response_headers = NULL;
req->sent_response_headers = false;
req->chunked_body = false;
req->response_size = -1;
req->unread_post_content = -1;
req->response_body = fopencookie( (void*)req, "w+", http_response_functions );
if( -1 == fcntl( sock, F_SETFL, O_NONBLOCK ) ) {
printf( "Unable to set O_NONBLOCK on request socket, continuing with reduced capacity: %s\n", strerror(errno) );
req->has_nonblocking_read = false;
} else {
req->has_nonblocking_read = true;
}
if( -1 == getcontext( &req->context ) ) {
printf( "Failed to create context for request: %s\n", strerror(errno) );
req->complete = true;
return req;
}
req->context.uc_stack.ss_size = 16384;
req->context.uc_stack.ss_sp = (char*)malloc( req->context.uc_stack.ss_size );
if( !req->context.uc_stack.ss_sp ) {
printf( "Failed to allocate stack for request.\n");
free(req);
close(sock);
return NULL;
}
req->context.uc_link = main;
makecontext( &req->context, request_fiber_entry, 0 );
}
void http_request_release( struct http_request* req )
{
printf( "closing socket %d\n", req->sock );
close( req->sock );
for( struct header* iter = req->request_headers; iter; ) {
struct header* next = iter->next;
free(iter->key);
free(iter);
iter = next;
}
free( req->first_line );
free( req->context.uc_stack.ss_sp );
free( req->remote_addr );
free( req );
}
void http_request_append_list_front( struct http_request* req, struct http_request** list_head )
{
req->next = (*list_head);
*list_head = req;
}
void http_request_clear_completed_at( struct http_request** list_head )
{
struct http_request* req;
while( (req = *list_head) && req->complete ) {
*list_head = req->next;
http_request_release( req );
}
}
void http_request_clear_completed( struct http_request* req )
{
http_request_clear_completed_at( &req->next );
}
struct http_request* http_request_get_next( struct http_request* req )
{
return req->next;
}
const char* http_request_get_remote_host_address( struct http_request* req )
{
return req->remote_addr;
}
const char* string_for_status( int status_code )
{
switch( status_code ) {
case 200:
return "OK";
default:
return "Unknown";
}
}
void http_request_begin_send_headers( struct http_request* req, int status_code, bool chunked )
{
req->chunked_body = false;
FILE* f = http_request_get_response_body(req);
req->sent_response_headers = true;
fprintf( f, "HTTP/1.1 %d %s\n", status_code, string_for_status( status_code ) );
if( chunked ) {
http_request_send_header( req, "Transfer-Encoding", "chunked" );
} else {
char buffer[512];
snprintf( buffer, 512, "%ld", req->response_size );
http_request_send_header( req, "Content-Size", buffer );
}
}
void http_request_send_header( struct http_request* req, const char* header, const char* value )
{
FILE* f = http_request_get_response_body(req);
fprintf( f, "%s: %s\n", header, value );
}
void http_request_end_send_headers( struct http_request* req, bool chunked )
{
FILE* f = http_request_get_response_body(req);
fprintf( f, "\n" );
fflush(f);
req->chunked_body = chunked;
}
void http_request_send_headers( struct http_request* req, int status_code, const char* content_type, bool chunked )
{
http_request_begin_send_headers( req, status_code, chunked );
http_request_send_header( req, "Content-Type", content_type );
http_request_end_send_headers( req, chunked );
}
FILE* http_request_get_response_body( struct http_request* req )
{
return req->response_body;
}
FILE* http_request_get_request_data( struct http_request* req )
{
return req->response_body;
}
bool http_request_send_file( struct http_request* req, const char* fs_path, const char* content_type )
{
FILE* f = fopen( fs_path, "r" );
if( !f ) {
printf( "Unable to open file %s: %s\n", fs_path, strerror(errno) );
return false;
}
// Get file size
fseek(f,0,SEEK_END);
req->response_size = ftell(f);
fseek(f,0,SEEK_SET);
http_request_send_headers( req, 200, content_type, false );
FILE* body = http_request_get_response_body( req );
size_t res;
char buffer[512];
while( res = fread( buffer, 1, sizeof(buffer), f ) ) {
fwrite( buffer, 1, sizeof(buffer), body );
}
fflush(body);
return true;
}
const char* http_request_get_relative_path( struct http_request* req, const char* path )
{
if( path[0] != '/' ) {
return path;
}
int size = strlen(path) + 3 * req->levels_to_root;
char* res = malloc( size );
res[0] = '\0';
for( int i = 0; i < req->levels_to_root; ++i ) {
strcat( res, "../" );
}
return strcat( res, &path[1] );
}
bool http_request_route( struct http_request* req, const char* str )
{
int len = strlen(str);
if( 0 == strncmp( req->path, str, len ) ) {
req->path += len;
return true;
}
return false;
}
bool http_request_route_term( struct http_request* req, const char* str )
{
int len = strlen(str);
if( 0 == strncmp( req->path, str, len ) ) {
if( req->path[len] == '\0' ) {
req->path += len;
return true;
}
}
return false;
}
bool http_request_route_method( struct http_request* req, const char* method )
{
return ( 0 == strcmp( method, req->method ) );
}
char* http_request_route_get_dir( struct http_request* req )
{
int len = strlen( req->path );
for( int i = 0; i < len; ++i ) {
char ch = req->path[i];
if( ch == '/' ) {
char* res = strndup( req->path, i );
req->path += ( i + 1 );
return res;
}
}
return NULL;
}
void http_request_process( struct http_request* req, ucontext_t* fallback )
{
if( !req->nonblock.callback( req->nonblock.data ) ) {
return;
}
// Pass the current request into the entry point thru thread-local storage (safe-ish)
current_request = req;
// Run the fiber
if( swapcontext( fallback, &req->context ) == -1 ) {
printf( "Error entering fiber context: %s\n", strerror(errno) );
req->complete = true;
}
// Clear entry point data
current_request = NULL;
}
struct async_work_data
{
void (*func)( void* data );
void* func_data;
bool complete;
};
static void async_work( void* data_void )
{
struct async_work_data* data = (struct async_work_data*)( data_void );
data->func( data->func_data );
data->complete = true;
}
static bool check_async_work_complete( void* data_void )
{
struct async_work_data* data = (struct async_work_data*)( data_void );
return data->complete;
}
void http_request_call_async( void(*func)( void* ), void* data )
{
// Convert async call to future + nonblocking check for completion
struct async_work_data aw_data;
aw_data.func = func;
aw_data.func_data = data;
aw_data.complete = false;
// Submit the task to the worker pool
worker_pool_add_task( async_work, &aw_data );
// Handle nonblocking callback (will return when async work is finished)
http_request_call_nonblock( check_async_work_complete, &aw_data );
}
void http_request_call_nonblock( bool(*func)( void* ), void* data )
{
if( func != ready && func(data) ) {
return;
}
// Hold on to the current request
struct http_request* req = current_request;
// Preserve the old callback data
bool (*old_nonblock_callback)(void*) = req->nonblock.callback;
void* old_nonblock_data = req->nonblock.data;
// Update to point to new callback
req->nonblock.callback = func;
req->nonblock.data = data;
// Yield this fiber. We will not return here until the callback returns
// true.
if( -1 == swapcontext( &req->context, req->context.uc_link ) ) {
printf( "Failure during context swap: %s\n", strerror(errno) );
exit(1);
}
// restor the old callback data
req->nonblock.callback = old_nonblock_callback;
req->nonblock.data = old_nonblock_data;
}

@ -0,0 +1,50 @@
#pragma once
#include <ucontext.h>
#include <stdbool.h>
#include <stdio.h>
struct http_request;
typedef void (*http_server_handler)( struct http_request*, void* handler_data );
// Construct/deconstruct
struct http_request* http_request_new( ucontext_t* main, int sock, const char* remote_addr, http_server_handler handler, void* handler_data );
void http_request_release( struct http_request* req );
// Request linked list handling
void http_request_append_list_front( struct http_request* req, struct http_request** list_head );
void http_request_clear_completed_at( struct http_request** list_head );
void http_request_clear_completed( struct http_request* req );
struct http_request* http_request_get_next( struct http_request* req );
// Raw data handling
char* http_request_read_line( struct http_request* req );
// Request data access
const char* http_request_get_remote_host_address( struct http_request* req );
// Response handling
void http_request_send_headers( struct http_request* req, int status_code, const char* content_type, bool chunked );
void http_request_begin_send_headers( struct http_request* req, int status_code, bool chunked );
void http_request_send_header( struct http_request* req, const char* header, const char* value );
void http_request_end_send_headers( struct http_request* req, bool chunked );
FILE* http_request_get_response_body( struct http_request* req );
FILE* http_request_get_request_data( struct http_request* req );
bool http_request_send_file( struct http_request* req, const char* fs_path, const char* content_type );
// Path routing
const char* http_request_get_relative_path( struct http_request* req, const char* path );
bool http_request_route( struct http_request* req, const char* str );
bool http_request_route_term( struct http_request* req, const char* str );
bool http_request_route_method( struct http_request* req, const char* method );
char* http_request_route_get_dir( struct http_request* req );
// Run the request fiber
void http_request_process( struct http_request* req, ucontext_t* fallback );
// Pause the current request until a function call is completed in a worker thread
void http_request_call_async( void (*func)( void* ), void* data );
// Pause the current request until a non-blocking handler is completed
void http_request_call_nonblock( bool (*func)( void* ), void* data );

@ -0,0 +1,141 @@
#include "http_server.h"
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <ucontext.h>
#include "app_args.h"
#include "http_request.h"
struct http_request;
struct http_server
{
int sock;
http_server_handler handler;
void* handler_data;
struct http_request* pending_requests;
ucontext_t context;
};
struct http_server* http_server_new( struct app_args* args, http_server_handler handler, void* handler_data )
{
// Create server structure
struct http_server* srv = (struct http_server*)malloc( sizeof(struct http_server) );
if( !srv ) {
printf( "Unable to allocate memory for server.\n" );
return NULL;
}
srv->handler = handler;
srv->handler_data = handler_data;
srv->pending_requests = NULL;
// Open server socket
srv->sock = socket( AF_INET, SOCK_STREAM, 0 );
if( srv->sock == -1 ) {
printf( "Error creating socket: %s\n", strerror(errno) );
exit(1);
}
// Allow multiple sockets to listen on this port
int optval = 1;
if( -1 == setsockopt( srv->sock, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval) ) ) {
printf( "Could not enable port reuse: %s\n", strerror(errno) );
}
// Bind socket
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(args->port);
addr.sin_addr.s_addr = INADDR_ANY;
if( !inet_aton( args->addr, &addr.sin_addr ) ) {
printf( "Unable to parse address %s: %s\n", args->addr, strerror(errno) );
exit(1);
}
if( -1 == bind( srv->sock, (struct sockaddr*)&addr, sizeof(addr) ) ) {
printf( "Error binding socket to %s:%d: %s\n",
args->addr, args->port, strerror(errno)
);
exit(1);
}
if( -1 == listen( srv->sock, 0 ) ) {
printf( "Unable to listen on %s:%d: %s\n",
args->addr, args->port, strerror(errno)
);
exit(1);
}
if( -1 == fcntl( srv->sock, F_SETFL, O_NONBLOCK ) ) {
printf( "Unable to set O_NONBLOCK on server listening socket, continuing with reduced capacity: %s\n", strerror(errno) );
}
return srv;
}
void http_server_process( struct http_server* srv )
{
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
int res = accept( srv->sock, (struct sockaddr*)&addr, &addrlen );
if( res == -1 ) {
switch( errno ) {
case EAGAIN:
case ENETDOWN:
case EPROTO:
case ENOPROTOOPT:
case EHOSTDOWN:
case ENONET:
case EHOSTUNREACH:
case ENETUNREACH:
break;
default:
printf( "Error when accepting connection: %s\n", strerror(errno) );
}
} else {
// Handle connection
printf( "Accepting connection\n" );
struct http_request* req = http_request_new( &srv->context, res, inet_ntoa(addr.sin_addr), srv->handler, srv->handler_data );
if( req ) {
http_request_append_list_front( req, &srv->pending_requests );
}
}
struct http_request* req;
// Clear completed requests at start of list
http_request_clear_completed_at( &srv->pending_requests );
for( req = srv->pending_requests; req; req = http_request_get_next(req) ) {
// clear completed requests in middle of list
http_request_clear_completed( req );
// Process request
http_request_process( req, &srv->context );
}
}
void http_server_release( struct http_server* srv )
{
printf( "Shutting down HTTP server..." );
// TODO: wait for all pending requests to finish
close( srv->sock );
free( srv );
printf( "done.\n" );
}

@ -0,0 +1,12 @@
#pragma once
struct app_args;
struct http_server;
struct http_request;
typedef void (*http_server_handler)( struct http_request*, void* handler_data );
struct http_server* http_server_new( struct app_args* args, http_server_handler handler, void* handler_data );
void http_server_process( struct http_server* srv );
void http_server_release( struct http_server* srv );

@ -0,0 +1,10 @@
#include "worker_pool.h"
#include <stdlib.h>
#include <stdio.h>
void worker_pool_add_task( void (*func)( void* ), void* data )
{
printf( "TODO: implement worker_pool_add_task\n" );
exit(1);
}

@ -0,0 +1,4 @@
#pragma once
void worker_pool_add_task( void (*func)( void* ), void* data );
Loading…
Cancel
Save