Skip to content

Instantly share code, notes, and snippets.

@ahmedabouelnaga
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.
#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;
}
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