Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
A very simple HTTP server in C, for Unix, using fork()

Pico HTTP Server in C

This is a very simple HTTP server for Unix, using fork(). It's very easy to use

How to use

  1. include header httpd.h
  2. write your route method, handling requests.
  3. call serve_forever("12913") to start serving on port 12913

See main.c, an interesting example.

To log stuff, use fprintf(stderr, "message");

View httpd.h for more information

based on

#include "httpd.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#define CONNMAX 1000
static int listenfd, clients[CONNMAX];
static void error(char *);
static void startServer(const char *);
static void respond(int);
typedef struct { char *name, *value; } header_t;
static header_t reqhdr[17] = { {"\0", "\0"} };
static int clientfd;
static char *buf;
void serve_forever(const char *PORT)
struct sockaddr_in clientaddr;
socklen_t addrlen;
char c;
int slot=0;
"Server started %shttp://\n",
// Setting all elements to -1: signifies there is no client connected
int i;
for (i=0; i<CONNMAX; i++)
// Ignore SIGCHLD to avoid zombie threads
// ACCEPT connections
while (1)
addrlen = sizeof(clientaddr);
clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);
if (clients[slot]<0)
perror("accept() error");
if ( fork()==0 )
while (clients[slot]!=-1) slot = (slot+1)%CONNMAX;
//start server
void startServer(const char *port)
struct addrinfo hints, *res, *p;
// getaddrinfo for host
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if (getaddrinfo( NULL, port, &hints, &res) != 0)
perror ("getaddrinfo() error");
// socket and bind
for (p = res; p!=NULL; p=p->ai_next)
int option = 1;
listenfd = socket (p->ai_family, p->ai_socktype, 0);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
if (listenfd == -1) continue;
if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) break;
if (p==NULL)
perror ("socket() or bind()");
// listen for incoming connections
if ( listen (listenfd, 1000000) != 0 )
perror("listen() error");
// get request header
char *request_header(const char* name)
header_t *h = reqhdr;
while(h->name) {
if (strcmp(h->name, name) == 0) return h->value;
return NULL;
//client connection
void respond(int n)
int rcvd, fd, bytes_read;
char *ptr;
buf = malloc(65535);
rcvd=recv(clients[n], buf, 65535, 0);
if (rcvd<0) // receive error
fprintf(stderr,("recv() error\n"));
else if (rcvd==0) // receive socket closed
fprintf(stderr,"Client disconnected upexpectedly.\n");
else // message received
buf[rcvd] = '\0';
method = strtok(buf, " \t\r\n");
uri = strtok(NULL, " \t");
prot = strtok(NULL, " \t\r\n");
fprintf(stderr, "\x1b[32m + [%s] %s\x1b[0m\n", method, uri);
if (qs = strchr(uri, '?'))
*qs++ = '\0'; //split URI
} else {
qs = uri - 1; //use an empty string
header_t *h = reqhdr;
char *t, *t2;
while(h < reqhdr+16) {
char *k,*v,*t;
k = strtok(NULL, "\r\n: \t"); if (!k) break;
v = strtok(NULL, "\r\n"); while(*v && *v==' ') v++;
h->name = k;
h->value = v;
fprintf(stderr, "[H] %s: %s\n", k, v);
t = v + 1 + strlen(v);
if (t[1] == '\r' && t[2] == '\n') break;
t++; // now the *t shall be the beginning of user payload
t2 = request_header("Content-Length"); // and the related header if there is
payload = t;
payload_size = t2 ? atol(t2) : (rcvd-(t-buf));
// bind clientfd to stdout, making it easier to write
clientfd = clients[n];
dup2(clientfd, STDOUT_FILENO);
// call router
// tidy up
//Closing SOCKET
shutdown(clientfd, SHUT_RDWR); //All further send and recieve operations are DISABLED...
#ifndef _HTTPD_H___
#define _HTTPD_H___
#include <string.h>
#include <stdio.h>
//Server control functions
void serve_forever(const char *PORT);
// Client request
char *method, // "GET" or "POST"
*uri, // "/index.html" things before '?'
*qs, // "a=1&b=2" things after '?'
*prot; // "HTTP/1.1"
char *payload; // for POST
int payload_size;
char *request_header(const char* name);
// user shall implement this function
void route();
// some interesting macro for `route()`
#define ROUTE_START() if (0) {
#define ROUTE(METHOD,URI) } else if (strcmp(URI,uri)==0&&strcmp(METHOD,method)==0) {
#define ROUTE_END() } else printf(\
"HTTP/1.1 500 Not Handled\r\n\r\n" \
"The server has no handler to the request.\r\n" \
#include "httpd.h"
int main(int c, char** v)
return 0;
void route()
printf("HTTP/1.1 200 OK\r\n\r\n");
printf("Hello! You are using %s", request_header("User-Agent"));
printf("HTTP/1.1 200 OK\r\n\r\n");
printf("Wow, seems that you POSTed %d bytes. \r\n", payload_size);
printf("Fetch the data using `payload` variable.");
all: server
@rm -rf *.o
@rm -rf server
server: main.o httpd.o
gcc -o server $^
main.o: main.c httpd.h
gcc -c -o main.o main.c
httpd.o: httpd.c httpd.h
gcc -c -o httpd.o httpd.c
Copy link

debuti commented Nov 22, 2018

Hello! What license is this code using?


Copy link

serefercelik commented Feb 20, 2019

How can i payload? Payload is not working..

Copy link

sjm1982 commented Mar 13, 2019

thank you so much

Copy link

foxweb commented Mar 26, 2019

\r\n? Unix?

Copy link

foxweb commented Mar 27, 2019

Please check it out 🌚

Copy link

RomanStone commented Apr 18, 2019

\r\n? Unix?

YES. [CRLF] (HTTP protocol, no matter Unix or other)
2.1. Client/Server Messaging

Copy link

Minssc commented Jun 10, 2019

slots are not freed since process is forked. once CONNMAX is reached, the program will hang

Copy link

chienminglo commented Sep 11, 2019

How can i payload? Payload is not working..

it looks "char* t" declare twice so payload data is not point correctly

Copy link

thuan-olli commented Oct 8, 2019

How can i payload? Payload is not working..

it looks "char* t" declare twice so payload data is not point correctly

I think we should change as below:
t = strtok(NULL, "\r\n");
instead of t++
at the line 168 http.c file.

Copy link

chientranse commented Dec 18, 2019

@laobubu hello my friend, what is the license on using this code.

Copy link

glenkleidon commented Mar 3, 2020

Thanks to Abijeet way back in 2012 and to you laobubu. I have been re-introducing myself to C after 30 years or so using this project.
I have forked the GIST and started to a new enterprise style project - so its too big for a GIST GitHub - pico. This will work out of the box with VSCode on Linux.

A number of important things to note:

  1. When forking, the file handles for ACCEPT and SOCKET need to be closed depending on the Parent or Child. On the server side, it no longer needs the accepted FD close(clients[slot] ) and the client no longer needs the listening FD close(listenfd)
if ( fork()==0 )
	// I am now the client - close the listener: client doesnt need it
} else
	// I am still the server - close the accepted handle: server doesnt need it.

If you dont do this, the server will maintain file handle on the listener making it hard to restart the service and if you are still debugging, it will leave behind TCP close waits

  1. As someone already pointed out, A way is needed to communicate back to he parent that the slot is no longer busy and can be re-used.
  2. HTTP1.1 is KEEP-ALIVE by default. So, if the client does not disconnect, they may want to send a new request on the same connection. I am not 100% this is working right - I will update if this needs fixing.

overall- pretty nice workflow! BTW -it was pretty far sited to use that routing method back in 2012! That is pretty much the standard approach now.

Thanks for the effort!

Copy link

renehorstmann commented Oct 20, 2020

There might be a little overflow error while reading the header:
if "recv" recieves the complete buffer size of 65535, "rcvd" will be set to 65535, so:

"buf[rcvd] = '\0';"

would cause an overflow.

Copy link

mesaglio commented Oct 29, 2020

Hi! Greate job.

It would be great, that you resolve a small memory leak from buf variable.

You can just add one line, at the end of respond function like


Copy link

glenkleidon commented Oct 30, 2020

Copy link

laobubu commented Nov 8, 2020

Copy link

VillainMan commented Feb 15, 2021

\r\n? Unix?

It's not about Unix. That is the standard for http. You can send http messages from any device, AFAIK the standard only specifies message formatting and TCP connections.

Copy link

glenkleidon commented Feb 15, 2021

Copy link

Kepsz commented May 6, 2021

Hi, thank you for this code!
I was able to get it working on a Raspberry, using VS2019 with remote debug. But there are 2 things that needs to be corrected for a successful compile.

First: in line 131, buf = malloc(65535); -malloc needs casting. This line should be:
buf = (char *) malloc(65535);

Second: there are variables declared in the header that causes "multiple declared" errors in the linker. This should be avoided by copiying those variables into the c file, while adding "extern" to them in the header.

So these should be in the first part of the c file:

char* method,    // "GET" or "POST"
* uri,       // "/index.html" things before '?'
* qs,        // "a=1&b=2"     things after  '?'
* prot;      // "HTTP/1.1"

char* payload;     // for POST
int      payload_size;

While line 12..18 in the header should be:

extern char* method,    // "GET" or "POST"
* uri,       // "/index.html" things before '?'
* qs,        // "a=1&b=2"     things after  '?'
* prot;      // "HTTP/1.1"

extern char* payload;     // for POST
extern int      payload_size;

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