parent
ed63559159
commit
f27aadd39c
@ -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…
Reference in new issue