This is a continuation in the series of post to build a static HTTP server from scratch. Previous post about Unit testing can be found here.
In this post we are going to refactor the code a little bit to make it somewhat more modular and configurable. Modular in the sense, break out from a single class into smaller classes based on functionality. Finally remove hard-coded configurations.
Break it down
At this point our entire code resides in one class HttpServer
. We will break this down into two, an Interface Http
and HttpRequestHandler
. The interface Http
is going to hold some of the constants like, status codes and header names and each instance of HttpRequestHandler
will service one incoming request. After the refactoring our main class HttpServer
will read the server configuration, based on the configuration create a ServerSocket
and listen to requests. When a request arrives, creates an instance of HttpRequestHandler and spawn a new thread to process the request.
Default configuration
Like any configurable application, our server will also have a default set of configuration and user can override them using command line arguments. For default configuration we will create a server.properties
file with the following contents in the resources directory.
# default server port
server.port=8080
# default hostname
server.name=localhost
# default server root
server.root=server
# Server string
server.response.version=Http Server v1.0
We will move all the “loose” HTML files into a directory called server
in the root of the repository. This directory will act as our fallback document root. Document root is the directory from which requests will be served.
Constants
Next we will create our Interface Http
which will define all the constants that we need. Inside this interface we will define three other interfaces Status
, Method
and Header
.
Request Handler
Each new request will be served by a new instance of HttpRequestHandler
. Create this class and move all the logic for the moment into the run
method. To help us serve a request we define four helper methods parseRequestHeaders
, readFile
, writeResponseHeaders
, and log
as shown below.
private Map<String, String> parseRequestHeaders(BufferedReader in) throws IOException { | |
Map<String, String> headers = new HashMap<>(); | |
String line; | |
while ((line = in.readLine()) != null && !line.isEmpty()) { | |
int idx = line.indexOf(':'); | |
if (idx > 0) { | |
headers.put(line.substring(0, idx).toLowerCase(), line.substring(idx + 1).trim()); | |
} | |
} | |
return headers; | |
} | |
private byte[] readFile(File file) throws IOException { | |
byte[] res; | |
try (FileInputStream fis = new FileInputStream(file)) { | |
int length = (int) file.length(); | |
res = new byte[length]; | |
//noinspection ResultOfMethodCallIgnored | |
fis.read(res, 0, length); | |
} | |
return res; | |
} | |
private void writeResponseHeaders(PrintWriter out, String protocol, String status, String date, int length) { | |
out.println(protocol + status); | |
out.println(SERVER + config.getProperty(SERVER_VERSION_PARAM)); | |
out.println(DATE + date); | |
out.println(CONTENT_TYPE + "text/html; charset=utf-8"); | |
out.println(CONTENT_LENGTH + length); | |
out.println(CONNECTION + "close"); | |
out.println(); | |
out.flush(); | |
} | |
private void log(InetAddress remoteAddress, String date, String request, String status, String ua) { | |
logger.printf("%s [%s] \"%s\"%s %s\n", remoteAddress.getHostAddress(), date, request, status, ua); | |
} |
HTTP header Connection
Most browsers will send HTTP header Connection: keep-alive
. This is to use the same connection to request multiple resources. A quick way to speed up page load performance. Since our server only handles single resource request per connection, in our response we will set the header Connection: close
to indicate that we are closing this connection.
Next steps
Code up to this point can be found on github branch step-2. After this we will refactor to support new config property web.root
. Also the run method’s try/catch
block is very big, that will also be broken down into finer chunks for better error handling to send a 500 Internal Server Error
when an exceptions occurs.
One thought on “HTTP server from scratch: Modularization”