Initial commit of server code

master
teknomunk 1 year ago
parent ed63559159
commit f27aadd39c

7
.gitignore vendored

@ -0,0 +1,7 @@
*.o
*.deps
*.inc
src.a
src.bin
debug
release

@ -0,0 +1,13 @@
#!/bin/bash
find src | egrep "\.template$" | while read FILE; do
ruby src/embed.rb "$FILE"
done
#ruby src/embed.rb
ruby src/build.rb --code src
mv src.bin debug
cp debug release
strip release

@ -0,0 +1,20 @@
#include "app_args.h"
#include <stdlib.h>
#include <string.h>
struct app_args* app_args_new( int argc, char** argv )
{
struct app_args* args = (struct app_args*)malloc( sizeof(struct app_args) );
args->port = 9053;
args->addr = strdup("0.0.0.0");
return args;
}
void app_args_release( struct app_args* args )
{
free(args->addr);
free(args);
}

@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
struct app_args
{
uint16_t port;
char* addr;
};
struct app_args* app_args_new( int argc, char** argv );
void app_args_release( struct app_args* args );

@ -0,0 +1,88 @@
#!/usr/bin/ruby
require "digest/sha1"
FLAGS="-g -MP -MD -Os"
LDFLAGS = "-larchive"
#FLAGS="-g -MP -MD"
def cxx( code_base, filename )
hash = Digest::SHA1.hexdigest(filename)[0,8]
dir = File.dirname(filename)
base = "." + File.basename(filename)
basename = File.join(dir, base.gsub(/\.c$/,"") + "-" + hash )
rebuild_needed = $clean
if !File.exists?(obj_file=(basename + ".o"))
rebuild_needed = true
elsif !File.exists?(deps=(basename + ".deps"))
rebuild_needed = true
else
obj_file_mtime = File.mtime(obj_file)
if obj_file_mtime < File.mtime(filename)
rebuild_needed = true
else
File.read(deps).each_line {|line|
line = line.strip
if File.exists?(line)
if File.mtime(line) > obj_file_mtime
#puts "#{line} is forcing rebuild of #{filename}"
rebuild_needed = true
end
end
}
end
end
return unless rebuild_needed
puts "[CC] #{filename} => #{obj_file}"
if !system("gcc -c #{FLAGS} \"#{filename}\" -o \"#{obj_file}\" -Isrc/ -I #{code_base} -I./")
File.unlink( obj_file )
File.unlink( deps )
exit(1)
end
deps = File.read(basename+".d").strip.split("\n").select {|l|
!l.include?("tmp") \
&& !l.include?("include") \
&& l[0] != ' '
}.map {|l|
l[..-2]
}
File.open(basename+".deps","w") {|f|
deps.each {|d| f.puts d }
}
File.unlink(basename+".d")
cmd = "ar r #{code_base}.a \"#{obj_file}\""
#puts cmd
system(cmd)
return true
end
$clean = false
code_base = "src/"
ARGV.each_with_index {|arg,i|
case arg
when "clean"
$clean = true
%x( rm #{code_base}.a )
when "--code"
code_base = ARGV[i+1]
end
}
puts "Compiling #{code_base}"
files = %x( find "#{code_base}" -type f ).strip.split("\n").select {|f| /\.c$/ =~ f }
%x( ar cr #{code_base}.a )
files.each {|filename|
cxx( code_base, filename )
}
system("gcc #{code_base}.a -g -o #{code_base}.bin -rdynamic #{LDFLAGS}")

@ -0,0 +1,26 @@
#include "mastodon_api.h"
#include "http_request.h"
#include <stdio.h>
#include <stdlib.h>
bool route_mastodon_api( struct http_request* req )
{
if( http_request_route( req, "apps" ) ) {
if( http_request_route_method( req, "POST" ) ) {
FILE* data = http_request_get_request_data( req );
printf( "OAuth2 app\n" );
// http_request_get_header( "Content-Length" );
//while( -1 != getline( &line, &size, data ) ) {
char ch;
while( (ch = fgetc(data) ) != EOF ) {
printf( "%c", ch );
fflush(stdout);
}
printf( "End of file\n" );
}
}
return false;
}

@ -0,0 +1,8 @@
#pragma once
#include <stdbool.h>
struct http_request;
bool route_mastodon_api( struct http_request* req );

@ -0,0 +1,113 @@
class Templator
def initialize( filename )
output_filename = filename.gsub(/\.template/,".inc")
if File.exist?(output_filename) && File.mtime(filename) < File.mtime(output_filename)
return
end
puts "[EMBED] #{filename} -> #{output_filename}"
@filename = filename
@i = File.open(filename,"r")
@o = File.open( output_filename, "w" )
@count = 0
@arg_list = []
self.process
end
def output( text )
@o.write(text)
@count += text.size
if @count > 65
@o.write "\"\n\t\t\""
@count = 0
end
end
def process()
@o.puts "#ifndef RENDER"
@o.puts "\t#include \"#{ @filename.gsub(/\.template$/,'.h') }\""
@o.puts "#else"
@o.write "\tfprintf( f, \n\t\t\""
while !@i.eof()
c = @i.read(1)
if c == "%"
c = @i.read(1)
if c == "%"
output "%%"
elsif c == "("
# Logic
@o.write "\"\n"
@arg_list.each {|item|
@o.write "\t\t, #{item.strip}\n"
}
@arg_list = []
@o.write "\t);\n"
levels = 1
while !@i.eof() && levels > 0
c = @i.read(1)
if c == ')'
levels -= 1
elsif c == '('
levels += 1
end
if levels > 0
@o.write c
end
end
if( (c=@i.read(1)) != "\n" )
puts "Expecting endline after %(), got #{c}"
exit
end
@o.write "\n"
@o.write "\tfprintf( f, \""
else
# Variable
output("%")
output(c)
while !@i.eof() && (c=@i.read(1)) != "{"
output(c)
end
argument = ""
while !@i.eof() && (c=@i.read(1)) != "}"
argument += c
end
@arg_list.push(argument)
end
elsif c == "\n"
output( "\\n" )
elsif c == "\""
output( "\\\"" )
elsif c == "\t"
output( "\\t" )
elsif c == "\\"
output( "\\" )
else
output(c)
end
end
# Close out printf
@o.write "\"\n"
@arg_list.each {|item|
@o.write "\t\t, #{item.strip}\n"
}
arg_list = []
@o.write "\t);\n"
@o.puts "#endif"
end
end
#Templator.new("src/view/series.html.template").process
Templator.new(ARGV[0])

@ -0,0 +1,625 @@
#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;
// 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;
do {
n = 0;
getline_stripped( &ptr, &n, f );
length = strlen(ptr);
if( length > 0 ) {
/*
struct header* h = (struct header*)malloc(sizeof(struct header));
h->next = NULL;
int pos = index( ptr, ':' )
*/
printf( "Header: '%s' (%d)\n", ptr, length );
}
free(ptr);
} while( length > 0 );
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;
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 );
}
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 cookie_io_functions_t http_response_functions = {
.read = http_request_read,
.write = http_request_body_write,
.seek = NULL,
.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->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 );
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,463 @@
#include "json.h"
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
enum parser_state
{
ps_object_expect_key = 1,
ps_object_expect_comma_then_key = 2,
ps_object_expect_value = 3,
ps_array_expect_value = 11,
ps_array_expect_comma_then_value = 12,
ps_expect_value = 20,
};
struct json_pull_parser
{
FILE* f;
int curr_state;
};
static void eat_whitespace( FILE* f )
{
for(;;) {
int i = fgetc(f);
if( i == EOF ) {
return;
}
if( !isspace(i) ) {
ungetc(i,f);
return;
}
}
}
static bool json_pull_parser_handle_array_comma( struct json_pull_parser* jpp )
{
eat_whitespace(jpp->f);
switch( jpp->curr_state ) {
case ps_array_expect_comma_then_value:
break;
case ps_object_expect_key:
case ps_object_expect_comma_then_key:
return false;
default:
return true;
}
int i = fgetc(jpp->f);
if( i == ']' ) {
ungetc(i,jpp->f);
return true;
}
if( i != ',' ) {
printf( "expecting comma, got %c(%d)\n", (char)i, i );
return false;
}
eat_whitespace(jpp->f);
return true;
}
static bool finish_value_read( struct json_pull_parser* jpp, bool result )
{
switch( jpp->curr_state ) {
case ps_object_expect_value:
jpp->curr_state = ps_object_expect_comma_then_key;
break;
case ps_array_expect_value:
jpp->curr_state = ps_array_expect_comma_then_value;
break;
}
return result;
}
struct json_pull_parser* json_pull_parser_new( FILE* f )
{
if( !f ) {
return NULL;
}
struct json_pull_parser* jpp = (struct json_pull_parser*)malloc( sizeof( struct json_pull_parser ) );
if( !jpp ) {
return NULL;
}
jpp->f = f;
jpp->curr_state = ps_expect_value;
return jpp;
}
void json_pull_parser_release( struct json_pull_parser* jpp )
{
free(jpp);
}
bool json_pull_parser_begin_object( struct json_pull_parser* jpp, int* save )
{
if( !json_pull_parser_handle_array_comma( jpp ) ) {
return false;
}
*save = jpp->curr_state;
FILE* f = jpp->f;
eat_whitespace(f);
int i = fgetc( f );
if( i != '{' ) {
ungetc( i, jpp->f );
return false;
}
jpp->curr_state = ps_object_expect_key;
return true;
}
bool json_pull_parser_end_object( struct json_pull_parser* jpp, int* save )
{
eat_whitespace( jpp->f );
int i = fgetc( jpp->f );
if( i != '}' ) {
printf( "Missing }, got %c (%d)\n", (char)i, i );
return false;
}
jpp->curr_state = *save;
return finish_value_read( jpp, true );
}
bool json_pull_parser_begin_array( struct json_pull_parser* jpp, int* save )
{
*save = jpp->curr_state;
eat_whitespace(jpp->f);
int i = fgetc(jpp->f);
if( i != '[' ) {
printf( "Unable to start array, i=%c(%d)\n", (char)i, i );
ungetc( i, jpp->f );
return false;
}
jpp->curr_state = ps_array_expect_value;
return true;
}
bool json_pull_parser_end_array( struct json_pull_parser* jpp, int* save )
{
eat_whitespace(jpp->f);
int i = fgetc( jpp->f );
if( i != ']' ) {
ungetc(i,jpp->f);
return false;
}
jpp->curr_state = *save;
return finish_value_read( jpp, true );
}
static char* json_pull_parser_raw_read_string( struct json_pull_parser* jpp )
{
eat_whitespace(jpp->f);
int c = fgetc(jpp->f);
if( c != '"' ) {
ungetc(c,jpp->f);
return NULL;
}
char* result = (char*)malloc(17);
if( !result ) {
return NULL;
}
size_t alloc_size = 17;
size_t length = 0;
result[0] = '\0';
bool done = false;
while(!done) {
c = fgetc(jpp->f);
if( c == EOF ) {
return NULL;
}
char ch;
if( c == '"' ) {
done = true;
break;
} else if( c == '\\' ) {
printf( "TODO: handle string escape sequences\n" );
exit(1);
} else {
ch = (char)c;
}
if( length + 1 >= alloc_size ) {
result = realloc( result, alloc_size + 16 );
alloc_size += 16;
}
result[length] = ch;
result[length+1] = '\0';
length += 1;
}
finish_value_read( jpp, true );
return result;
}
char* json_pull_parser_read_object_key ( struct json_pull_parser* jpp )
{
if( jpp->curr_state == ps_object_expect_comma_then_key ) {
eat_whitespace(jpp->f);
int i = fgetc(jpp->f);
if( i != ',' ) {
ungetc( i, jpp->f );
return NULL;
}
} else if( jpp->curr_state != ps_object_expect_key ) {
return NULL;
}
char* str = json_pull_parser_raw_read_string( jpp );
if( !str ) {
return NULL;
}
eat_whitespace(jpp->f);
int i = fgetc( jpp->f );
if( i != ':' ) {
free(str);
return NULL;
}
jpp->curr_state = ps_object_expect_value;
return str;
}
char* json_pull_parser_read_string( struct json_pull_parser* jpp )
{
if( !json_pull_parser_handle_array_comma( jpp ) ) {
return false;
}
return json_pull_parser_raw_read_string(jpp);
}
enum number_type {
nt_no_result = 0,
nt_integer = 1,
nt_float = 2,
};
int json_pull_parser_read_number( struct json_pull_parser* jpp, int* i, float* f )
{
eat_whitespace(jpp->f);
char buffer[50];
char* pos = &buffer[0];
bool must_be_float = false;
int c = fgetc(jpp->f);
if( c == '-' ) {
*pos = '-';
pos += 1;
c = fgetc(jpp->f);
}
if( c == EOF ) { return nt_no_result; }
if( c == '0' ) {
*pos = c;
pos += 1;
} else if( c >= '1' && c <= '9' ) {
*pos = c;
pos += 1;
for(;;) {
c = fgetc(jpp->f);
if( c == EOF ) { return nt_no_result; }
if( c >= '0' && c <= '9' ) {
*pos = c;
pos += 1;
} else {
break;
}
}
} else {
ungetc(c,jpp->f);
return false;
}
if( c == '.' ) {
// fractional part
for(;;) {
c = fgetc(jpp->f);
if( c == '0' ) {
} else if( c >= '1' && c <= '9' ) {
must_be_float = true;
} else {
break;
}
*pos = c;
++pos;
}
}
if( c == 'e' || c == 'E' ) {
bool negative_exponent = false;
// exponent part
c = fgetc( jpp->f );
if( c == '-' ) {
negative_exponent = true;
c = fgetc(jpp->f);
} else if( c == '+' ) {
c = fgetc(jpp->f);
}
for(;;) {
if( c >= '1' && c <= '9' ) {
if( negative_exponent ) {
must_be_float = true;
}
*pos = c;
pos += 1;
c = fgetc(jpp->f);
} else {
break;
}
}
}
ungetc( c, jpp->f );
int res = nt_no_result;
*pos = '\0';
if( pos == &buffer[0] ) {
return nt_no_result;
}
if( f ) {
char* rem = NULL;
*f = strtof( buffer, &rem );
if( rem != pos ) {
return nt_no_result;
}
if( i && !must_be_float ) {
*i = *f;
res = nt_integer;
goto finish;
}
res = nt_float;
goto finish;
} else if( must_be_float ) {
return nt_no_result;
}
if( i ) {
char* rem = NULL;
*i = strtol( buffer, &rem, 10 );
if( rem != pos ) {
return nt_no_result;
}
res = nt_integer;
goto finish;
}
return nt_no_result;
finish:
return finish_value_read( jpp, res );
}
bool json_pull_parser_read_int( struct json_pull_parser* jpp, int* i )
{
return json_pull_parser_read_number(jpp,i,NULL) == nt_integer;
}
bool json_pull_parser_read_float( struct json_pull_parser* jpp, float* f )
{
return json_pull_parser_read_number(jpp,NULL,f) == nt_float;
}
bool json_pull_parser_read_null( struct json_pull_parser* jpp )
{
return false;
}
bool json_pull_parser_read_value( struct json_pull_parser* jpp )
{
if( json_pull_parser_read_null(jpp) ) {
return true;
}
if( !json_pull_parser_handle_array_comma( jpp ) ) {
return false;
}
int i;
float f;
if( json_pull_parser_read_number(jpp,&i,&f) ) {
return true;
}
char* s;
if( s = json_pull_parser_read_string(jpp) ) {
free(s);
return true;
}
if( json_pull_parser_begin_object(jpp,&i) ) {
while( s = json_pull_parser_read_object_key(jpp) ) {
free(s);
if( !json_pull_parser_read_value(jpp) ) {
return false;
}
}
return json_pull_parser_end_object(jpp,&i);
}
if( json_pull_parser_begin_array(jpp,&i) ) {
for(;;) {
if( json_pull_parser_end_array(jpp,&i) ) {
return true;
}
if( !json_pull_parser_read_value(jpp) ) {
return false;
}
}
return true;
}
return false;
}
void json_pull_parser_debug_dump( struct json_pull_parser* jpp )
{
printf( "state = %d\n", jpp->curr_state );
FILE* f = jpp->f;
for(;;) {
int c = fgetc(f);
if( c == EOF ) {
return;
}
printf( "%c", (char)c );
}
}

@ -0,0 +1,26 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
struct json_pull_parser;
struct json_pull_parser* json_pull_parser_new( FILE* f );
void json_pull_parser_release ( struct json_pull_parser* jpp );
bool json_pull_parser_begin_object ( struct json_pull_parser* jpp, int* save );
bool json_pull_parser_end_object ( struct json_pull_parser* jpp, int* save );
bool json_pull_parser_begin_array ( struct json_pull_parser* jpp, int* save );
bool json_pull_parser_end_array ( struct json_pull_parser* jpp, int* save );
char* json_pull_parser_read_object_key ( struct json_pull_parser* jpp );
char* json_pull_parser_read_string ( struct json_pull_parser* jpp );
bool json_pull_parser_read_int ( struct json_pull_parser* jpp, int* i );
bool json_pull_parser_read_float ( struct json_pull_parser* jpp, float* i );
bool json_pull_parser_read_null ( struct json_pull_parser* jpp );
bool json_pull_parser_read_value ( struct json_pull_parser* jpp );
void json_pull_parser_debug_dump ( struct json_pull_parser* jpp );

@ -0,0 +1,26 @@
#include "json.h"
#include "vector.h"
#include "json_vector.h"
bool vector_append_from_json( struct json_pull_parser* jpp, struct vector_vtable* vtable, bool (*T_new_from_json)( struct json_pull_parser*, void*, void*),
struct vector* v, void* extra )
{
int save = 0;
if( !json_pull_parser_begin_array( jpp, &save ) ) {
return false;
}
char tmp[ vtable->T_sizeof() ];
for(;;) {
if( json_pull_parser_end_array(jpp,&save) ) {
return true;
}
if( !T_new_from_json( jpp, extra, tmp ) ) {
printf( "Unable to read T from json\n" );
return false;
}
vector_push( v, vtable, tmp );
}
}

@ -0,0 +1,11 @@
#pragma once
#include <stdbool.h>
struct json_pull_parser;
struct vector_vtable;
struct vector;
bool vector_append_from_json( struct json_pull_parser* jpp,
struct vector_vtable* vtable, bool (*T_new_from_json)( struct json_pull_parser*, void*, void*), struct vector* v, void* extra
);

@ -0,0 +1,97 @@
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdbool.h>
#include <unistd.h>
#include <signal.h>
#include "app_args.h"
#include "http_server.h"
#include "http_request.h"
#include "controller/mastodon_api.h"
bool terminate = false;
void handle_ctrl_c(int)
{
terminate = true;
}
bool route_asset( struct http_request* req )
{
static struct allowed_t {
const char* route_path;
const char* fs_path;
const char* mime_type;
} allowed[] = {
{ "style.css", "src/assets/style.css", "text/css" },
{ "jquery.js", "src/assets/jquery.js", "application/javascript" },
{ "jquery.plugins.js", "src/assets/jquery.plugins.js", "application/javascript" },
{ "background_noise.png","src/assets/background_noise.png", "image/png" },
{ "feed-icon-28x28.png", "src/assets/feed-icon-28x28.png", "image/png" },
};
for( int i = 0; i < sizeof(allowed)/sizeof(struct allowed_t); ++i ) {
if( http_request_route_term( req, allowed[i].route_path ) ) {
return http_request_send_file( req, allowed[i].fs_path, allowed[i].mime_type );
}
}
return false;
}
bool route_request( struct http_request* req )
{
if( http_request_route( req, "/api/v1/" ) ) {
return route_mastodon_api( req );
}
if( http_request_route( req, "/.well-known" ) ) {
return false;
}
if( http_request_route( req, "/assets/" ) ) {
printf( "handling asset\n" );
return route_asset(req);
}
return false;
}
void handle_request( struct http_request* req, void* )
{
printf( "Handling request from %s\n", http_request_get_remote_host_address( req ) );
if( !route_request( req ) ) {
http_request_send_headers( req, 404, "text/html", true );
}
}
int main( int argc, char* argv[] )
{
signal(SIGINT, handle_ctrl_c);
signal(SIGPIPE, SIG_IGN);
struct app_args* args = app_args_new( argc, argv );
if( !args ) {
printf( "Error processing argument\n" );
return 1;
}
struct http_server* srv = http_server_new( args, handle_request, NULL );
if( !srv ) {
printf( "Error setting up server\n" );
app_args_release(args);
return 1;
}
while(!terminate) {
http_server_process( srv );
usleep(1);
}
http_server_release( srv );
app_args_release(args);
return 0;
}

@ -0,0 +1,2 @@
#pragma once

@ -0,0 +1,86 @@
#include "vector.h"
#include <stdlib.h>
#include <stdio.h>
struct vector* vector_new( struct vector_vtable* vtable )
{
struct vector* v = (struct vector*)malloc( sizeof(struct vector) );
if( !v ) {
return NULL;
}
v->size = 0;
v->data = vtable->slice_T_new( 16 );
return v;
}
void vector_release( struct vector* v, struct vector_vtable* vtable )
{
vtable->slice_T_release( v->data );
free(v);
}
void vector_push( struct vector* v, struct vector_vtable* vtable, void* item )
{
if( v->size + 1 > v->data.size ) {
struct slice new_data = vtable->slice_T_new( v->data.size + 16 );
char tmp[ vtable->T_sizeof() ];
for( int i = 0; i < v->size; ++i ) {
vtable->slice_T_get_index( v->data, i, tmp );
vtable->slice_T_set_index( new_data, i, tmp );
}
vtable->slice_T_release( v->data );
v->data = new_data;
}
vtable->slice_T_set_index( v->data, v->size, item );
v->size += 1;
}
uint32_t vector_size( struct vector* v, struct vector_vtable* vtable )
{
if( !v ) {
return 0;
}
return v->size;
}
void vector_get_index_unsafe( struct vector* v, struct vector_vtable* vtable, uint32_t idx, void* item )
{
vtable->slice_T_get_index( v->data, idx, item );
}
void vector_bubble_sort( struct vector* v, struct vector_vtable* vtable, bool (*compare)( void* a, void* b ) )
{
uint32_t size = vector_size( v, vtable );
uint32_t T_sizeof = vtable->T_sizeof();
char a[T_sizeof];
char b[T_sizeof];
while( size > 1 ) {
bool any_swaps = false;
for( int j = 0; j < size - 1; ++j ) {
vtable->slice_T_get_index( v->data, j, a );
vtable->slice_T_get_index( v->data, j+1, b );
if( !compare(a,b) ) {
any_swaps = true;
vtable->slice_T_set_index( v->data, j, b );
vtable->slice_T_set_index( v->data, j+1, a );
}
}
// Short circuit
if( !any_swaps ) {
return;
}
size -= 1;
}
}

@ -0,0 +1,90 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
struct slice
{
uint32_t size;
char* data;
};
struct vector_vtable
{
struct slice (*slice_T_new)( uint32_t size );
void (*slice_T_release)( struct slice s );
void (*slice_T_set_index)( struct slice s, uint32_t idx, void* ptr );
void (*slice_T_get_index)( struct slice s, uint32_t idx, void* ptr );
uint32_t (*T_sizeof)();
bool (*T_operator_equal)( void* a, void* b );
};
struct vector
{
uint32_t size;
struct slice data;
};
struct vector* vector_new( struct vector_vtable* vtable );
void vector_release( struct vector* v, struct vector_vtable* vtable );
void vector_push( struct vector* v, struct vector_vtable* vtable, void* item );
uint32_t vector_size( struct vector* v, struct vector_vtable* vtable );
void vector_get_index_unsafe( struct vector* v, struct vector_vtable* vtable, uint32_t idx, void* item );
// Comparison sort functions
// All these functions take a comparitor function that should return true if 'a' should appear after 'b' in the list
void vector_bubble_sort( struct vector* v, struct vector_vtable* vtable, bool (*compare)( void* a, void* b ) );
// Sorted Insertion functions
//void vector_insert_sorted( struct vector* v, struct vector_vtable* vtable, bool (*compare)( void* a, void* b ) );
#define _PASTE( a, b ) _PASTE2( a, b )
#define _PASTE2( a, b ) a ## b
#define VECTOR_FOREACH_WITH_INDEX( vec, vtable, item_type, item_name, index, ignore ) \
uint32_t _PASTE(limit,__LINE__) = vector_size( vec, vtable );\
for( int index = 0; index < _PASTE(limit,__LINE__); ++index ) { \
item_type item_name = 0; \
vector_get_index_unsafe( vec, vtable, index, &item_name );
#define VECTOR_FOREACH( vec, vtable, item_type, item_name, ignore ) \
VECTOR_FOREACH_WITH_INDEX( vec, vtable, item_type, item_name, _PASTE(i,__LINE__), ignore )
#define VECTOR_VTABLE( T, type ) \
static struct slice slice_ ## T ## _new( uint32_t size ) \
{ \
struct slice res = { \
.size = size, \
.data = malloc(sizeof(type) * size), \
}; \
return res; \
} \
static void slice_ ## T ## _release( struct slice s ) \
{ \
free(s.data); \
} \
static void slice_ ## T ## _set_index( struct slice s, uint32_t index, void* ptr ) \
{ \
type* data = (type*)s.data; \
data[index] = *(type*)ptr; \
} \
static void slice_ ## T ## _get_index( struct slice s, uint32_t index, void* ptr ) \
{ \
type* data = (type*)s.data; \
*(type*)ptr = data[index]; \
} \
static uint32_t T ## _sizeof() \
{ \
return sizeof(struct chapter*); \
} \
struct vector_vtable vector_ ## T ## _vtable = { \
.slice_T_new = slice_## T ##_new, \
.slice_T_release = slice_## T ##_release, \
.slice_T_set_index = slice_## T ## _set_index, \
.slice_T_get_index = slice_## T ## _get_index, \
.T_sizeof = T ## _sizeof, \
.T_operator_equal = T ##_operator_equal, \
}

@ -0,0 +1,4 @@
#pragma once
#define SERVER_VERSION "0.1"

@ -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