Implemented responses

This commit is contained in:
Nate Choe
2022-01-22 19:54:03 -06:00
parent 55da97269b
commit ad06bbc7be
8 changed files with 214 additions and 54 deletions

View File

@@ -32,22 +32,28 @@
#include <responses.h> #include <responses.h>
#include <connections.h> #include <connections.h>
int newConnection(Connection *ret, int fd) { Connection *newConnection(int fd) {
Connection *ret = malloc(sizeof(Connection));
if (ret == NULL)
return NULL;
ret->fd = fd; ret->fd = fd;
if (fcntl(ret->fd, F_SETFL, O_NONBLOCK))
return 1;
ret->progress = RECEIVE_REQUEST; ret->progress = RECEIVE_REQUEST;
ret->currLineAlloc = 30; ret->currLineAlloc = 30;
ret->currLineLen = 0; ret->currLineLen = 0;
ret->currLine = malloc(ret->currLineAlloc); ret->currLine = malloc(ret->currLineAlloc);
if (ret->currLine == NULL) if (ret->currLine == NULL) {
return 1; free(ret);
return NULL;
}
ret->allocatedFields = 10; ret->allocatedFields = 10;
ret->fields = malloc(sizeof(char *[2]) * ret->allocatedFields); ret->fields = malloc(sizeof(Field) * ret->allocatedFields);
if (ret->fields == NULL) if (ret->fields == NULL) {
return 1; free(ret->currLine);
free(ret);
return NULL;
}
ret->path = NULL; ret->path = NULL;
ret->body = NULL; ret->body = NULL;
@@ -55,7 +61,7 @@ int newConnection(Connection *ret, int fd) {
//initialized to NULL so that free() doens't fail. //initialized to NULL so that free() doens't fail.
ret->next = NULL; ret->next = NULL;
return 0; return ret;
} }
void resetConnection(Connection *conn) { void resetConnection(Connection *conn) {
@@ -82,6 +88,8 @@ static int processRequest(Connection *conn) {
if (line[i] == ' ') { if (line[i] == ' ') {
line[i] = '\0'; line[i] = '\0';
conn->type = getType(line); conn->type = getType(line);
if (conn->type == INVALID)
return 1;
line += i + 1; line += i + 1;
break; break;
} }
@@ -114,9 +122,11 @@ static int processRequest(Connection *conn) {
static int processField(Connection *conn) { static int processField(Connection *conn) {
if (conn->currLineLen == 0) { if (conn->currLineLen == 0) {
conn->progress = RECEIVE_BODY; conn->progress = RECEIVE_BODY;
conn->bodylen = 0;
for (size_t i = 0; i < conn->fieldCount; i++) { for (size_t i = 0; i < conn->fieldCount; i++) {
if (strcmp(conn->fields[i][0], "Content-Length") == 0) if (strcmp(conn->fields[i].field,
conn->bodylen = atol(conn->fields[i][1]); "Content-Length") == 0)
conn->bodylen = atol(conn->fields[i].value);
} }
conn->body = malloc(conn->bodylen + 1); conn->body = malloc(conn->bodylen + 1);
if (conn->body == NULL) if (conn->body == NULL)
@@ -127,7 +137,7 @@ static int processField(Connection *conn) {
if (conn->fieldCount >= conn->allocatedFields) { if (conn->fieldCount >= conn->allocatedFields) {
conn->allocatedFields *= 2; conn->allocatedFields *= 2;
char *(*newfields)[2] = realloc(conn->fields, conn->allocatedFields * Field *newfields = realloc(conn->fields, conn->allocatedFields *
sizeof(char *[2])); sizeof(char *[2]));
if (newfields == NULL) if (newfields == NULL)
return 1; return 1;
@@ -140,17 +150,17 @@ static int processField(Connection *conn) {
if (split == NULL) if (split == NULL)
return 1; return 1;
char *header = malloc(split - line + 1); char *field = malloc(split - line + 1);
memcpy(header, line, split - line); memcpy(field, line, split - line);
header[split - line] = '\0'; field[split - line] = '\0';
linelen -= split - line + 2; linelen -= split - line + 2;
line += split - line + 2; line += split - line + 2;
char *value = malloc(linelen + 1); char *value = malloc(linelen + 1);
memcpy(value, line, linelen + 1); memcpy(value, line, linelen + 1);
conn->fields[conn->fieldCount][0] = header; conn->fields[conn->fieldCount].field = field;
conn->fields[conn->fieldCount][1] = value; conn->fields[conn->fieldCount].value = value;
conn->fieldCount++; conn->fieldCount++;
@@ -188,9 +198,9 @@ static int processChar(Connection *conn, char c, Sitefile *site) {
if (conn->receivedBody < conn->bodylen) if (conn->receivedBody < conn->bodylen)
conn->body[conn->receivedBody++] = c; conn->body[conn->receivedBody++] = c;
} }
if (conn->receivedBody >= conn->bodylen) { if (conn->progress == RECEIVE_BODY &&
conn->receivedBody >= conn->bodylen)
sendResponse(conn, site); sendResponse(conn, site);
}
return 0; return 0;
} }
@@ -199,7 +209,7 @@ int updateConnection(Connection *conn, Sitefile *site) {
for (;;) { for (;;) {
ssize_t received = read(conn->fd, buff, sizeof(buff)); ssize_t received = read(conn->fd, buff, sizeof(buff));
if (received < 0) if (received < 0)
return 1; return errno != EAGAIN;
if (received == 0) if (received == 0)
break; break;
for (unsigned long i = 0; i < received; i++) { for (unsigned long i = 0; i < received; i++) {

View File

@@ -27,6 +27,11 @@ typedef enum {
SEND_RESPONSE, SEND_RESPONSE,
} ConnectionSteps; } ConnectionSteps;
typedef struct {
char *field;
char *value;
} Field;
typedef struct Connection { typedef struct Connection {
int fd; int fd;
ConnectionSteps progress; ConnectionSteps progress;
@@ -35,7 +40,7 @@ typedef struct Connection {
char *path; char *path;
//ephemeral //ephemeral
char *(*fields)[2]; Field *fields;
//pointer to array of 2 pointers, persistent //pointer to array of 2 pointers, persistent
size_t fieldCount; size_t fieldCount;
size_t allocatedFields; size_t allocatedFields;
@@ -57,7 +62,7 @@ typedef struct Connection {
//Ephemeral fields: Things which are freed and reallocated after each new //Ephemeral fields: Things which are freed and reallocated after each new
//request, path, body //request, path, body
int newConnection(Connection *ret, int fd); Connection *newConnection(int fd);
//returns non-zero on error. creates a new connection bound to fd //returns non-zero on error. creates a new connection bound to fd
void resetConnection(Connection *conn); void resetConnection(Connection *conn);
void freeConnection(Connection *conn); void freeConnection(Connection *conn);

34
src/include/util.h Normal file
View File

@@ -0,0 +1,34 @@
/*
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_UTIL
#define _HAVE_UTIL
typedef enum {
GET,
POST,
PUT,
HEAD,
DELETE,
PATCH,
OPTIONS,
INVALID,
//this indicates an invalid type of request, there is no request called
//INVALID in HTTP/1.1.
} RequestType;
RequestType getType(char *str);
#endif

View File

@@ -21,6 +21,7 @@
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <pthread.h> #include <pthread.h>
#include <netinet/in.h> #include <netinet/in.h>
@@ -75,7 +76,7 @@ int main(int argc, char **argv) {
assert(fd >= 0); assert(fd >= 0);
int opt = 1; int opt = 1;
assert(setsockopt(fd, SOL_SOCKET, assert(setsockopt(fd, SOL_SOCKET,
SO_KEEPALIVE | SO_REUSEPORT, SO_REUSEPORT,
&opt, sizeof(opt)) >= 0); &opt, sizeof(opt)) >= 0);
struct sockaddr_in addr; struct sockaddr_in addr;
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
@@ -127,16 +128,19 @@ int main(int argc, char **argv) {
int newfd = accept(fd, (struct sockaddr *) &addr, &addrlen); int newfd = accept(fd, (struct sockaddr *) &addr, &addrlen);
if (newfd < 0) if (newfd < 0)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
int flags = fcntl(newfd, F_GETFL);
if (fcntl(newfd, F_SETFL, flags | O_NONBLOCK))
exit(EXIT_FAILURE);
int lowestThread = 0; int lowestThread = 0;
int lowestCount = schedule[0]; int lowestCount = pending[0];
for (int i = 1; i < processes - 1; i++) { for (int i = 1; i < processes - 1; i++) {
if (schedule[i] < lowestCount) { if (pending[i] < lowestCount) {
lowestThread = i; lowestThread = i;
lowestCount = schedule[i]; lowestCount = pending[i];
} }
} }
schedule[0] = lowestThread;
schedule[1] = newfd; schedule[1] = newfd;
schedule[0] = lowestThread;
} }
} }
} }

View File

@@ -16,29 +16,87 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <responses.h> #include <responses.h>
static int sendConn(int fd, char *str) { static int sendConnection(Connection *conn, char *format, ...) {
size_t len = strlen(str); va_list ap;
if (write(fd, str, len)) va_start(ap, format);
int len = vsnprintf(NULL, 0, format, ap);
char *data = malloc(len + 1);
if (data == NULL)
return 1; return 1;
va_end(ap);
va_start(ap, format);
vsnprintf(data, len + 1, format, ap);
write(conn->fd, data, len);
free(data);
return 0; return 0;
} }
static void readResponse(Connection *conn, char *path) {
FILE *file = fopen(path, "r");
if (file == NULL) {
sendConnection(conn,
"HTTP/1.1 403 Forbidden\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Server: swebs/0.1\r\n"
"Content-Length: 21\r\n"
"\r\n"
"<h1>Invalid page</h1>\n"
);
return;
}
fseek(file, 0, SEEK_END);
long len = ftell(file);
char *data = malloc(len);
if (data == NULL)
return;
fseek(file, 0, SEEK_SET);
fread(data, 1, len, file);
fclose(file);
sendConnection(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Server: swebs/0.1\r\n"
"Content-Length: %ld\r\n"
"\r\n", len
);
write(conn->fd, data, len);
free(data);
}
int sendResponse(Connection *conn, Sitefile *site) { int sendResponse(Connection *conn, Sitefile *site) {
printf("test %d\n", site->size); if (conn->path == NULL)
return 1;
for (int i = 0; i < site->size; i++) { for (int i = 0; i < site->size; i++) {
printf("%s %s\n", site->content[i].path, site->content[i].arg); if (site->content[i].respondto != conn->type)
continue;
if (strcmp(conn->path, site->content[i].path) == 0) {
switch (site->content[i].command) {
case READ:
readResponse(conn, site->content[i].arg);
goto end;
default:
return 1;
} }
}
sendConn(conn->fd, "HTTP/1.1 200 OK\r\n"); }
sendConn(conn->fd, "Content-Type: text/html\r\n"); sendConnection(conn,
sendConn(conn->fd, "Content-Length: 16\r\n"); "HTTP/1.1 404 Not Found\r\n"
sendConn(conn->fd, "\r\n"); "Content-Type: text/html; charset=UTF-8\r\n"
sendConn(conn->fd, "<p>Hi there!</p>"); "Server: swebs/0.1\r\n"
"Content-Length: 24\r\n"
"\r\n"
"<h1>File not found</h1>\n"
);
end:
resetConnection(conn); resetConnection(conn);
return 0; return 0;
} }

View File

@@ -42,9 +42,8 @@ void *runServer(RunnerArgs *args) {
//list where we add to the end and read from the beginning. //list where we add to the end and read from the beginning.
for (;;) { for (;;) {
if (schedule[0] == id) { if (schedule[0] == id) {
Connection *newconn = malloc(sizeof(Connection)); Connection *newconn = newConnection(schedule[1]);
assert(newconn != NULL); assert(newconn != NULL);
assert(newConnection(newconn, schedule[1]) == 0);
if (last == NULL) if (last == NULL)
connections = newconn; connections = newconn;
@@ -62,17 +61,16 @@ void *runServer(RunnerArgs *args) {
//pointers which have pointers. //pointers which have pointers.
while (iter != NULL) { while (iter != NULL) {
if (updateConnection(iter, site)) { if (updateConnection(iter, site)) {
if (iter->next == NULL) if (iter == last)
last = prev; last = prev;
Connection *old = iter;
iter = iter->next;
freeConnection(old);
if (prev == NULL) if (prev == NULL)
connections = connections->next; connections = iter;
else else
prev->next = iter->next; prev->next = iter;
Connection *newiter = iter->next;
freeConnection(iter);
iter = newiter;
pending[id]--; pending[id]--;
//TODO: Clean this later
} }
else { else {
prev = iter; prev = iter;

View File

@@ -71,6 +71,13 @@ static ReturnCode getToken(FILE *file, char **ret) {
*ret = malloc(allocatedLen); *ret = malloc(allocatedLen);
for (len = 0;; len++) { for (len = 0;; len++) {
if (len >= allocatedLen) {
allocatedLen *= 2;
char *newret = realloc(*ret, allocatedLen);
if (newret == NULL)
goto error;
*ret = newret;
}
int c = fgetc(file); int c = fgetc(file);
switch (type) { switch (type) {
case QUOTED: case QUOTED:
@@ -95,13 +102,6 @@ static ReturnCode getToken(FILE *file, char **ret) {
goto gotToken; goto gotToken;
goto error; goto error;
} }
if (len >= allocatedLen) {
allocatedLen *= 2;
char *newret = realloc(*ret, allocatedLen);
if (newret == NULL)
goto error;
*ret = newret;
}
(*ret)[len] = c; (*ret)[len] = c;
} }
gotToken: gotToken:

51
src/util.c Normal file
View File

@@ -0,0 +1,51 @@
/*
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 <string.h>
#include <stdint.h>
#include <util.h>
RequestType getType(char *str) {
if (strlen(str) >= 8)
return INVALID;
uint64_t type = 0;
for (int i = 0; str[i]; i++) {
type <<= 8;
type |= str[i];
}
switch (type) {
case 0x474554l:
return GET;
case 0x504f5354l:
return POST;
case 0x505554l:
return PUT;
case 0x48454144l:
return HEAD;
case 0x44454c455445l:
return DELETE;
case 0x5041544348l:
return PATCH;
case 0x4f5054494f4e53l:
return OPTIONS;
default:
return INVALID;
}
//This would actually be far nicer in HolyC of all languages. I feel
//like the context immediately following each magic number is enough.
}