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

@peterhellberg
Copy link
Author

#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
#include <netinet/in.h>
#include <signal.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 65536

struct mime_map {
  const char *ext;
  const char *type;
};

static const struct mime_map mime_types[] = {
    {"css", "text/css"},
    {"c", "text/x-c"},
    {"html", "text/html"},
    {"jpg", "image/jpeg"},
    {"js", "application/javascript"},
    {"json", "application/json"},
    {"png", "image/png"},
    {"txt", "text/plain"},
    {"wasm", "application/wasm"},
};

static void url_decode(char *dst, const char *src) {
  while (*src) {
    if (*src == '%' && src[1] && src[2]) {
      char hex[3] = {src[1], src[2], 0};
      *dst++ = (char)strtol(hex, NULL, 16);
      src += 3;
    } else if (*src == '+') {
      *dst++ = ' ';
      src++;
    } else {
      *dst++ = *src++;
    }
  }

  *dst = '\0';
}

static const char *get_mime_type(const char *path) {
  const char *ext = strrchr(path, '.');

  if (!ext || ext == path)
    return "application/octet-stream";

  ext++;

  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) {
      if (errno == EINTR)
        continue;
      return -1;
    }

    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) {
      fprintf(stderr, "Warning: client closed connection prematurely\n");
      break;
    }

  fclose(f);
}

static void handle_client(int sock, const char *root) {
  char req[BUFFER_SIZE];
  ssize_t len;

  do {
    len = recv(sock, req, sizeof(req) - 1, 0);
  } while (len < 0 && errno == EINTR);

  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) {
    send_404(sock);
    return;
  }

  char decoded[512];
  url_decode(decoded, path);

  if (strstr(decoded, "..") || decoded[0] != '/') {
    send_404(sock);
    return;
  }

  size_t path_len = strlen(decoded);
  if (path_len > 0 && decoded[path_len - 1] == '/') {
    if (path_len + strlen("index.html") < sizeof(decoded))
      strcat(decoded, "index.html");
    else {
      send_404(sock);
      return;
    }
  }

  char fullpath[PATH_MAX];
  if (snprintf(fullpath, sizeof(fullpath), "%s%s", root, decoded) >=
      (int)sizeof(fullpath)) {
    send_404(sock);
    return;
  }

  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;

  memset(&addr, 0, sizeof(addr));

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(port);

  if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
    perror("bind");
    exit(1);
  }

  if (listen(sock, SOMAXCONN) < 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[]) {
  signal(SIGPIPE, SIG_IGN);

  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;
}

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