Initial commit

This commit is contained in:
Nate Choe
2022-01-22 02:44:45 -06:00
commit ad64e4ecb2
15 changed files with 1602 additions and 0 deletions

244
src/connections.c Normal file
View File

@@ -0,0 +1,244 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <runner.h>
#include <sitefile.h>
#include <responses.h>
#include <connections.h>
int newConnection(Connection *ret, int fd) {
ret->fd = fd;
if (fcntl(ret->fd, F_SETFL, O_NONBLOCK))
return 1;
ret->progress = RECEIVE_REQUEST;
ret->currLineAlloc = 30;
ret->currLineLen = 0;
ret->currLine = malloc(ret->currLineAlloc);
if (ret->currLine == NULL)
return 1;
ret->allocatedFields = 10;
ret->fields = malloc(sizeof(char *[2]) * ret->allocatedFields);
if (ret->fields == NULL)
return 1;
ret->path = NULL;
ret->body = NULL;
//pointers to things that are allocated within functions should be
//initialized to NULL so that free() doens't fail.
ret->next = NULL;
return 0;
}
void resetConnection(Connection *conn) {
conn->type = RECEIVE_REQUEST;
conn->fieldCount = 0;
free(conn->body);
free(conn->path);
conn->body = NULL;
conn->path = NULL;
}
void freeConnection(Connection *conn) {
shutdown(conn->fd, SHUT_RDWR);
free(conn->currLine);
free(conn->path);
free(conn->fields);
free(conn->body);
free(conn);
}
static int processRequest(Connection *conn) {
char *line = conn->currLine;
for (int i = 0;; i++) {
if (line[i] == ' ') {
line[i] = '\0';
if (i >= 8)
return 1;
uint64_t type = 0;
for (int j = 0; j < i; j++) {
type <<= 8;
type |= line[j];
}
switch (type) {
case 0x474554l:
conn->type = GET;
break;
case 0x504f5354l:
conn->type = POST;
break;
case 0x505554l:
conn->type = PUT;
break;
case 0x48454144l:
conn->type = HEAD;
break;
case 0x44454c455445l:
conn->type = DELETE;
break;
case 0x5041544348l:
conn->type = PATCH;
break;
case 0x4f5054494f4e53l:
conn->type = OPTIONS;
break;
default:
return 1;
}
//This would actually be far nicer in HolyC of all
//languages. I feel like the context immediately
//following each magic number is enough.
line += i + 1;
break;
}
if (line[i] == '\0') {
return 1;
}
}
for (int i = 0;; i++) {
if (line[i] == ' ') {
line[i] = '\0';
conn->path = malloc(i + 1);
if (conn->path == NULL)
return 1;
memcpy(conn->path, line, i + 1);
line += i + 1;
break;
}
if (line[i] == '\0') {
return 1;
}
}
if (strcmp(line, "HTTP/1.1"))
return 1;
conn->progress = RECEIVE_HEADER;
conn->fieldCount = 0;
return 0;
}
static int processField(Connection *conn) {
if (conn->currLineLen == 0) {
conn->progress = RECEIVE_BODY;
for (size_t i = 0; i < conn->fieldCount; i++) {
if (strcmp(conn->fields[i][0], "Content-Length") == 0)
conn->bodylen = atol(conn->fields[i][1]);
}
conn->body = malloc(conn->bodylen + 1);
if (conn->body == NULL)
return 1;
conn->receivedBody = 0;
return 0;
}
if (conn->fieldCount >= conn->allocatedFields) {
conn->allocatedFields *= 2;
char *(*newfields)[2] = realloc(conn->fields, conn->allocatedFields *
sizeof(char *[2]));
if (newfields == NULL)
return 1;
conn->fields = newfields;
}
char *line = conn->currLine;
char *split = strstr(line, ": ");
size_t linelen = conn->currLineLen;
if (split == NULL)
return 1;
char *header = malloc(split - line + 1);
memcpy(header, line, split - line);
header[split - line] = '\0';
linelen -= split - line + 2;
line += split - line + 2;
char *value = malloc(linelen + 1);
memcpy(value, line, linelen + 1);
conn->fields[conn->fieldCount][0] = header;
conn->fields[conn->fieldCount][1] = value;
conn->fieldCount++;
return 0;
}
static int processChar(Connection *conn, char c, Sitefile *site) {
if (conn->progress < RECEIVE_BODY) {
if (conn->currLineLen >= conn->currLineAlloc) {
conn->currLineAlloc *= 2;
char *newline = realloc(conn->currLine,
conn->currLineAlloc);
assert(newline != NULL);
conn->currLine = newline;
}
conn->currLine[conn->currLineLen++] = c;
if (c == '\n') {
if (--conn->currLineLen <= 0)
return 1;
if (conn->currLine[--conn->currLineLen] != '\r')
return 1;
conn->currLine[conn->currLineLen] = '\0';
if (conn->progress == RECEIVE_REQUEST) {
if (processRequest(conn))
return 1;
}
else if (conn->progress == RECEIVE_HEADER) {
if (processField(conn))
return 1;
}
conn->currLineLen = 0;
}
}
else if (conn->progress == RECEIVE_BODY) {
if (conn->receivedBody < conn->bodylen)
conn->body[conn->receivedBody++] = c;
}
if (conn->receivedBody >= conn->bodylen) {
sendResponse(conn, site);
}
return 0;
}
int updateConnection(Connection *conn, Sitefile *site) {
char buff[300];
for (;;) {
ssize_t received = read(conn->fd, buff, sizeof(buff));
if (received < 0)
return 1;
if (received == 0)
break;
for (unsigned long i = 0; i < received; i++) {
if (processChar(conn, buff[i], site))
return 1;
}
}
return 0;
}

78
src/include/connections.h Normal file
View File

@@ -0,0 +1,78 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_CONNECTIONS
#define _HAVE_CONNECTIONS
#include <runner.h>
#include <sitefile.h>
typedef enum {
RECEIVE_REQUEST,
RECEIVE_HEADER,
RECEIVE_BODY,
SEND_RESPONSE,
} ConnectionSteps;
typedef enum {
GET,
POST,
PUT,
HEAD,
DELETE,
PATCH,
OPTIONS,
} RequestTypes;
typedef struct Connection {
int fd;
ConnectionSteps progress;
RequestTypes type;
char *path;
//ephemeral
char *(*fields)[2];
//pointer to array of 2 pointers, persistent
size_t fieldCount;
size_t allocatedFields;
char *body;
//ephemeral
size_t bodylen;
size_t receivedBody;
struct Connection *next;
char *currLine;
//persistent
size_t currLineAlloc;
size_t currLineLen;
} Connection;
//The 2 types of fields:
//Persistent fields: Things which aren't freed after a reset, currLine, fields
//Ephemeral fields: Things which are freed and reallocated after each new
//request, path, body
int newConnection(Connection *ret, int fd);
//returns non-zero on error. creates a new connection bound to fd
void resetConnection(Connection *conn);
void freeConnection(Connection *conn);
int updateConnection(Connection *conn, Sitefile *site);
//returns non-zero on error.
//Generating a new connection and repeatedly calling updateConnection will
//handle everything.
#endif

26
src/include/responses.h Normal file
View File

@@ -0,0 +1,26 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_RESPONSES
#define _HAVE_RESPONSES
#include <sitefile.h>
#include <connections.h>
int sendResponse(Connection *conn, Sitefile *site);
//This function assumes that every field in conn has been initialized, and
//resets conn for the next request
#endif

41
src/include/runner.h Normal file
View File

@@ -0,0 +1,41 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_RUNNER
#define _HAVE_RUNNER
#include <sys/socket.h>
#include <sitefile.h>
#include <connections.h>
typedef struct {
Sitefile *site;
int *pending;
//pending[thread id] = the number of connections being handled by that
// thread
int *schedule;
/*
* schedule[0] = the thread that should take the next connection (-1 if
* there is no connection).
* schedule[1] = the fd of the connection to be accepted
*/
int id;
} RunnerArgs;
//my least favourite anti pattern
void *runServer(RunnerArgs *args);
#endif

32
src/include/sitefile.h Normal file
View File

@@ -0,0 +1,32 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _HAVE_SITEFILE
#define _HAVE_SITEFILE
typedef struct {
int argc;
char **argv;
} SiteCommand;
typedef struct {
int size;
SiteCommand *content;
} Sitefile;
Sitefile *parseFile(char *path);
void freeSitefile(Sitefile *site);
#endif

141
src/main.c Normal file
View File

@@ -0,0 +1,141 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <netinet/in.h>
#include <runner.h>
#include <sitefile.h>
FILE *logs;
int main(int argc, char **argv) {
char *logout = "/var/log/swebs.log";
char *sitefile = NULL;
int processes = 8;
uint16_t port = htons(80);
int backlog = 100;
for (;;) {
int c = getopt(argc, argv, "o:j:s:c:p:b:h");
if (c == -1)
break;
switch (c) {
case 'o':
logout = optarg;
break;
case 'j':
processes = atoi(optarg);
break;
case 's':
sitefile = optarg;
break;
case 'p':
port = htons(atoi(optarg));
break;
case 'b':
backlog = atoi(optarg);
break;
case 'h':
printf(
"Usage: swebs [options]\n"
" -o [out] Set the log file (default: /var/log/swebs.log)\n"
" -j [cores] Use that many cores (default: 8)\n"
" -s [site file] Use that site file (required)\n"
" -p [port] Set the port (default: 443)\n"
" -b [backlog] Set the socket backlog (default: 100)\n"
);
exit(EXIT_SUCCESS);
case '?':
exit(EXIT_FAILURE);
}
}
int fd = socket(AF_INET, SOCK_STREAM, 0);
assert(fd >= 0);
int opt = 1;
assert(setsockopt(fd, SOL_SOCKET,
SO_KEEPALIVE | SO_REUSEPORT,
&opt, sizeof(opt)) >= 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = port;
socklen_t addrlen = sizeof(addr);
assert(bind(fd, (struct sockaddr *) &addr, addrlen) >= 0);
assert(listen(fd, backlog) >= 0);
if (sitefile == NULL) {
fprintf(stderr, "No sitefile configured\n");
exit(EXIT_FAILURE);
}
logs = fopen(logout, "a");
if (logs == NULL) {
fprintf(stderr, "Couldn't open logs file %s\n", logout);
exit(EXIT_FAILURE);
}
Sitefile *site = parseFile(sitefile);
if (site == NULL) {
fprintf(stderr, "Invalid sitefile %s\n", sitefile);
exit(EXIT_FAILURE);
}
int *pending = calloc(processes - 1, sizeof(int));
int *schedule = malloc(2 * sizeof(int));
if (schedule == NULL)
exit(EXIT_FAILURE);
schedule[0] = -1;
pthread_t *threads = malloc(sizeof(pthread_t) * (processes - 1));
if (threads == NULL)
exit(EXIT_FAILURE);
for (int i = 0; i < processes - 1; i++) {
RunnerArgs *args = malloc(sizeof(RunnerArgs));
if (args == NULL)
exit(EXIT_FAILURE);
args->pending = pending;
args->schedule = schedule;
args->id = i;
pthread_create(threads + i, NULL,
(void*(*)(void*)) runServer, args);
}
for (;;) {
if (schedule[0] == -1) {
int newfd = accept(fd, (struct sockaddr *) &addr, &addrlen);
if (newfd < 0)
exit(EXIT_FAILURE);
int lowestThread = 0;
int lowestCount = schedule[0];
for (int i = 1; i < processes - 1; i++) {
if (schedule[i] < lowestCount) {
lowestThread = i;
lowestCount = schedule[i];
}
}
schedule[0] = lowestThread;
schedule[1] = newfd;
}
}
}

40
src/responses.c Normal file
View File

@@ -0,0 +1,40 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <responses.h>
static int sendConn(int fd, char *str) {
size_t len = strlen(str);
if (write(fd, str, len))
return 1;
return 0;
}
int sendResponse(Connection *conn, Sitefile *site) {
printf("test\n");
sendConn(conn->fd, "HTTP/1.1 200 OK\r\n");
sendConn(conn->fd, "Content-Type: text/html\r\n");
sendConn(conn->fd, "Content-Length: 16\r\n");
sendConn(conn->fd, "\r\n");
sendConn(conn->fd, "<p>Hi there!</p>");
resetConnection(conn);
return 0;
}

84
src/runner.c Normal file
View File

@@ -0,0 +1,84 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <runner.h>
#include <sitefile.h>
#include <connections.h>
void *runServer(RunnerArgs *args) {
Sitefile *site = args->site;
int *pending = args->pending;
int *schedule = args->schedule;
int id = args->id;
Connection *connections = NULL;
Connection *last = NULL;
//Connections are processed in a queue, which is really just a linked
//list where we add to the end and read from the beginning.
for (;;) {
if (schedule[0] == id) {
Connection *newconn = malloc(sizeof(Connection));
assert(newconn != NULL);
assert(newConnection(newconn, schedule[1]) == 0);
if (last == NULL)
connections = newconn;
else
last->next = newconn;
last = newconn;
pending[id]++;
schedule[0] = -1;
}
Connection *prev = NULL;
Connection *iter = connections;
//I know of the Linus Thorvalds good taste code thing, it just
//gets very confusing very fast to think about pointers to
//pointers which have pointers.
while (iter != NULL) {
if (updateConnection(iter, site)) {
if (iter->next == NULL)
last = prev;
if (prev == NULL)
connections = connections->next;
else
prev->next = iter->next;
Connection *newiter = iter->next;
freeConnection(iter);
iter = newiter;
pending[id]--;
//TODO: Clean this later
}
else {
prev = iter;
iter = iter->next;
}
}
}
return NULL;
}

198
src/sitefile.c Normal file
View File

@@ -0,0 +1,198 @@
/*
swebs - a simple web server
Copyright (C) 2022 Nate Choe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <sitefile.h>
typedef enum {
SUCCESS,
LINE_END,
FILE_END,
ERROR,
} ReturnCode;
//this isn't ideal, but it's necessary to avoid namespace collisions.
static void freeTokens(int argc, char **argv) {
for (int i = 0; i < argc; i++)
free(argv[i]);
free(argv);
}
static ReturnCode getToken(FILE *file, char **ret) {
typedef enum {
QUOTED,
NONQUOTED,
} TokenType;
TokenType type;
for (;;) {
int c = fgetc(file);
if (c == '\n')
return LINE_END;
if (c == EOF)
return FILE_END;
if (c == '#') {
while (c != '\n')
c = fgetc(file);
return LINE_END;
}
if (!isspace(c)) {
if (c == '"')
type = QUOTED;
else {
type = NONQUOTED;
ungetc(c, file);
}
break;
}
}
long allocatedLen = 30;
long len;
*ret = malloc(allocatedLen);
for (len = 0;; len++) {
int c = fgetc(file);
switch (type) {
case QUOTED:
if (c == '"')
goto gotToken;
break;
case NONQUOTED:
if (isspace(c)) {
ungetc(c, file);
goto gotToken;
}
break;
}
switch (c) {
case '\\':
c = fgetc(file);
if (c == EOF)
goto error;
break;
case EOF:
if (type == NONQUOTED)
goto gotToken;
goto error;
}
(*ret)[len] = c;
}
gotToken:
(*ret)[len] = '\0';
return SUCCESS;
error:
free(*ret);
return ERROR;
}
static ReturnCode getCommand(FILE *file, int *argcret, char ***argvret) {
//THIS FUNCTION WILL NOT RETURN LINE_END
if (feof(file))
return FILE_END;
int argc = 0;
int allocatedTokens = 5;
char **argv = malloc(allocatedTokens * sizeof(char *));
for (;;) {
ReturnCode code = getToken(file, argv + argc);
switch (code) {
case ERROR:
goto error;
case LINE_END:
if (argc == 0)
continue;
//We allow empty lines
//fallthrough
case FILE_END:
if (argc == 0) {
free(argv);
return FILE_END;
}
*argcret = argc;
*argvret = argv;
return SUCCESS;
case SUCCESS:
argc++;
if (argc >= allocatedTokens) {
allocatedTokens *= 2;
char **newargv = realloc(*argv,
allocatedTokens * sizeof(char *));
if (newargv == NULL)
goto error;
argv = newargv;
}
break;
}
}
error:
freeTokens(argc, argv);
return ERROR;
}
Sitefile *parseFile(char *path) {
FILE *file = fopen(path, "r");
if (file == NULL)
return NULL;
Sitefile *ret = malloc(sizeof(Sitefile));
if (ret == NULL)
return NULL;
int allocatedLength = 50;
ret->size = 0;
ret->content = malloc(allocatedLength * sizeof(SiteCommand));
if (ret->content == NULL) {
free(ret);
return NULL;
}
for (;;) {
int argc;
char **argv;
ReturnCode status = getCommand(file, &argc, &argv);
switch (status) {
case FILE_END:
fclose(file);
return ret;
case ERROR: case LINE_END:
goto error;
case SUCCESS:
break;
}
if (ret->size >= allocatedLength) {
allocatedLength *= 2;
SiteCommand *newcontent = realloc(ret->content,
allocatedLength * sizeof(SiteCommand));
if (newcontent == NULL)
goto error;
ret->content = newcontent;
}
ret->content[ret->size].argc = argc;
ret->content[ret->size].argv = argv;
}
error:
freeSitefile(ret);
return NULL;
}
void freeSitefile(Sitefile *site) {
for (long i = 0; i < site->size; i++)
freeTokens(site->content[i].argc, site->content[i].argv);
free(site->content);
free(site);
}