/Makefile Secret
Last active
January 7, 2025 04:47
An HTTP Server written in C from scratch using sockets for I/O and a dynamic lookup db.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <time.h> | |
#include <signal.h> | |
#include <sys/stat.h> | |
#include <netdb.h> | |
#include <sys/types.h> | |
#include <sys/wait.h> | |
#include <assert.h> | |
#define CHUNKSIZE 4096 | |
#define MAXPENDING 5 | |
#define STATUS200 "HTTP/1.0 200 OK\r\n\r\n" | |
#define STATUS404 "HTTP/1.0 404 Not Found\r\n\r\n<html><body>\n<h1>404 Not Found</h1>\n</body></html>\n" | |
#define STATUS501 "HTTP/1.0 501 Not Implemented\r\n\r\n<html><body>\n<h1>501 Not Implemented</h1>\n</body></html>\n" | |
#define STATUS400 "HTTP/1.0 400 Bad Request\r\n\r\n<html><body>\n<h1>400 Bad Request</h1>\n</body></html>\n" | |
#define STATUS403 "HTTP/1.0 403 Forbidden\r\n\r\n<html><body>\n<h1>403 Forbidden</h1>\n</body></html>\n" | |
static void die(const char *s) | |
{ | |
perror(s); | |
exit(1); | |
} | |
void MDB(int clntsock, int lookupsock, FILE *sock1, struct sockaddr_in clntaddr, char *requestURI, char *httpVer) | |
{ | |
const char *key = requestURI + 16; | |
char query[4096]; | |
if (strcmp(requestURI, "/mdb-lookup") == 0) | |
{ | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
fprintf(stderr, "200 OK\n"); | |
} | |
else if (strstr(requestURI, "/mdb-lookup?key=")) | |
{ | |
fprintf(stderr, "looking up [%s]: ", key); | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
snprintf(query, sizeof(query), "%s\n", key); | |
send(lookupsock, query, strlen(query), 0); | |
send(clntsock, "<p><table border>", strlen("<p><table border>"), 0); | |
fprintf(stderr, "200 OK\n"); | |
while (1) | |
{ | |
char line[1000]; | |
if (fgets(line, sizeof(line), sock1) == NULL) | |
{ | |
return; | |
} | |
if (strcmp(line, "\n") == 0) | |
{ | |
break; | |
} | |
char *r = "\n<tr><td>"; | |
send(clntsock, "\n<tbody>", strlen("\n<tbody>"), 0); | |
send(clntsock, r, strlen(r), 0); | |
send(clntsock, line, strlen(line), 0); | |
send(clntsock, "\n", strlen("\n"), 0); | |
send(clntsock, "\n</td></tr></tbody>", 9, 0); | |
} | |
send(clntsock, "\n</table></body></html>", 24, 0); | |
} | |
} | |
void Client(int clntsock, struct sockaddr_in clntaddr, const char *webRoot, int lookupsock, FILE *sock1) | |
{ | |
char buffer[CHUNKSIZE]; | |
char method[10]; | |
char requestURI[100]; | |
char httpVer[10]; | |
const char *form = | |
"<h1>mdb-lookup</h1>\n" | |
"<p>\n" | |
"<form method=GET action=/mdb-lookup>\n" | |
"lookup: <input type=text name=key>\n" | |
"<input type=submit>\n" | |
"</form>\n" | |
"<p>\n"; | |
int recvbytes = recv(clntsock, buffer, sizeof(buffer), 0); | |
if (recvbytes < 0) | |
{ | |
close(clntsock); | |
return; | |
} | |
sscanf(buffer, "%s %s %s", method, requestURI, httpVer); | |
if (strcmp(method, "GET") != 0) | |
{ | |
send(clntsock, STATUS501, strlen(STATUS501), 0); | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
fprintf(stderr, "501 Not Implemented\n"); | |
} | |
else if (requestURI[0] != '/' || strstr(requestURI, "/..")) | |
{ | |
send(clntsock, STATUS400, strlen(STATUS400), 0); | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
fprintf(stderr, "400 Bad Request\n"); | |
} | |
else if (strstr(requestURI, "/mdb-lookup") != NULL) | |
{ | |
send(clntsock, STATUS200, strlen(STATUS200), 0); | |
send(clntsock, form, strlen(form), 0); | |
MDB(clntsock, lookupsock, sock1, clntaddr, requestURI, httpVer); | |
} | |
else | |
{ | |
char fp[256]; | |
strcpy(fp, webRoot); | |
strcat(fp, requestURI); | |
int len = strlen(fp) - 1; | |
if (fp[len] == '/') | |
{ | |
strcat(fp, "index.html"); | |
} | |
FILE *filename = fopen(fp, "rb"); | |
struct stat st; | |
if (stat(fp, &st) == 0 && S_ISDIR(st.st_mode)) | |
{ | |
send(clntsock, STATUS403, strlen(STATUS403), 0); | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
fprintf(stderr, "403 Forbidden\n"); | |
} | |
else if (filename == NULL) | |
{ | |
send(clntsock, STATUS404, strlen(STATUS404), 0); | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
fprintf(stderr, "404 Not Found\n"); | |
} | |
else | |
{ | |
send(clntsock, STATUS200, strlen(STATUS200), 0); | |
fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
fprintf(stderr, "200 OK\n"); | |
char filebuf[CHUNKSIZE]; | |
int bytes; | |
while ((bytes = fread(filebuf, 1, sizeof(filebuf), filename)) > 0) | |
{ | |
send(clntsock, filebuf, bytes, 0); | |
} | |
fclose(filename); | |
//fprintf(stderr, "%s \"GET %s %s\" ", inet_ntoa(clntaddr.sin_addr), requestURI, httpVer); | |
//fprintf(stderr, "200 OK\n"); | |
} | |
} | |
close(clntsock); | |
} | |
int main(int argc, char **argv) | |
{ | |
if (argc != 5) | |
{ | |
fprintf(stderr, "Usage: %s <server_port> <web_root> <mdb-lookup-host> <mdb-lookup-port>\n", argv[0]); | |
exit(1); | |
} | |
int server_port = atoi(argv[1]); | |
const char *webRoot = argv[2]; | |
const char *host = argv[3]; | |
int mdb_lookup_port = atoi(argv[4]); | |
struct hostent *he; | |
if ((he = gethostbyname(host)) == NULL) | |
{ | |
die("gethostbyname failed"); | |
} | |
int serverSocket = socket(AF_INET, SOCK_STREAM, 0); | |
if (serverSocket < 0) | |
die("socket failed"); | |
struct sockaddr_in servaddr; | |
struct sockaddr_in clntaddr; | |
memset(&servaddr, 0, sizeof(servaddr)); | |
servaddr.sin_family = AF_INET; | |
servaddr.sin_addr.s_addr = INADDR_ANY; | |
servaddr.sin_port = htons(server_port); | |
if (bind(serverSocket, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) | |
die("bind failed"); | |
if (listen(serverSocket, 10) < 0) | |
die("listen failed"); | |
int lookupsock = socket(AF_INET, SOCK_STREAM, 0); | |
if (lookupsock < 0) | |
die("socket failed"); | |
struct sockaddr_in lookupaddr; | |
memset(&lookupaddr, 0, sizeof(lookupaddr)); | |
lookupaddr.sin_family = AF_INET; | |
lookupaddr.sin_port = htons(mdb_lookup_port); | |
inet_pton(AF_INET, host, &lookupaddr.sin_addr); | |
if (connect(lookupsock, (struct sockaddr *)&lookupaddr, sizeof(lookupaddr)) < 0) | |
die("connect failed"); | |
socklen_t clntlen = sizeof(clntaddr); | |
FILE *sock1 = fdopen(lookupsock, "r"); | |
while (1) | |
{ | |
int clntsock = accept(serverSocket, (struct sockaddr *)&clntaddr, &clntlen); | |
if (clntsock < 0) | |
die("accept failed"); | |
Client(clntsock, clntaddr, webRoot, lookupsock, sock1); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CC = gcc | |
CFLAGS = -g -Wall | |
LDFLAGS = -g -Wall | |
http-server: http-server.o | |
http-server.o: http-server.c | |
.PHONY:clean | |
clean: | |
rm -f *.o a.out core http-server | |
.PHONY: all | |
all: clean http-server | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment