In this guide we will create simple but scalable “Hello World” HTTP server.
DataKernel uses event-driven programming model. The key component of Datakernel Framework is Eventloop which polls various sources of events and calls corresponding event handlers without blocking the main thread. Eventloop is based on Asynchronous I/O (Java NIO) and runs in single thread, which allows to significantly improve performance and avoid common multithreading concerns, such as synchronization, race conditions, etc.
Most Datakernel modules, including HTTP, are based on Eventloop. Since Eventloop is single-threaded, we cannot use all capacities of modern multi-core processors if we run only one HTTP-server/Eventloop. If we want to load all cores of processor, we should use worker servers and load-balancer to distribute requests between those servers.
In this tutorial we will build the following architecture which is suitable for 4-core processors:
Actually, it’s not a simple task to implement load balancer, worker servers and run them properly. But there are good news: Boot module already supports worker pools, so we can easily write down HTTP-server with similar architecture in a few lines of code.
Firstly, create a folder for application and build an appropriate project structure:
Next, configure your pom.xml file. We will need the following dependencies: datakernel-http, datakernel-boot and some logger (Note: we don’t need to specify eventloop, because it already is a transitive dependency of both datakernel-boot and datakernel-http modules). So your pom.xml should look like following:
Write down a SimpleServlet which will return the web-page with appropriate content
Let’s now consider a Boot Module that will enable us to easily implement multi-worker HTTP-server.
Boot module consists of three main parts:
Service Graph uses dependency tree, built by Google Guice to run services in a proper order. Service Graph considers all dependencies from Guice, determines which of them can be threated as services and then starts those services in a proper way. You just need to extend AbstractModule and write down the dependencies of your app, rest of work Service Graph will do for you.
Configs are a useful extension for properties file. Main features:
using a set of standard converters
specifying default value for property
saving all properties that were used into file
A typical usage of configs looks like following:
where “ofInteger()” is a converter, “port” is a property key and “5577” is a default value.
So let’s extend SimpleModule and write down all the dependencies needed for multi-worker HTTP-server:
Add configs to config.properties:
The last but not least part of Boot Module is Launcher.
Launcher integrates all components together and manages application lifecycle, which consist of the following phases:
wire (injecting dependencies, mostly done by Google Guice)
start (starting services, mostly done by Service Graph)
stop (stopping services, mostly done by Service Graph)
We should extend Launcher, pass Stage and Guice modules as arguments to superclass constructor and override method run() to finish our HTTP-server. In run() we will call awaitShutdown() to enable application stop properly after interruption is made (for example Ctrl+C in unix-like systems).
Congratulations! We’ve done with writing simple HTTP-server. Enter the command below to compile and run it:
Launch your favourite browser and go to “localhost:5577” or just enter the following command to the terminal:
You should see content like following:
If you make this HTTP request several times, worker id will be different, which means load-balancing works pretty well.