You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
976 lines
24 KiB
C
976 lines
24 KiB
C
#define _GNU_SOURCE
|
|
#include "request.h"
|
|
|
|
#include "collections/list.h"
|
|
|
|
#include "worker_pool.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 <stdint.h>
|
|
#include <time.h>
|
|
#include <sys/stat.h>
|
|
|
|
enum {
|
|
recv_buffer_size = 512,
|
|
};
|
|
|
|
struct buffered_write
|
|
{
|
|
struct buffered_write* next;
|
|
char* buffer;
|
|
int size;
|
|
};
|
|
|
|
struct http_request
|
|
{
|
|
bool debug;
|
|
|
|
// 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;
|
|
FILE* request;
|
|
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;
|
|
struct http_header data;
|
|
};
|
|
|
|
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;
|
|
if( getline_stripped( &req->first_line, &n, f ) <= 0 ) {
|
|
goto failed;
|
|
}
|
|
char* ptr = NULL;
|
|
req->method = strtok_r( req->first_line, " ", &ptr );
|
|
req->path = req->full_path = strtok_r( NULL, " ", &ptr );
|
|
if( !req->method ) { goto failed; }
|
|
if( !req->path ) { goto failed; }
|
|
|
|
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->data.key = key;
|
|
h->data.value = strdup(value);
|
|
if( req->debug ) {
|
|
printf( "Header: %s: %s\n", h->data.key, h->data.value );
|
|
}
|
|
list_add_front( &req->request_headers, h );
|
|
|
|
if( 0 == strcmp("Content-Length", key ) ) {
|
|
sscanf( value, "%u", &content_length );
|
|
}
|
|
} else {
|
|
free(ptr);
|
|
}
|
|
} 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" );
|
|
failed:
|
|
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 )
|
|
{
|
|
if( !req ) {
|
|
return 0;
|
|
}
|
|
/*
|
|
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;
|
|
}
|
|
|
|
static ssize_t http_request_body_write( void* cookie, const char* buf, size_t size )
|
|
{
|
|
// TODO: rework cookie to allow request to be deleted and have this just dump the write data
|
|
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, "%lX\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 %lu, but only wrote %lu\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 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
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->debug = false;
|
|
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 );
|
|
req->request = fopencookie( (void*)req, "r", 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 + 15000;
|
|
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 );
|
|
|
|
return req;
|
|
}
|
|
|
|
static uint64_t curr_time()
|
|
{
|
|
//uint64_t time;
|
|
struct timespec ts;
|
|
clock_gettime( CLOCK_REALTIME, &ts );
|
|
return (uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec;
|
|
}
|
|
|
|
void http_request_release( struct http_request* req )
|
|
{
|
|
close( req->sock );
|
|
//printf( "socket %d closed at %lu\n", req->sock, curr_time() );
|
|
|
|
for( struct header* iter = req->request_headers; iter; ) {
|
|
struct header* next = iter->next;
|
|
free(iter->data.key);
|
|
free(iter->data.value);
|
|
free(iter);
|
|
iter = next;
|
|
}
|
|
for( struct header* iter = req->response_headers; iter; ) {
|
|
struct header* next = iter->next;
|
|
free(iter->data.key);
|
|
free(iter->data.value);
|
|
free(iter);
|
|
iter = next;
|
|
}
|
|
|
|
free( req->first_line );
|
|
free( req->context.uc_stack.ss_sp );
|
|
free( req->remote_addr );
|
|
free( req );
|
|
}
|
|
|
|
void http_request_set_debug( struct http_request* req, bool debug )
|
|
{
|
|
req->debug = debug;
|
|
}
|
|
|
|
void http_request_append_list_front( struct http_request* req, struct http_request** list_head )
|
|
{
|
|
req->next = (*list_head);
|
|
*list_head = req;
|
|
}
|
|
int http_request_clear_completed_at( struct http_request** list_head )
|
|
{
|
|
int count = 0;
|
|
struct http_request* req;
|
|
while( (req = *list_head) && req->complete ) {
|
|
*list_head = req->next;
|
|
http_request_release( req );
|
|
count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
int http_request_clear_completed( struct http_request* req )
|
|
{
|
|
return 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* http_request_get_header( struct http_request* req, const char* header )
|
|
{
|
|
for( struct header* iter = req->request_headers; iter; iter = iter->next ) {
|
|
if( 0 == strcasecmp( header, iter->data.key ) ) {
|
|
return iter->data.value;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
void http_request_copy_headers( struct http_request* req, struct collection dst )
|
|
{
|
|
struct collection src = {
|
|
.ptr = &req->request_headers,
|
|
.vtable = &list_vtable,
|
|
.itable = &http_header_itable,
|
|
};
|
|
list_copy_cc( src, dst );
|
|
}
|
|
const char* string_for_status( int status_code )
|
|
{
|
|
switch( status_code ) {
|
|
case 200:
|
|
return "Ok";
|
|
case 202:
|
|
return "Accepted";
|
|
case 302:
|
|
return "Found";
|
|
case 304:
|
|
return "Not Modified";
|
|
case 401:
|
|
return "Unauthorized";
|
|
case 402:
|
|
return "Payment Required";
|
|
case 404:
|
|
return "Not Found";
|
|
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( req->debug || ( status_code >= 400 ) ) {
|
|
printf( "> %s %s -> %d %s\n", req->method, req->full_path, status_code, string_for_status( status_code ) );
|
|
}
|
|
|
|
//http_request_send_header( req, "Connection", "close" );
|
|
|
|
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 );
|
|
}
|
|
static const char* day_of_week_str[] = {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
|
};
|
|
static const char* month_of_year_str[] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
void http_request_send_header_last_modified( struct http_request* req, time_t timestamp )
|
|
{
|
|
struct tm t;
|
|
gmtime_r( ×tamp, &t );
|
|
char buffer[100];
|
|
snprintf( buffer, sizeof(buffer), "%s, %02d %s %04d %02d:%02d:%02d GMT",
|
|
day_of_week_str[t.tm_wday],
|
|
t.tm_mday,
|
|
month_of_year_str[t.tm_mon],
|
|
t.tm_year + 1900,
|
|
t.tm_hour, t.tm_min, t.tm_sec
|
|
);
|
|
http_request_send_header( req, "Last-Modified", buffer );
|
|
}
|
|
void http_request_end_send_headers( struct http_request* req, bool chunked )
|
|
{
|
|
FILE* f = http_request_get_response_body(req);
|
|
|
|
for( struct header* h = req->response_headers; h; h = h->next ) {
|
|
fprintf( f, "%s: %s\n", h->data.key, h->data.value );
|
|
}
|
|
|
|
fprintf( f, "\n" );
|
|
fflush(f);
|
|
|
|
req->chunked_body = chunked;
|
|
}
|
|
void http_request_add_header( struct http_request* req, const char* header, const char* value )
|
|
{
|
|
struct header* h;
|
|
h = malloc(sizeof(*h));
|
|
memset(h,0,sizeof(*h));
|
|
|
|
h->data.key = strdup(header);
|
|
h->data.value = strdup(value);
|
|
|
|
h->next = req->response_headers;
|
|
req->response_headers = h;
|
|
}
|
|
|
|
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->request;
|
|
}
|
|
|
|
static void find_precompressed_data( struct http_request* req, const char* fs_path, char* buffer, int size, char* encoding, int encoding_size )
|
|
{
|
|
const char* accept_encoding = http_request_get_header(req, "Accept-Encoding" );
|
|
|
|
strncpy( buffer, fs_path, size );
|
|
struct stat file_data;
|
|
if( -1 == stat(fs_path,&file_data) ) {
|
|
return;
|
|
}
|
|
int min_size = file_data.st_size;
|
|
|
|
char candidate[512];
|
|
|
|
if( accept_encoding ) {
|
|
//printf( "Acceptable encodings: %s\n", accept_encoding );
|
|
char encodings[512];
|
|
strncpy(encodings,accept_encoding,sizeof(encodings));
|
|
char* remain = NULL;
|
|
for( char* enc = strtok_r(encodings,",",&remain); enc; enc = strtok_r(NULL,",",&remain) ) {
|
|
// Strip whitespace
|
|
while( *enc == ' ') { ++enc; }
|
|
char* end = &enc[strlen(enc)-1];
|
|
while( *end == ' ') { *enc = '\0'; --enc; }
|
|
|
|
//printf( "Encoding: %s\n", enc );
|
|
for( char* i = enc; *i; ++i ) {
|
|
if( !isalpha(*i) ) { goto skip_encoding; }
|
|
}
|
|
snprintf( candidate, sizeof(candidate), "%s.%s", fs_path, enc );
|
|
//printf( "Candidate filename: %s\n", candidate );
|
|
|
|
if( 0 == stat(candidate,&file_data) ) {
|
|
if( file_data.st_size < min_size ) {
|
|
//printf( "Current minimum is %d bytes for %s\n", size, candidate );
|
|
strncpy( buffer, candidate, size );
|
|
min_size = file_data.st_size;
|
|
|
|
strncpy( encoding, enc, encoding_size );
|
|
}
|
|
}
|
|
|
|
skip_encoding:
|
|
}
|
|
}
|
|
|
|
if( *encoding ) {
|
|
printf( "Using file: %s (encoded with %s)\n", buffer, encoding );
|
|
} else {
|
|
printf( "Using file: %s\n", buffer );
|
|
}
|
|
}
|
|
static bool try_if_modified_since( struct http_request* req, time_t last_modified )
|
|
{
|
|
const char* if_modified_since_header = http_request_get_header( req, "If-Modified-Since" );
|
|
if( !if_modified_since_header ) {
|
|
//printf( "IMS -> No header\n" );
|
|
return false;
|
|
}
|
|
|
|
// Expected format Sat, 31 Dec 2022 19:59:14 GMT
|
|
struct tm t = { 0 };
|
|
char day_of_week[4] = { 0 };
|
|
char month[4] = { 0 };
|
|
char timezone[4] = { 0 };
|
|
int count = 0;
|
|
if( 8 != (count=sscanf( if_modified_since_header, "%3s, %d %3s %d %d:%d:%d %3s",
|
|
day_of_week,
|
|
&t.tm_mday,
|
|
month,
|
|
&t.tm_year,
|
|
&t.tm_hour, &t.tm_min, &t.tm_sec,
|
|
timezone
|
|
)) ) {
|
|
printf( "IMS -> Date format error. Got only %d items\n", count );
|
|
return false;
|
|
}
|
|
if( 0 != strcmp(timezone,"GMT") ) {
|
|
printf( "IMS -> Unsupported timezone\n" );
|
|
return false;
|
|
}
|
|
t.tm_year -= 1900;
|
|
|
|
time_t test_time = timegm(&t);
|
|
if( last_modified < test_time ) {
|
|
printf( "IMS -> %ld < %ld = Has been modified, resend required\n", last_modified, test_time );
|
|
return false;
|
|
}
|
|
|
|
printf( "If-Modified-Since = %s -> hasn't been modified\n", if_modified_since_header );
|
|
|
|
http_request_begin_send_headers( req, 304, false );
|
|
http_request_send_header_last_modified( req, last_modified );
|
|
http_request_end_send_headers( req, false );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool http_request_send_file( struct http_request* req, const char* fs_path, const char* content_type )
|
|
{
|
|
char filename[512];
|
|
char encoding[30] = "";
|
|
find_precompressed_data( req, fs_path, filename, sizeof(filename), encoding, sizeof(encoding) );
|
|
|
|
FILE* f = fopen( filename, "r" );
|
|
if( !f ) {
|
|
printf( "Unable to open file %s: %s\n", fs_path, strerror(errno) );
|
|
return false;
|
|
}
|
|
|
|
// Get file information
|
|
struct stat s;
|
|
stat( fs_path, &s );
|
|
time_t last_modified = s.st_mtim.tv_sec;
|
|
if( try_if_modified_since( req, last_modified ) ) {
|
|
return true;
|
|
}
|
|
|
|
// Get filesize
|
|
stat( filename, &s );
|
|
req->response_size = s.st_size;
|
|
|
|
http_request_begin_send_headers( req, 200, false );
|
|
if( *encoding ) {
|
|
http_request_send_header( req, "Content-Encoding", encoding );
|
|
}
|
|
http_request_send_header( req, "Content-Type", content_type );
|
|
http_request_send_header( req, "Cache-Control", "public,max-age=3600" );
|
|
http_request_send_header_last_modified( req, last_modified );
|
|
http_request_end_send_headers( req, false );
|
|
|
|
FILE* body = http_request_get_response_body( req );
|
|
size_t res;
|
|
char buffer[4096];
|
|
uint64_t start_time = curr_time();
|
|
/*
|
|
printf( "Starting file transfer %s at %lu.%lu\n",fs_path,
|
|
start_time / 1000000000,
|
|
start_time % 1000000000
|
|
);
|
|
//*/
|
|
while(( res = fread( buffer, 1, sizeof(buffer), f ) )) {
|
|
//printf( "Writing %d bytes at %llu\n", res, curr_time() );
|
|
fwrite( buffer, 1, res, body );
|
|
}
|
|
uint64_t finish_time = curr_time();
|
|
printf( "File transfer %s completed at %lu.%lu (took %lu.%09lus)\n",
|
|
fs_path,
|
|
finish_time / 1000000000,
|
|
finish_time % 1000000000,
|
|
(finish_time - start_time) / 1000000000,
|
|
(finish_time - start_time) % 1000000000
|
|
);
|
|
fflush(body);
|
|
fclose(f);
|
|
|
|
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;
|
|
}
|
|
char* http_request_route_get_dir_or_file( struct http_request* req )
|
|
{
|
|
int len = strlen( req->path );
|
|
for( int i = 0; i < len; ++i ) {
|
|
char ch = req->path[i];
|
|
if( ch == '?' || ch == '/' ) {
|
|
char* res = strndup( req->path, i );
|
|
req->path += ( i + 1 );
|
|
return res;
|
|
}
|
|
}
|
|
|
|
char* res = strdup(req->path);
|
|
req->path += len;
|
|
|
|
return res;
|
|
}
|
|
const char* http_request_route_query_key( struct http_request* req )
|
|
{
|
|
return strtok_r( req->path, "=", &req->path );
|
|
}
|
|
const char* http_request_route_query_value( struct http_request* req )
|
|
{
|
|
if( req->path[0] == '&' ) {
|
|
req->path += 1;
|
|
return "";
|
|
}
|
|
char* result = strtok_r( req->path, "&", &req->path );
|
|
if( !result ) { return ""; }
|
|
|
|
// In-place unescape of %XX codes
|
|
int i;
|
|
char* out = result;
|
|
for( char* iter = result; *iter; ++iter,++out ) {
|
|
if( *iter == '%' ) {
|
|
sscanf(&iter[1],"%02X",&i);
|
|
*out = i;
|
|
iter += 2;
|
|
} else {
|
|
*out = *iter;
|
|
}
|
|
}
|
|
*out = '\0';
|
|
|
|
return result;
|
|
}
|
|
const char* http_request_get_full_path( struct http_request* req )
|
|
{
|
|
return req->full_path;
|
|
}
|
|
const char* http_request_get_remaining_path( struct http_request* req )
|
|
{
|
|
return req->path;
|
|
}
|
|
void http_request_route_eat_path( struct http_request* req, int count )
|
|
{
|
|
int remaining = strlen(req->path);
|
|
if( count > remaining ) {
|
|
count = remaining;
|
|
}
|
|
|
|
req->path = &req->path[count];
|
|
}
|
|
|
|
void http_request_process( struct http_request* req, ucontext_t* fallback )
|
|
{
|
|
if( !req->nonblock.callback( req->nonblock.data ) ) {
|
|
//printf( "Non-block isn't finished for %p -> %s\n", req, req->full_path );
|
|
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;
|
|
if( !req ) {
|
|
func(data);
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
//printf( "Swapping out %p -> %s\n", req, req->full_path );
|
|
if( -1 == swapcontext( &req->context, req->context.uc_link ) ) {
|
|
printf( "Failure during context swap: %s\n", strerror(errno) );
|
|
exit(1);
|
|
}
|
|
//printf( "Swapping in %p -> %s\n", req, req->full_path );
|
|
|
|
// restor the old callback data
|
|
req->nonblock.callback = old_nonblock_callback;
|
|
req->nonblock.data = old_nonblock_data;
|
|
}
|
|
|