Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Created March 3, 2026 20:26
Show Gist options
  • Select an option

  • Save peterhellberg/061cf3ae47f4f6941488e89d6296687b to your computer and use it in GitHub Desktop.

Select an option

Save peterhellberg/061cf3ae47f4f6941488e89d6296687b to your computer and use it in GitHub Desktop.
Very basic web server in C
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#define DEFAULT_PORT 9090
#define BUFFER_SIZE 4096
struct mime_map {
const char *ext;
const char *type;
};
static const struct mime_map mime_types[] = {
{"css", "text/css"}, {"html", "text/html"},
{"jpg", "image/jpeg"}, {"js", "application/javascript"},
{"json", "application/json"}, {"png", "image/png"},
{"txt", "text/plain"}, {"wasm", "application/wasm"},
};
static const char *get_mime_type(const char *path) {
const char *ext = strrchr(path, '.');
if (!ext || ext == path)
return "application/octet-stream";
ext++; // skip '.'
for (size_t i = 0; i < sizeof(mime_types) / sizeof(mime_types[0]); i++) {
if (strcasecmp(ext, mime_types[i].ext) == 0) {
return mime_types[i].type;
}
}
return "application/octet-stream";
}
static int send_all(int sock, const void *buf, size_t len) {
size_t total = 0;
const char *p = buf;
while (total < len) {
ssize_t sent = send(sock, p + total, len - total, 0);
if (sent <= 0) {
return -1;
}
total += sent;
}
return 0;
}
static void send_response(int sock, int status, const char *status_text,
const char *content_type, const void *body,
size_t body_len) {
char header[512];
int header_len = snprintf(header, sizeof(header),
"HTTP/1.1 %d %s\r\n"
"Content-Length: %zu\r\n"
"Content-Type: %s\r\n"
"Connection: close\r\n"
"\r\n",
status, status_text, body_len, content_type);
send_all(sock, header, header_len);
if (body && body_len > 0) {
send_all(sock, body, body_len);
}
}
static void send_404(int sock) {
const char *body =
"<html>\n"
"<head>\n"
" <title>404 Not Found</title>\n"
" <style>\n"
" body { background-color: #333; color: #fff; "
"font-family: sans-serif; text-align: center; margin-top: 20%; }\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <h1>404 Not Found</h1>\n"
"</body>\n"
"</html>\n";
send_response(sock, 404, "Not Found", "text/html", body, strlen(body));
}
static void serve_file(int sock, const char *path) {
struct stat st;
FILE *f =
(stat(path, &st) == 0 && S_ISREG(st.st_mode)) ? fopen(path, "rb") : NULL;
if (!f) {
send_404(sock);
return;
}
const char *mime = get_mime_type(path);
char header[512];
int header_len = snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Content-Length: %ld\r\n"
"Content-Type: %s\r\n"
"X-Content-Type-Options: nosniff\r\n"
"Connection: close\r\n\r\n",
(long)st.st_size, mime);
if (header_len <= 0 || send_all(sock, header, header_len) < 0) {
fclose(f);
return;
}
char buf[BUFFER_SIZE];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
if (send_all(sock, buf, n) < 0)
break;
fclose(f);
}
static void handle_client(int sock, const char *root) {
char req[BUFFER_SIZE];
ssize_t len = recv(sock, req, sizeof(req) - 1, 0);
if (len <= 0)
return;
req[len] = '\0';
char method[16], path[512];
if (sscanf(req, "%15s %511s", method, path) != 2) {
send_404(sock);
return;
}
if (strcmp(method, "GET") != 0 || strstr(path, "..")) {
send_404(sock);
return;
}
size_t path_len = strlen(path);
if (path[path_len - 1] == '/') {
if (path_len + strlen("index.html") < sizeof(path)) {
strcat(path, "index.html");
} else {
send_404(sock);
return;
}
}
char fullpath[1024];
snprintf(fullpath, sizeof(fullpath), "%s%s", root, path);
serve_file(sock, fullpath);
}
static int parse_port(int argc, char *argv[]) {
if (argc <= 1)
return DEFAULT_PORT;
char *endptr;
long p = strtol(argv[1], &endptr, 10);
if (*endptr != '\0' || p <= 0 || p > 65535) {
fprintf(stderr, "Invalid port number: %s\n", argv[1]);
exit(1);
}
return (int)p;
}
static const char *parse_root(int argc, char *argv[]) {
if (argc > 2)
return argv[2];
return ".";
}
static int setup_server(int port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
exit(1);
}
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(port)};
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
if (listen(sock, 16) < 0) {
perror("listen");
exit(1);
}
return sock;
}
static int accept_client(int server, char *ip_buf, size_t ip_len,
int *client_port) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client = accept(server, (struct sockaddr *)&client_addr, &len);
if (client < 0) {
perror("accept");
return -1;
}
inet_ntop(AF_INET, &client_addr.sin_addr, ip_buf, ip_len);
*client_port = ntohs(client_addr.sin_port);
return client;
}
int main(int argc, char *argv[]) {
const char *root = parse_root(argc, argv);
int port = parse_port(argc, argv);
int server = setup_server(port);
printf("Serving files from %s on port %d\n", root, port);
while (1) {
char ip[INET_ADDRSTRLEN];
int client_port;
int client = accept_client(server, ip, sizeof(ip), &client_port);
if (client < 0)
continue;
printf("Client %s:%d connected\n", ip, client_port);
{
handle_client(client, root);
shutdown(client, SHUT_WR);
close(client);
}
printf("Client %s:%d disconnected\n", ip, client_port);
}
close(server);
return 0;
}
@peterhellberg
Copy link
Author

$ zig cc web.c -o web && ./web
Serving files from . on port 9090

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment