This post is the conclusion of HTTP server from scratch. Previous post about modularization can be found here.
In this post we will make our webserver somewhat more configurable. We will add one more switch to the
server.properties to point to a external directory that will be used to serve the documents from. Since it is a good idea to support compression of the documents that we send, we will add support to gzip compression and finally, we will update our build configuration to generate a property distribution.
The new configuration property that we will add is the
web.root, default setting will point to
server.root, when running it outside of the IDE we will be able to use
Before we can do that we have add one more HTTP status code
400 Bad Request. According to the HTTP spec, the resource the user-agent can request should be a absolute path and not a relative path. As a defensive approach we will sanitize the incoming request to contain only absolute paths, otherwise we will return with HTTP status code
400 Bad Request.
So far we have been only serving a single file,
index.html from the root of
server.root, but a web server should be able to serve other static documents from root and sub-directories. Typically all CSS documents will be located in
We will improve our logging to include the requested resource in the output. To do that we will add an additional String argument
resource to the method
log. Now we will know what was requested and its corresponding status.
Resolving the requested resource
To help resolve the requested resource, we will add a helper method
resolveResource, which will take a String and return a String. In this method we will walk the requested resource path and skipping relative paths like
Other helper methods
We need other helper methods which will parse the request headers, determine if the User-Agent can support response compression and get the correct type of OutputStream to which we will write the response.
We will also update the existing helper method,
writeResponseHeaders to take an additional argument describing the MIME type of the response. Earlier we had hardcoded the MIME type as
text/html. To help determine the MIME type of the document, Java already has a static method
probeContentType in Files class.
Finally we are in a position to refactor the
run method to get to what we set out to achieve in the beginning of the post.
We will not know the response type until we have parsed the headers, so we will not be able to use the
try-with-resources for the
dataOut OutputStream. So we will have to do it the old fashioned way. After parsing the headers we determine how to respond to the request the corresponding Status code. Finally, we will create either a BufferedOutputStream or GZIPOutputStream depending on what the User-Agent accepts.
Building a distribution
To let gradle know that we have to package the
server directory, which is our default
server.root, we will update our
build.gradle and add the following.
Now if execute
gradle distTar or
gradle distZip we will get a nice distribution ready tar or zip file.
Code so far
Code for this post and the rest of the posts in the series can be found on GitHub at https://github.com/cx0der/http-server. Branch
step-3 contains the modifications from this post.