A threaded web server in 60 lines

I wrote this toy web server to play with sockets and threads. The web pages are actually in the code, just to make it easy (and self contained). One thread listens for incoming requests and forwards the request to a thread pool. The worker threads read from a request queue, the thread whose read succeeds gets the job. It then processes the request, writes the response and goes back to reading the request queue.

I added a CGI form to play with blowing apart a CGI query string into a hash table (2 lines but doesn’t handle all character encodings) and giving the threads a little more to do.

You can download the source from http://zenkinetic.com/Documents/httpServer.zkl (use Save As, otherwise the embedded HTML will really messes things up).

ztws

const PORT = 8080;
const SERVLET_THREADS = 4;

  /* ******************************************************** */
  /* ********************** The Server ********************** */
  /* ******************************************************** */

    // A class to process requests from clients (eg browsers)
    // in a thread. Requests are received via a pipe,
    //
which feeds all Servlet threads.
class Servlet {
   fcn init(jobPipe) { self.launch(jobPipe); }
   fcn liftoff(jobPipe) {
      while(1) {    // read request, write response, repeat
         socket := jobPipe.read();
         if(socket.wait(60) != 1)    // what is Chrome doing?
            { socket.close(); continue; }
         if (request := socket.read())
           
try { processRequest(request,socket); } catch {}
      }
   }
   fcn splashdown(h,e)
    
{ println(“Servlet died before its time”); }
}

    // map requested page to fcn
var getMap = D(“/”,homePage, “/favicon.ico”,favicon,
    “/testPage”,testPage);

fcn processRequest(request,socket) {
   println(“vvvvvvvvv “,vm,”\n”,
           
request.text,”\n^^^^^^^^^^^”);
   req := request.text.split(“\r\n”);
   method,page := req[0].split();

   response := “”;
   switch(method){      // GET, HEAD, POST, etc
      case(“GET”) {
         response =
            ( if (n:=page.find(“?”)) cgi(page[n+1,*]) else
                               getMap.find(page,homePage)() );
      }
      case(“HEAD”) { response = responseHeader(); }
//      else do something
   }
   socket.write(response); socket.close();    // no Keep-Alive
}

      //////////////// Start the server ////////////////////

var jobPipe = Thread.Pipe();    // a queue of requests
do(SERVLET_THREADS) { Servlet(jobPipe) }  // start threads

    // Create the HTTP server listen socket
    // Sits here forever passing client HTTP connects
    //  
to Servlets
serverSocket := Network.TCPServerSocket.open(PORT);
println(“HTTP server started at “,
    serverSocket.addr, “:”, serverSocket.port);
serverSocket.listen(fcn(socket) { jobPipe.write(socket); });

/* ******************************************************** */
/* ********************* The Web Site ********************* */
/* ******************************************************** */

fcn cgi(queryString) {   // GET /?value=8&fact=calc HTTP/1.1
   args := queryString.replace(“+”,” “);
   args = args.split(“&”).apply(“split”,”=”).toDictionary();
   try {
      x := BigNum(args[“value”]);
      if (x > 0) return(homePage(x,”%,d”.fmt(factTail(x))));
   } catch {}
   homePage();
}

The rest of the code (the web site) is in the download link.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s