mirror of
https://github.com/sigmasternchen/Serwer
synced 2025-03-15 07:08:54 +00:00
first working version
This commit is contained in:
parent
73daacc816
commit
7caf5c2c64
6 changed files with 1123 additions and 0 deletions
30
Makefile
Normal file
30
Makefile
Normal file
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# Makefile for Serwer
|
||||
#
|
||||
# Author: overflowerror
|
||||
#
|
||||
#
|
||||
|
||||
CC = gcc
|
||||
DEFS = -D_XOPEN_SOURCE=500 -D_BSD_SOURCE
|
||||
CFLAGS = -Wall -g -std=c99 -pedantic -DENDEBUG $(DEFS)
|
||||
LDFLAGS = $(DEFS)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: test
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
test: main.o webserver.o help.o
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
|
||||
main.o: main.c webserver.h
|
||||
|
||||
help.o: help.c help.h
|
||||
|
||||
webserver.o: webserver.c webserver.h help.h
|
||||
|
||||
clean:
|
||||
rm -f *.o test
|
80
help.c
Normal file
80
help.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @file help.c
|
||||
* @author overflowerror
|
||||
* @date 2016.11.27
|
||||
* @brief code for the help functions
|
||||
*
|
||||
* This file contains the code for this lib.
|
||||
*/
|
||||
|
||||
#include "help.h"
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
|
||||
/**
|
||||
* @see help.h
|
||||
*/
|
||||
const char* progname = "";
|
||||
|
||||
/**
|
||||
* @brief the free function
|
||||
*
|
||||
* This function gets executes if bail_out() or usage() are called.
|
||||
*/
|
||||
static free_t freeFunction = NULL;
|
||||
|
||||
void help_init(free_t function, const char* name) {
|
||||
freeFunction = function;
|
||||
progname = name;
|
||||
}
|
||||
|
||||
void bail_out(int exitcode, const char* module) {
|
||||
|
||||
if (module != NULL) {
|
||||
// output is not split into smaller quantities to
|
||||
// ensure error messages of child and parent
|
||||
// don't get mixed
|
||||
|
||||
if (errno == 0) {
|
||||
(void) fprintf(stderr, "%s: %s\n", progname, module);
|
||||
} else {
|
||||
(void) fprintf(stderr, "%s: %s: %s\n", progname, module, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
if (freeFunction != NULL)
|
||||
(*freeFunction)();
|
||||
exit(exitcode);
|
||||
}
|
||||
|
||||
void usage(const char* arguments, const char* description, const char* error, int exitcode) {
|
||||
if (error != NULL) {
|
||||
(void) fprintf(stderr, "%s\n\n", error);
|
||||
}
|
||||
(void) fprintf(stderr, "Usage: %s %s\n", progname, arguments);
|
||||
if (description != NULL) {
|
||||
(void) fprintf(stderr, "\n%s\n", description);
|
||||
}
|
||||
|
||||
if (freeFunction != NULL)
|
||||
(*freeFunction)();
|
||||
|
||||
exit(exitcode);
|
||||
}
|
||||
|
||||
void setup_signal_handler(const int signal, sighandler_t handler) {
|
||||
struct sigaction s;
|
||||
|
||||
s.sa_handler = handler;
|
||||
s.sa_flags = 0;
|
||||
if(sigfillset(&s.sa_mask) < 0) {
|
||||
bail_out(EXIT_FAILURE, "sigfillset");
|
||||
}
|
||||
if (sigaction(signal, &s, NULL) < 0) {
|
||||
bail_out(EXIT_FAILURE, "sigaction");
|
||||
}
|
||||
}
|
88
help.h
Normal file
88
help.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* @file help.h
|
||||
* @author overflowerror
|
||||
* @date 2016.11.27
|
||||
* @brief header file for the help functions
|
||||
*
|
||||
* This file contains all definitions, constants, macros and prototypes for this lib.
|
||||
*/
|
||||
|
||||
#ifndef HELP_H
|
||||
#define HELP_H
|
||||
|
||||
/**
|
||||
* @brief count array entries
|
||||
*
|
||||
* This macro counts the entries in an array.
|
||||
*/
|
||||
#define COUNT(a) (sizeof(a)/sizeof(a[0]))
|
||||
|
||||
/**
|
||||
* @brief a signalhandler
|
||||
*
|
||||
* This is a type representing a signal handler.
|
||||
*
|
||||
* Appearently on my system signal.h doesn't define such a type.
|
||||
*/
|
||||
typedef void(*sighandler_t)(int);
|
||||
|
||||
/**
|
||||
* @brief a free-function
|
||||
*
|
||||
* This is a type representing a free-function, used to free resources on error.
|
||||
*/
|
||||
typedef void(*free_t)(void);
|
||||
|
||||
/**
|
||||
* @brief the program name
|
||||
*
|
||||
* The name of the progname, should initialized with help_init().
|
||||
*/
|
||||
extern const char* progname;
|
||||
|
||||
/**
|
||||
* @brief init this lib
|
||||
* @param function the free-function; can be NULL
|
||||
* @param name the name of the program
|
||||
*
|
||||
* Sets the given free-function and the program name.
|
||||
*/
|
||||
void help_init(free_t, const char*);
|
||||
|
||||
/**
|
||||
* @brief bail out from the program flow
|
||||
* @param exitcode the code to exit with
|
||||
* @param module the name of the current module; can be NULL
|
||||
*
|
||||
* This function prints (to stderr) an error message (if the module name is not NULL),
|
||||
* prints strerror() (if errno is not 0), executes the free-function (if set),
|
||||
* and exits the program with the given exit code.
|
||||
*/
|
||||
void bail_out(int, const char*);
|
||||
|
||||
/**
|
||||
* @brief displays a usage message
|
||||
* @param arguments the string to display after the program name
|
||||
* @param description a description of the arguments; can be NULL
|
||||
* @param error a error message for a synopsis violation; can be NULL
|
||||
* @param exitcode the code to exit with
|
||||
*
|
||||
* Prints to sterr:
|
||||
* - the error message (if given)
|
||||
* - the program name
|
||||
* - the arguments string
|
||||
* - the argument description (if given)
|
||||
* Then it exits with the given exit code.
|
||||
*/
|
||||
void usage(const char*, const char*, const char*, int);
|
||||
|
||||
/**
|
||||
* @brief sets up a signal handler
|
||||
* @param signal the signal to set the handler up for
|
||||
* @param handler the signal handler
|
||||
*
|
||||
* This function sets up a signal handler for the given signal.
|
||||
*/
|
||||
void setup_signal_handler(const int, sighandler_t );
|
||||
|
||||
#endif
|
54
main.c
Normal file
54
main.c
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include "webserver.h"
|
||||
#include "help.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int hello_world(method_t method, const char* host, const char* path, headers_t requestHeaders,
|
||||
headers_t* responseHeaders, stream_t request, stream_t response) {
|
||||
|
||||
fprintf(response, "Hello World!\n");
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int test(method_t method, const char* host, const char* path, headers_t requestHeaders,
|
||||
headers_t* responseHeaders, stream_t request, stream_t response) {
|
||||
|
||||
fprintf(response, "Method: %s, URI: %s%s\n", ws_strm(method), host, path);
|
||||
fprintf(response, "Request headers:\n");
|
||||
for (int i = 0; i < requestHeaders.nrfields; i++) {
|
||||
header_t header = requestHeaders.fields[i];
|
||||
fprintf(response, " %s - %s\n", header.key, header.value);
|
||||
}
|
||||
fprintf(response, "\n");
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
help_init(NULL, "test");
|
||||
|
||||
handle_t hello_handle = {
|
||||
.host = NULL,
|
||||
.path = "/world",
|
||||
.handler = &hello_world
|
||||
};
|
||||
handle_t test_handle = {
|
||||
.host = NULL,
|
||||
.path = "/test",
|
||||
.handler = &test
|
||||
};
|
||||
srvoptions_t options = {
|
||||
.mode = LINEAR,
|
||||
.timeout = 30,
|
||||
.maxconnections = 5,
|
||||
.loglevel = LOG_DEBUG
|
||||
};
|
||||
|
||||
webserver_t server = ws_create("test_server", NULL, "8080", stderr, options);
|
||||
|
||||
ws_handle_add(&server, hello_handle);
|
||||
ws_handle_add(&server, test_handle);
|
||||
|
||||
ws_run(&server);
|
||||
}
|
756
webserver.c
Normal file
756
webserver.c
Normal file
|
@ -0,0 +1,756 @@
|
|||
#include "webserver.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <stdlib.h>
|
||||
#include "help.h"
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
bool ws_host_match(const char* host, const char* match) {
|
||||
// only two possible positions for wildcard: begin and end
|
||||
int lenh = strlen(host);
|
||||
int lenm = strlen(match);
|
||||
|
||||
if (lenm > lenh)
|
||||
return false;
|
||||
|
||||
bool wildcard = false;
|
||||
int wnext = -1;
|
||||
int found = -1;
|
||||
|
||||
int h = 0, m = 0;
|
||||
for (; m < lenm && h < lenh; m++, h++) {
|
||||
if (match[m] == '*') {
|
||||
wildcard = true;
|
||||
wnext = m + 1;
|
||||
found = -1;
|
||||
// current host char matches automatically
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wildcard) {
|
||||
if (found == -1) {
|
||||
if (match[m] != host[h])
|
||||
m--;
|
||||
else
|
||||
found = h;
|
||||
} else {
|
||||
if (match[m] != host[h]) {
|
||||
// found was negated; reset cursor positions
|
||||
m = wnext;
|
||||
h = found + 1;
|
||||
found = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (match[m] != host[h])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// if not whole match string matches, return false
|
||||
if (m != lenm)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ws_path_match(const char* path, const char* match) {
|
||||
// TODO wildcards
|
||||
int lenp = strlen(path);
|
||||
int lenm = strlen(match);
|
||||
|
||||
if (lenm > lenp)
|
||||
return false;
|
||||
|
||||
bool wildcard = false;
|
||||
int wnext = -1;
|
||||
int found = -1;
|
||||
|
||||
int p = 0, m = 0;
|
||||
for (; m < lenm && p < lenp; m++, p++) {
|
||||
if (match[m] == '*') {
|
||||
wildcard = true;
|
||||
wnext = m + 1;
|
||||
found = -1;
|
||||
// current host char matches automatically
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wildcard) {
|
||||
if (found < 0) {
|
||||
if (match[m] != path[p])
|
||||
m--;
|
||||
else
|
||||
found = p;
|
||||
} else {
|
||||
if (match[m] != path[p]) {
|
||||
// found was negated; reset cursor positions
|
||||
m = wnext;
|
||||
p = found + 1;
|
||||
found = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// if directory end
|
||||
if (path[p] == '/') {
|
||||
// found is here only true if match[m] = path[p] = '/'
|
||||
if (found >= 0) {
|
||||
wildcard = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (match[m] != path[p])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if not whole match string used, return false
|
||||
if (m != lenm)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
handler_t ws_handler_find(webserver_t* server, const char* path, const char* host) {
|
||||
for (int i = 0; i < server->nrhandles; i++) {
|
||||
handle_t handle = server->handles[i];
|
||||
|
||||
if (handle.host != NULL) {
|
||||
if (!ws_host_match(host, handle.host))
|
||||
continue;
|
||||
}
|
||||
if (handle.path != NULL) {
|
||||
if (!ws_path_match(path, handle.path))
|
||||
continue;
|
||||
}
|
||||
return handle.handler;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* ws_host_find(const char** path, headers_t headers) {
|
||||
char* host = NULL;
|
||||
for (int i = 0; i < headers.nrfields; i++) {
|
||||
header_t header = headers.fields[i];
|
||||
if (strcmp(header.key, "Host") == 0) {
|
||||
host = (char*) malloc(strlen(header.value) + 1);
|
||||
memcpy(host, header.value, strlen(header.value) + 1);
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(*path) <= strlen("http://"))
|
||||
return host;
|
||||
if ((*path)[0] == '/')
|
||||
return host;
|
||||
|
||||
bool setHost = false;
|
||||
|
||||
if (host == NULL) {
|
||||
setHost = true;
|
||||
host = (char*) malloc(strlen(*path) + 1);
|
||||
}
|
||||
int hposition = 0;
|
||||
|
||||
int nrslash = 0;
|
||||
|
||||
for (int i = 0; i < strlen(*path); i++) {
|
||||
if (nrslash < 2) {
|
||||
// http://
|
||||
} else if (nrslash < 3) {
|
||||
if (setHost) {
|
||||
host[hposition++] = (*path)[i];
|
||||
/*if ((*path)[i] == ':') {
|
||||
host[hposition - 1] = '\0';
|
||||
setHost = false;
|
||||
}*/
|
||||
}
|
||||
} else {
|
||||
// rebase path
|
||||
*path = *path + i;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((*path)[i] == '/')
|
||||
nrslash++;
|
||||
}
|
||||
if (setHost) {
|
||||
host[hposition] = '\0';
|
||||
host = (char*) realloc(host, strlen(host) + 1);
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
const char* ws_strm(method_t method) {
|
||||
switch(method) {
|
||||
case OPTIONS:
|
||||
return "OPTIONS";
|
||||
case GET:
|
||||
return "GET";
|
||||
case HEAD:
|
||||
return "HEAD";
|
||||
case POST:
|
||||
return "POST";
|
||||
case PUT:
|
||||
return "PUT";
|
||||
case DELETE:
|
||||
return "DELETE";
|
||||
case TRACE:
|
||||
return "TRACE";
|
||||
case CONNECT:
|
||||
return "CONNECT";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int ws_request_parse(char* buffer, const char** path, method_t* method) {
|
||||
char lbuffer[8];
|
||||
int position = 0;
|
||||
|
||||
int state = 0;
|
||||
|
||||
for(int i = 0; i < strlen(buffer); i++) {
|
||||
switch(state) {
|
||||
case 0: // method
|
||||
if (buffer[i] != ' ') {
|
||||
lbuffer[position++] = buffer[i];
|
||||
if (position > 7)
|
||||
return -1; // method name too long
|
||||
} else {
|
||||
state++;
|
||||
lbuffer[position] = '\0';
|
||||
if (strcmp(lbuffer, "OPTIONS") == 0)
|
||||
*method = OPTIONS;
|
||||
else if (strcmp(lbuffer, "GET") == 0)
|
||||
*method = GET;
|
||||
else if (strcmp(lbuffer, "HEAD") == 0)
|
||||
*method = HEAD;
|
||||
else if (strcmp(lbuffer, "POST") == 0)
|
||||
*method = POST;
|
||||
else if (strcmp(lbuffer, "PUT") == 0)
|
||||
*method = PUT;
|
||||
else if (strcmp(lbuffer, "DELETE") == 0)
|
||||
*method = DELETE;
|
||||
else if (strcmp(lbuffer, "TRACE") == 0)
|
||||
*method = TRACE;
|
||||
else if (strcmp(lbuffer, "CONNECT") == 0)
|
||||
*method = CONNECT;
|
||||
else
|
||||
return -2; // unknown method
|
||||
}
|
||||
break;
|
||||
case 1: // path begin
|
||||
if (buffer[i] == ' ' || buffer[i] == '\r')
|
||||
return -3; // malformed;
|
||||
*path = buffer + i;
|
||||
state++;
|
||||
break;
|
||||
case 2: // path
|
||||
if (buffer[i] == ' ') {
|
||||
buffer[i] = '\0';
|
||||
position = 0;
|
||||
state++;
|
||||
}
|
||||
break;
|
||||
case 3: // version
|
||||
if (buffer[i] != '\r') {
|
||||
lbuffer[position++] = buffer[i];
|
||||
if (position > 7)
|
||||
return -4; // version too long
|
||||
} else {
|
||||
lbuffer[position] = '\0';
|
||||
|
||||
// we just support HTTP 1.0 and 1.1
|
||||
if (!strcmp(lbuffer, "HTTP/1.0"))
|
||||
return 0;
|
||||
if (!strcmp(lbuffer, "HTTP/1.1"))
|
||||
return 0;
|
||||
|
||||
return -5;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
headers_t ws_headers_create(void) {
|
||||
headers_t headers;
|
||||
headers.fields = (header_t*) malloc(0 * sizeof(header_t));
|
||||
headers.nrfields = 0;
|
||||
return headers;
|
||||
}
|
||||
|
||||
void ws_headers_add(headers_t* headers, const char* key, const char* value) {
|
||||
headers->fields = (header_t*) realloc(headers->fields, ++(headers->nrfields) * sizeof(header_t));
|
||||
headers->fields[headers->nrfields - 1].key = key;
|
||||
headers->fields[headers->nrfields - 1].value = value;
|
||||
}
|
||||
|
||||
void ws_headers_convert(headers_t* headers, char* line) {
|
||||
const char* key = line;
|
||||
const char* value = NULL;
|
||||
bool foundSeperator = false;
|
||||
for (int i = 0; line[i] != '\0'; i++) {
|
||||
if (foundSeperator) {
|
||||
if (value == NULL) {
|
||||
if (line[i] != ' ') {
|
||||
value = line + i;
|
||||
}
|
||||
} else {
|
||||
if (line[i] == '\r' || line[i] == '\n') {
|
||||
line[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (line[i] == ':') {
|
||||
line[i] = '\0';
|
||||
foundSeperator = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value != NULL)
|
||||
ws_headers_add(headers, key, value);
|
||||
}
|
||||
|
||||
void ws_headers_free(headers_t* headers) {
|
||||
free(headers->fields);
|
||||
headers->fields = NULL;
|
||||
headers->nrfields = 0;
|
||||
}
|
||||
|
||||
int ws_listen(webserver_t* server) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *result, *rp;
|
||||
int s;
|
||||
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
s = getaddrinfo(server->host, server->port, &hints, &result);
|
||||
if (s != 0) {
|
||||
fprintf(stderr, "%s: ws_bind: getaddrinfo: %s\n", progname, gai_strerror(s));
|
||||
bail_out(EXIT_FAILURE, NULL);
|
||||
}
|
||||
|
||||
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||
server->sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (server->sfd == -1)
|
||||
continue;
|
||||
|
||||
const int on = 1;
|
||||
setsockopt(server->sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
||||
if (bind(server->sfd, rp->ai_addr, rp->ai_addrlen) == 0)
|
||||
break; // Success
|
||||
close(server->sfd);
|
||||
}
|
||||
|
||||
if (rp == NULL)
|
||||
return -1;
|
||||
|
||||
if (listen(server->sfd, server->options.maxconnections) < 0)
|
||||
return -1;
|
||||
|
||||
freeaddrinfo(result);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ws_handle_add(webserver_t* server, handle_t handle) {
|
||||
server->handles = (handle_t*) realloc(server->handles, ++(server->nrhandles) * sizeof(handle_t));
|
||||
server->handles[server->nrhandles - 1] = handle;
|
||||
}
|
||||
|
||||
webserver_t ws_create(const char* name, const char* host, const char* port, FILE* logfile, srvoptions_t options) {
|
||||
webserver_t server;
|
||||
server.name = name;
|
||||
server.host = host;
|
||||
server.port = port;
|
||||
server.logfile = logfile;
|
||||
server.nrhandles = 0;
|
||||
server.handles = (handle_t*) malloc(0);
|
||||
server.options.mode = LINEAR;
|
||||
server.options.timeout = 30;
|
||||
|
||||
server.options = options;
|
||||
|
||||
if (ws_listen(&server) < 0)
|
||||
bail_out(EXIT_FAILURE, "ws_listen");
|
||||
return server;
|
||||
}
|
||||
|
||||
void ws_log(webserver_t* server, loglevel_t loglevel, const char* format, ...) {
|
||||
if (loglevel > server->options.loglevel)
|
||||
return;
|
||||
|
||||
if (server->logfile == NULL)
|
||||
return;
|
||||
|
||||
time_t rawtime;
|
||||
struct tm* timeinfo;
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
|
||||
char buffer[26];
|
||||
strftime(buffer, 26, "%Y.%m.%d-%H:%M:%S", timeinfo);
|
||||
|
||||
va_list arg;
|
||||
|
||||
fprintf(server->logfile, "%s: %s: ", buffer, server->name);
|
||||
|
||||
va_start(arg, format);
|
||||
vfprintf(server->logfile, format, arg);
|
||||
va_end(arg);
|
||||
fprintf(server->logfile, "\n");
|
||||
}
|
||||
|
||||
const char* ws_code_reason(int code) {
|
||||
switch(code) {
|
||||
case 100:
|
||||
return "Continue";
|
||||
case 101:
|
||||
return "Switching Protocols";
|
||||
case 200:
|
||||
return "OK";
|
||||
|
||||
case 201:
|
||||
return "Created";
|
||||
case 202:
|
||||
return "Accepted";
|
||||
case 203:
|
||||
return "Non-Authoritative Information";
|
||||
case 204:
|
||||
return "No Content";
|
||||
case 205:
|
||||
return "Reset Content";
|
||||
case 206:
|
||||
return "Partial Content";
|
||||
|
||||
case 300:
|
||||
return "Multible Choices";
|
||||
case 301:
|
||||
return "Moved Permanently";
|
||||
case 302:
|
||||
return "Found";
|
||||
case 303:
|
||||
return "See Other";
|
||||
case 304:
|
||||
return "Not Modified";
|
||||
case 305:
|
||||
return "Use Proxy";
|
||||
case 307:
|
||||
return "Temporary Redirect";
|
||||
|
||||
case 400:
|
||||
return "Bad Request";
|
||||
case 401:
|
||||
return "Unauthorized";
|
||||
case 402:
|
||||
return "Payment Required";
|
||||
case 403:
|
||||
return "Forbidden";
|
||||
case 404:
|
||||
return "Not Found";
|
||||
case 405:
|
||||
return "Method Not Allowed";
|
||||
case 406:
|
||||
return "Not Acceptable";
|
||||
case 407:
|
||||
return "Proxy Authentication Required";
|
||||
case 408:
|
||||
return "Request Time-out";
|
||||
case 409:
|
||||
return "Conflict";
|
||||
case 410:
|
||||
return "Gone";
|
||||
case 411:
|
||||
return "Length Required";
|
||||
case 412:
|
||||
return "Precondition Failed";
|
||||
case 413:
|
||||
return "Request Entify Too Large";
|
||||
case 414:
|
||||
return "Request-URI Too Large";
|
||||
case 415:
|
||||
return "Unsupported Media Type";
|
||||
case 416:
|
||||
return "Requested range not satisfiable";
|
||||
case 417:
|
||||
return "Expectation Failed";
|
||||
|
||||
case 500:
|
||||
return "Internal Server Error";
|
||||
case 501:
|
||||
return "Not Implemented";
|
||||
case 502:
|
||||
return "Bad Gateway";
|
||||
case 503:
|
||||
return "Service Unavailable";
|
||||
case 504:
|
||||
return "Gateway Time-out";
|
||||
case 505:
|
||||
return "HTTP Version not supported";
|
||||
default:
|
||||
return "Unknown extension code";
|
||||
}
|
||||
}
|
||||
|
||||
void ws_send(int connfd, int code, headers_t headers, int pipefd) {
|
||||
stream_t connection = (stream_t) fdopen(connfd, "a+");
|
||||
|
||||
setbuf(connection, NULL);
|
||||
|
||||
fprintf(connection, "%s %i %s\r\n", WS_HTTP_VERSION, code, ws_code_reason(code));
|
||||
|
||||
bool serverHeader = false;
|
||||
|
||||
for (int i = 0; i < headers.nrfields; i++) {
|
||||
header_t header = headers.fields[i];
|
||||
if (strcmp(header.key, "Server") == 0)
|
||||
serverHeader = true;
|
||||
fprintf(connection, "%s: %s\r\n", header.key, header.value);
|
||||
}
|
||||
if (!serverHeader)
|
||||
fprintf(connection, "Server: %s %s\r\n", WS_NAME, WS_VERSION);
|
||||
|
||||
fprintf(connection, "\r\n");
|
||||
|
||||
fflush(connection);
|
||||
|
||||
if (pipefd == -1) {
|
||||
fclose(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
int bs = 0;
|
||||
while ((bs = read(pipefd, buffer, BUFFER_SIZE)) > 0)
|
||||
write(connfd, buffer, bs);
|
||||
|
||||
fclose(connection);
|
||||
}
|
||||
|
||||
void ws_simple_status(int connfd, int code) {
|
||||
ws_send(connfd, code, (headers_t) {.fields = NULL, .nrfields = 0}, -1);
|
||||
}
|
||||
|
||||
int ws_run_linear(webserver_t* server) {
|
||||
struct sockaddr_storage peer_addr;
|
||||
socklen_t peer_addr_len;
|
||||
int s;
|
||||
int connfd;
|
||||
|
||||
char* buffer = (char*) malloc(BUFFER_SIZE * sizeof(char));
|
||||
int buffersize = 0;
|
||||
int nb = 1;
|
||||
|
||||
char* header = (char*) malloc(BUFFER_SIZE * sizeof(char));
|
||||
int headersize = 0;
|
||||
int nhb = 1;
|
||||
|
||||
int headerlines = 0;
|
||||
|
||||
while (true) {
|
||||
if((connfd = accept(server->sfd, (struct sockaddr*) &peer_addr, &peer_addr_len)) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
char phost[NI_MAXHOST], service[NI_MAXSERV];
|
||||
|
||||
s = getnameinfo((struct sockaddr *) &peer_addr,
|
||||
peer_addr_len, phost, NI_MAXHOST, service,
|
||||
NI_MAXSERV, NI_NUMERICSERV);
|
||||
|
||||
if (s == 0)
|
||||
ws_log(server, LOG_DEBUG, "Connection from %s:%s", phost, service);
|
||||
else
|
||||
ws_log(server, LOG_DEBUG, "getnameinfo: %s", gai_strerror(s));
|
||||
|
||||
headers_t headers = ws_headers_create();
|
||||
char* host = NULL;
|
||||
method_t method;
|
||||
const char* path = NULL;
|
||||
|
||||
while(true) {
|
||||
if (read(connfd, buffer + buffersize++, 1) < 1) {
|
||||
ws_log(server, LOG_TESTING, "read: %s", strerror(errno));
|
||||
// error or disconect
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffersize > nb * BUFFER_SIZE - 1) {
|
||||
nb++;
|
||||
buffer = (char*) realloc(buffer, nb * BUFFER_SIZE * sizeof(char));
|
||||
}
|
||||
|
||||
buffer[buffersize] = '\0';
|
||||
|
||||
if (buffer[buffersize - 1] == '\n') {
|
||||
ws_log(server, LOG_TESTING, "got line: %i, %s", buffersize, buffer);
|
||||
|
||||
// line end
|
||||
if (buffersize == 1) {
|
||||
// TODO 400
|
||||
ws_simple_status(connfd, 400);
|
||||
break;
|
||||
}
|
||||
if (buffer[buffersize - 2] != '\r') {
|
||||
// TODO 400
|
||||
ws_simple_status(connfd, 400);
|
||||
break;
|
||||
}
|
||||
// buffer contains the line + \r\n\0
|
||||
|
||||
if (buffersize == 3 - 1) { // "\r\n" + \0
|
||||
ws_log(server, LOG_TESTING, "got last header line");
|
||||
|
||||
if (headerlines < 1) {
|
||||
// TODO 400
|
||||
ws_simple_status(connfd, 400);
|
||||
break;
|
||||
}
|
||||
|
||||
ws_log(server, LOG_TESTING, "find host");
|
||||
host = ws_host_find(&path, headers);
|
||||
ws_log(server, LOG_DEBUG, "uri: %s%s", host, path);
|
||||
|
||||
// find handler
|
||||
ws_log(server, LOG_TESTING, "find handler");
|
||||
handler_t handler = ws_handler_find(server, path, host);
|
||||
|
||||
if (handler == NULL) {
|
||||
// TODO 404
|
||||
ws_log(server, LOG_DEBUG, "handler not found");
|
||||
ws_simple_status(connfd, 404);
|
||||
break;
|
||||
}
|
||||
ws_log(server, LOG_TESTING, "found");
|
||||
|
||||
int pipefd[2];
|
||||
|
||||
ws_log(server, LOG_TESTING, "create pipe");
|
||||
if (pipe(pipefd) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ws_log(server, LOG_TESTING, "create response header struct");
|
||||
headers_t responseHeaders = ws_headers_create();
|
||||
ws_log(server, LOG_TESTING, "open request stream");
|
||||
stream_t request = (stream_t) fdopen(connfd, "r");
|
||||
|
||||
if (request == NULL) {
|
||||
ws_log(server, LOG_DEBUG, "fdopen: %s", strerror(errno));
|
||||
}
|
||||
|
||||
//fopen write end of pipe
|
||||
ws_log(server, LOG_TESTING, "open pipe stream");
|
||||
stream_t response = (stream_t) fdopen(pipefd[1], "w");
|
||||
|
||||
if (response == NULL) {
|
||||
ws_log(server, LOG_DEBUG, "fdopen: %s", strerror(errno));
|
||||
}
|
||||
|
||||
// unbuffer
|
||||
setbuf(request, NULL);
|
||||
|
||||
// handler
|
||||
ws_log(server, LOG_DEBUG, "execute handler");
|
||||
int code = (*handler)(method, host, path, headers, &responseHeaders, request, response);
|
||||
ws_log(server, LOG_TESTING, "handler finished with %i", code);
|
||||
|
||||
fclose(response);
|
||||
|
||||
// send
|
||||
ws_log(server, LOG_TESTING, "sending response");
|
||||
ws_send(connfd, code, responseHeaders, pipefd[0]);
|
||||
|
||||
// cleanup
|
||||
ws_log(server, LOG_DEBUG, "cleanup");
|
||||
close(pipefd[0]);
|
||||
fclose(request);
|
||||
ws_headers_free(&responseHeaders);
|
||||
|
||||
ws_log(server, LOG_TESTING, "done");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ws_log(server, LOG_TESTING, "enlarging header buffer");
|
||||
while (headersize + buffersize + 1 > nhb * BUFFER_SIZE) {
|
||||
nhb++;
|
||||
header = (char*) realloc(header, nhb * BUFFER_SIZE * sizeof(char));
|
||||
}
|
||||
|
||||
memcpy(header + headersize, buffer, buffersize + 1);
|
||||
header[headersize + buffersize] = '\0';
|
||||
// new line is now permanently saved in header + headersize
|
||||
|
||||
if (headerlines++ == 0) {
|
||||
ws_log(server, LOG_TESTING, "parsing first line");
|
||||
int e;
|
||||
if ((e = ws_request_parse(header + headersize, &path, &method)) < 0) {
|
||||
ws_log(server, LOG_DEBUG, "parsing error: %i", e);
|
||||
// TODO 400
|
||||
ws_simple_status(connfd, 400);
|
||||
break;
|
||||
}
|
||||
ws_log(server, LOG_TESTING, "path: %s", path);
|
||||
} else {
|
||||
// convert to header struct
|
||||
ws_log(server, LOG_TESTING, "convert line to headerstruct");
|
||||
ws_headers_convert(&headers, header + headersize);
|
||||
ws_log(server, LOG_DEBUG, "header: %s: %s", headers.fields[headers.nrfields - 1].key,
|
||||
headers.fields[headers.nrfields - 1].value);
|
||||
}
|
||||
headersize += buffersize + 1;
|
||||
|
||||
ws_log(server, LOG_TESTING, "reset buffer");
|
||||
buffer = (char*) realloc(buffer, BUFFER_SIZE * sizeof(char));
|
||||
nb = 1;
|
||||
buffersize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
header = (char*) realloc(header, BUFFER_SIZE * sizeof(char));
|
||||
nhb = 1;
|
||||
headersize = 0;
|
||||
|
||||
headerlines = 0;
|
||||
|
||||
ws_headers_free(&headers);
|
||||
|
||||
if (host != NULL)
|
||||
free(host);
|
||||
host = NULL;
|
||||
|
||||
buffer = (char*) realloc(buffer, BUFFER_SIZE * sizeof(char));
|
||||
nb = 1;
|
||||
buffersize = 0;
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
free(header);
|
||||
}
|
||||
|
||||
int ws_run(webserver_t* server) {
|
||||
switch(server->options.mode) {
|
||||
case LINEAR:
|
||||
return ws_run_linear(server);
|
||||
default:
|
||||
errno = EBADRQC;
|
||||
return -1;
|
||||
}
|
||||
}
|
115
webserver.h
Normal file
115
webserver.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
#ifndef WEBSERVER_H
|
||||
#define WEBSERVER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define BUFFER_SIZE 1024
|
||||
#define WS_HTTP_VERSION "HTTP/1.1"
|
||||
#define WS_VERSION "0.1"
|
||||
#define WS_NAME "SERWER"
|
||||
|
||||
typedef struct header {
|
||||
const char* key;
|
||||
const char* value;
|
||||
} header_t;
|
||||
|
||||
typedef struct headers {
|
||||
header_t* fields;
|
||||
int nrfields;
|
||||
} headers_t;
|
||||
|
||||
typedef FILE* stream_t;
|
||||
|
||||
typedef int loglevel_t;
|
||||
|
||||
#define LOG_TESTING 11
|
||||
#define LOG_DEBUG 10
|
||||
#define LOG_VERBOSE 5
|
||||
#define LOG_WARN 2
|
||||
#define LOG_ERROR 0
|
||||
|
||||
typedef enum method {
|
||||
OPTIONS,
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
TRACE,
|
||||
CONNECT
|
||||
} method_t;
|
||||
|
||||
typedef int(*handler_t)(method_t method, const char* host, const char* path, headers_t requestHeaders,
|
||||
headers_t* responseHeaders, stream_t request, stream_t response);
|
||||
|
||||
typedef struct handle {
|
||||
const char* path;
|
||||
const char* host;
|
||||
handler_t handler;
|
||||
} handle_t;
|
||||
|
||||
typedef enum mode {
|
||||
//PRE_FORKED,
|
||||
//POST_FORKED,
|
||||
//STATEFULL,
|
||||
//THREADED,
|
||||
LINEAR
|
||||
} srvmode_t;
|
||||
|
||||
typedef struct srvoptions {
|
||||
srvmode_t mode;
|
||||
int timeout;
|
||||
int maxconnections;
|
||||
loglevel_t loglevel;
|
||||
} srvoptions_t;
|
||||
|
||||
typedef struct webserver {
|
||||
const char* name;
|
||||
int sfd;
|
||||
const char* host;
|
||||
const char* port;
|
||||
handle_t* handles;
|
||||
int nrhandles;
|
||||
FILE* logfile;
|
||||
srvoptions_t options;
|
||||
} webserver_t;
|
||||
|
||||
bool ws_host_match(const char*, const char*);
|
||||
bool ws_path_match(const char*, const char*);
|
||||
|
||||
handler_t ws_handler_find(webserver_t*, const char*, const char*);
|
||||
|
||||
char* ws_host_find(const char**, headers_t);
|
||||
|
||||
const char* ws_strm(method_t);
|
||||
|
||||
int ws_request_parse(char*, const char**, method_t*);
|
||||
|
||||
headers_t ws_headers_create(void);
|
||||
|
||||
void ws_headers_add(headers_t*, const char*, const char*);
|
||||
|
||||
void ws_headers_convert(headers_t*, char*);
|
||||
|
||||
void ws_headers_free(headers_t*);
|
||||
|
||||
int ws_listen(webserver_t*);
|
||||
|
||||
void ws_handle_add(webserver_t*, handle_t);
|
||||
|
||||
webserver_t ws_create(const char*, const char*, const char*, FILE*, srvoptions_t);
|
||||
|
||||
void ws_log(webserver_t*, loglevel_t, const char*, ...);
|
||||
|
||||
const char* ws_code_reason(int);
|
||||
|
||||
void ws_send(int, int, headers_t, int);
|
||||
|
||||
void ws_simple_status(int, int);
|
||||
|
||||
int ws_run_linear(webserver_t*);
|
||||
|
||||
int ws_run(webserver_t*);
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue