Skip to content

Instantly share code, notes, and snippets.

@laobubu
Last active March 23, 2024 05:28
Show Gist options
  • Save laobubu/d6d0e9beb934b60b2e552c2d03e1409e to your computer and use it in GitHub Desktop.
Save laobubu/d6d0e9beb934b60b2e552c2d03e1409e to your computer and use it in GitHub Desktop.
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 http://blog.abhijeetr.com/2010/04/very-simple-http-server-writen-in-c.html

#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;
printf(
"Server started %shttp://127.0.0.1:%s%s\n",
"\033[92m",PORT,"\033[0m"
);
// Setting all elements to -1: signifies there is no client connected
int i;
for (i=0; i<CONNMAX; i++)
clients[i]=-1;
startServer(PORT);
// Ignore SIGCHLD to avoid zombie threads
signal(SIGCHLD,SIG_IGN);
// ACCEPT connections
while (1)
{
addrlen = sizeof(clientaddr);
clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);
if (clients[slot]<0)
{
perror("accept() error");
}
else
{
if ( fork()==0 )
{
respond(slot);
exit(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");
exit(1);
}
// 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()");
exit(1);
}
freeaddrinfo(res);
// listen for incoming connections
if ( listen (listenfd, 1000000) != 0 )
{
perror("listen() error");
exit(1);
}
}
// 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;
h++;
}
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;
h++;
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);
close(clientfd);
// call router
route();
// tidy up
fflush(stdout);
shutdown(STDOUT_FILENO, SHUT_WR);
close(STDOUT_FILENO);
}
//Closing SOCKET
shutdown(clientfd, SHUT_RDWR); //All further send and recieve operations are DISABLED...
close(clientfd);
clients[n]=-1;
}
#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_GET(URI) ROUTE("GET", URI)
#define ROUTE_POST(URI) ROUTE("POST", URI)
#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" \
);
#endif
#include "httpd.h"
int main(int c, char** v)
{
serve_forever("12913");
return 0;
}
void route()
{
ROUTE_START()
ROUTE_GET("/")
{
printf("HTTP/1.1 200 OK\r\n\r\n");
printf("Hello! You are using %s", request_header("User-Agent"));
}
ROUTE_POST("/")
{
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.");
}
ROUTE_END()
}
all: server
clean:
@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
@renehorstmann
Copy link

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.

@mesaglio
Copy link

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

free(buf);

@glenkleidon
Copy link

glenkleidon commented Oct 30, 2020 via email

@laobubu
Copy link
Author

laobubu commented Nov 8, 2020 via email

@nanigav
Copy link

nanigav 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.

@glenkleidon
Copy link

glenkleidon commented Feb 15, 2021 via email

@Kepsz
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;

@ruanhao
Copy link

ruanhao commented Sep 30, 2023

another thing to note: make sure to flush stdout before calling serve_forever.

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