Minimal webserver in C++ Here's my test question for C++ programmer job:


Servers only static content, no cgi
Single process, multithreaded
1000 concurrent request at least
Valid http status codes and headers
No external libraries, just STL, POSIX and glibc


Here's my code:

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;

#include &lt;sstream&gt;
#include &lt;vector&gt;

#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;pthread.h&gt;

using namespace std;

    const char *response_200 = "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n\n&lt;html&gt;&lt;body&gt;&lt;i&gt;Hello!&lt;/i&gt;&lt;/body&gt;&lt;/html&gt;";
    const char *response_400 = "HTTP/1.1 400 Bad Request\nContent-Type: text/html; charset=utf-8\n\n&lt;html&gt;&lt;body&gt;&lt;i&gt;Bad Request!&lt;/i&gt;&lt;/body&gt;&lt;/html&gt;";
    const char *response_404 = "HTTP/1.1 404 Not Found\nContent-Type: text/html; charset=utf-8\n\n&lt;html&gt;&lt;body&gt;&lt;i&gt;Not Found!&lt;/i&gt;&lt;/body&gt;&lt;/html&gt;";

void *handle_request(void *pcliefd) 
{
    int cliefd = *(int*)pcliefd;
    delete (int *)pcliefd;

    ssize_t n;
    char buffer[255];
    const char *response;

    n = recv(cliefd, buffer, sizeof(buffer), 0);
    if(n &lt; 0) {
        perror("recv()");
        return 0;
    }

    buffer[n] = 0;
    //printf("recv() %s\n", buffer);

    response = response_400;

    string s(buffer), token;
    istringstream ss(s);
    vector&lt;string&gt; token_list;
    for(int i = 0; i &lt; 3 &amp;&amp; ss; i++) {
        ss &gt;&gt; token;
        //printf("token %d %s\n", i, token.c_str());
        token_list.push_back(token);
    } 

    if(token_list.size() == 3 
            &amp;&amp; token_list[0] == "GET" 
            &amp;&amp; token_list[2].substr(0, 4) == "HTTP") {
        if(token_list[1] == "/index.html") {
            response = response_200;
        } else {
            response = response_404;
        }
    }

    n = write(cliefd, response, strlen(response));
    if(n &lt; 0) {
        perror("write()"); 
        return 0;
    }

    close(cliefd);
    return 0;
}

int main(int argc, const char *argv[])
{
    int sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    struct sockaddr_in servaddr;
    pthread_t thread;

    if(sockfd &lt; 0) {
        perror("socket() error");
        exit(EXIT_FAILURE); 
    }

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8080);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(sockfd, (struct sockaddr *)&amp;servaddr, sizeof(servaddr)) &lt; 0) {
        perror("bind()");
        exit(EXIT_FAILURE); 
    }


    if(listen(sockfd, 1000) &lt; 0) {
        perror("listen()");
        exit(EXIT_FAILURE); 
    }


    struct sockaddr_storage clieaddr;
    int cliefd;
    char s[INET_ADDRSTRLEN];
    socklen_t cliesize;

    while(true) {

        cliesize = sizeof(clieaddr);
        cliefd = accept(sockfd, (struct sockaddr *)&amp;clieaddr, &amp;cliesize);
        if(cliefd &lt; 0) {
            perror("accept()");
            continue;
        }

        inet_ntop(clieaddr.ss_family, (void *)&amp;((struct sockaddr_in *)&amp;clieaddr)-&gt;sin_addr, s, sizeof(s));
        printf("accept() %s\n", s);


        int *pcliefd = new int;
        *pcliefd = cliefd;
        if(true) {
            if(pthread_create(&amp;thread, 0, handle_request, pcliefd) &lt; 0) {
                perror("pthread_create()");
            } 
        } else {
            handle_request(pcliefd);
        }
    }

    return 0;
}
The server announces HTTP 1.1, therefore it must support HTTP 1.1 features, most importantly pipelining (client has a right to send multiple requests over the same socket). In any case, if you chose to close the client socket immediately you should inform client with a Connection: close header.
You expect to consume a complete request with a single read. In a streaming world of TCP you never know how many bytes has actually been consumed. You should be prepared to get as little as one byte at a time (same goes for send/write).

A correct way to read the request is to read it in the loop until the end of headers (i.e. an empty string) is seen.
Network program must be defensive. A malicious client can play tricks with your server.

For example, it may never send anything; eventually the server would run out of file descriptors (or pthreads), resulting in denial of service. Another client may close the socket immediately upon sending a request; the server receives a SIGPIPE and dies with a core dump.