Handling HTTP

To make the leap from a generic framework to an actual server, we’ll need to implement some sort of behavior or protocol that we can test. Fortunately for us, we already have the tools at hand to implement HTTP.

Beyond TCP’s basic streaming capabilities, HTTP adds a request-response messaging pattern, URLs, query parameters, and other useful metadata. HTTP is also universally supported, can pass through most firewall and NAT appliances, and is easy to integrate with browser-based applications.

Much as with our HTTP client code, there are high-quality C libraries available for server-side HTTP parsing;[28] for now, though, we’ll stick with our DIY philosophy and repurpose our HTTP parsing code from Chapter 3, Writing a Simple HTTP Client.

We can just plug the request-parsing functions into a modified handle_connection() function:

HTTPServer/httpserver/HTTPServer.scala
 import​ ​Parsing._
 def​ handle_connection(conn_socket​:​​Int​, max_size​:​​Int​ = 1024)​:​ ​Unit​ = {
 while​(​true​) {
  parse_request(conn_socket) ​match​ {
 case​ Some(request) ​=>
 val​ response ​=​ handleRequest(request)
  write_response(conn_socket, response)
 return
 case​ None ​=>
 return
  }
  }
 }

As you see, we reused the parse_request() function intact, as well as the corresponding HttpRequest and HttpResponse types.

Now all we need to do is add some actual application logic: a handle_request() function that transforms a request into a response. We’ll simply return a 200 OK code and print out some diagnostics about the request:

HTTPServer/httpserver/HTTPServer.scala
 def​ handleRequest(request​:​​HttpRequest​)​:​ ​HttpResponse​ = {
 val​ headers ​=​ Map(​"Content-type"​ -> ​"text/html"​)
 val​ body ​=​ s​"received ${request.toString}\n"
 return​ HttpResponse(200, headers, body)
 }

Few real-world applications will directly generate a response from only the request parameters; instead, file servers will return a file based on the request URI, model-view-controller applications will query a database, and so-called microservices often query many additional services to collect the data necessary to compose a response.

Likewise, it’s common for a routing function to map requests onto more specialized handlers based on the request URL. We’ll stick to a single HttpRequest => HttpResponse function for now, but we’ll revisit this topic in much more detail later in this book.