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…
Reference in new issue