Thursday, April 15, 2010

A Very Simple HTTP Server writen in C

This is a part of the semester assignment. I felt like sharing it because I couldnt find any simple code like this on internet. This is in no way a complete web server and is just a simple one day hack to build something that can talk to a web browser.


  • This code simply creates a web server with root in the current working directory and default port as 10000.
  • Can handle a maximum of 1000 clients.
  • fork() is used to handle each client. I know it's not a very efficient way of doing it but this code is just to demonstrate a very simple use-case and it's okay to be sloppy and miss details. 







/*
AUTHOR: Abhijeet Rastogi (http://www.google.com/profiles/abhijeet.1989)

This is a very simple HTTP server. Default port is 10000 and ROOT for the server is your current working directory..

You can provide command line arguments like:- $./a.aout -p [port] -r [path]

for ex. 
$./a.out -p 50000 -r /home/
to start a server at port 50000 with root directory as "/home"

$./a.out -r /home/shadyabhi
starts the server at port 10000 with ROOT as /home/shadyabhi

*/

#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<signal.h>
#include<fcntl.h>

#define CONNMAX 1000
#define BYTES 1024

char *ROOT;
int listenfd, clients[CONNMAX];
void error(char *);
void startServer(char *);
void respond(int);

int main(int argc, char* argv[])
{
    struct sockaddr_in clientaddr;
    socklen_t addrlen;
    char c;    
    
    //Default Values PATH = ~/ and PORT=10000
    char PORT[6];
    ROOT = getenv("PWD");
    strcpy(PORT,"10000");

    int slot=0;

    //Parsing the command line arguments
    while ((c = getopt (argc, argv, "p:r:")) != -1)
        switch (c)
        {
            case 'r':
                ROOT = malloc(strlen(optarg));
                strcpy(ROOT,optarg);
                break;
            case 'p':
                strcpy(PORT,optarg);
                break;
            case '?':
                fprintf(stderr,"Wrong arguments given!!!\n");
                exit(1);
            default:
                exit(1);
        }
    
    printf("Server started at port no. %s%s%s with root directory as %s%s%s\n","\033[92m",PORT,"\033[0m","\033[92m",ROOT,"\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);

    // ACCEPT connections
    while (1)
    {
        addrlen = sizeof(clientaddr);
        clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);

        if (clients[slot]<0)
            error ("accept() error");
        else
        {
            if ( fork()==0 )
            {
                respond(slot);
                exit(0);
            }
        }

        while (clients[slot]!=-1) slot = (slot+1)%CONNMAX;
    }

    return 0;
}

//start server
void startServer(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)
    {
        listenfd = socket (p->ai_family, p->ai_socktype, 0);
        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);
    }
}

//client connection
void respond(int n)
{
    char mesg[99999], *reqline[3], data_to_send[BYTES], path[99999];
    int rcvd, fd, bytes_read;

    memset( (void*)mesg, (int)'\0', 99999 );

    rcvd=recv(clients[n], mesg, 99999, 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
    {
        printf("%s", mesg);
        reqline[0] = strtok (mesg, " \t\n");
        if ( strncmp(reqline[0], "GET\0", 4)==0 )
        {
            reqline[1] = strtok (NULL, " \t");
            reqline[2] = strtok (NULL, " \t\n");
            if ( strncmp( reqline[2], "HTTP/1.0", 8)!=0 && strncmp( reqline[2], "HTTP/1.1", 8)!=0 )
            {
                write(clients[n], "HTTP/1.0 400 Bad Request\n", 25);
            }
            else
            {
                if ( strncmp(reqline[1], "/\0", 2)==0 )
                    reqline[1] = "/index.html";        //Because if no file is specified, index.html will be opened by default (like it happens in APACHE...

                strcpy(path, ROOT);
                strcpy(&path[strlen(ROOT)], reqline[1]);
                printf("file: %s\n", path);

                if ( (fd=open(path, O_RDONLY))!=-1 )    //FILE FOUND
                {
                    send(clients[n], "HTTP/1.0 200 OK\n\n", 17, 0);
                    while ( (bytes_read=read(fd, data_to_send, BYTES))>0 )
                        write (clients[n], data_to_send, bytes_read);
                }
                else    write(clients[n], "HTTP/1.0 404 Not Found\n", 23); //FILE NOT FOUND
            }
        }
    }

    //Closing SOCKET
    shutdown (clients[n], SHUT_RDWR);         //All further send and recieve operations are DISABLED...
    close(clients[n]);
    clients[n]=-1;
}

13 comments:

  1. heyy.. that's my code that u've modified!! :)) :))
    good to see my code on ur blog !!!! :D :D
    nice workarounds with args stuff..

    ReplyDelete
  2. Thanks for sharing

    ReplyDelete
  3. thank you.
    it works perfect!!.
    tanks your for sharing.!
    I'll use this for my OS proyect.!

    ReplyDelete
  4. Francisco Corrales MoralesSeptember 18, 2012 at 12:26 AM

    I have a question.
    how difficult would it be to change the server, so it accepts the solicitudes in FIFO, fork(), or in new Threads..?
    can this be done?
    please reply to my email.!
    Thankyou.!

    ReplyDelete
  5. Bonjour j'écris mon
    propre serveur web en c au juste base64.c est utiliser pour encoder les
    mots de passe que je vais placer dans le fichier .htpasswd se trouvant
    dans mon répertoire protéger admin



    CE que je voudrais c'est un code de statut 401 pour générer la fenètre d'authentification au client lorsque cet dernier voudra accéder a
    admin



    PAR la suite je devrais trouver un code qui va récupérer le login et le passwd saisi par le client pour le comparer avec le contenu de
    .htpasswd

    ReplyDelete
  6. mon code source ci dessous

    ReplyDelete
  7. hi

    On Unix-based Mac OS X, how can I solve I receive this error when I want to compile with gcc?

    batuhangoksu:~ batuhangoksu$ gcc /Users/batuhangoksu/Desktop/httpd.c -o httpd
    Undefined symbols for architecture x86_64:
    "_error", referenced from:
    _main in ccDeKhWT.o
    ld: symbol(s) not found for architecture x86_64
    collect2: ld returned 1 exit status

    ReplyDelete
  8. This server is really good implementation of handling every request with new process.
    If you want more efficient implementation check this https://github.com/ashishc/Http-Server#!

    ReplyDelete
  9. Your code is great however there's a classical network programming error in your code with this line


    rcvd=recv(clients[n], mesg, 99999, 0);

    Here you assume, that after this callee have returned, the buffer mesg is filled with complete request header.
    Well, it will happen (very often) when you test it on localhost. However network latences can be quite huge, so your recv call can return only a part of the header. Try connecting to this server with a terminal (eg. putty) and sending a GET request by hand, char by char. The recv will exits with one character received.



    Hope this helps,
    Tomek

    ReplyDelete
  10. I have implemented this server. Now how can i get connect client. Which address i have to call.

    ReplyDelete
  11. Hi, May i ask why clients[n]=-1 in the end of respond in child process can affect the parent process ?

    ReplyDelete