Recently, I was tasked with developing a load generation tool on top of Twitter’s open source Iago project. I initially validated the request rates of the app using a separate local Play! app as a victim server with restful endpoints summing the requests. But.. this setup wasn’t going to cut it within my acceptance test suite.
Solution: Embedded Jetty Server.
Goal
- Programmatically stand up a web server with minimal restful endpoints/routes as concise as possible.
Overview
For this example our Jetty server will act as a “Counter” and have the following characteristics:
- Listen on a random port
- Two restful endpoints
- /increment
- Will increment the counter by one and return the new count in the response body
- /reset
- Will reset the counter back to zero
- /increment

Prerequisites
- An SBT/Maven based Scala Project
- Scala version 2.11.8
Note: Setting up a project is out of the scope of this article
Steps:
1. Add Project Dependencies
Jetty has changed ownership a few times(currently Eclipse) and you can get Jetty working in a number of ways, but this example assumes the latest Jetty release from Eclipse.
Maven
<dependency> | |
<groupId>org.eclipse.jetty</groupId> | |
<artifactId>jetty-server</artifactId> | |
<version>9.3.12.v20160915</version> | |
</dependency> | |
<dependency> | |
<groupId>org.eclipse.jetty</groupId> | |
<artifactId>jetty-servlet</artifactId> | |
<version>9.3.12.v20160915</version> | |
</dependency> |
build.sbt
libraryDependencies ++= Seq( | |
"org.eclipse.jetty" % "jetty-servlet" % "9.3.12.v20160915", | |
"org.eclipse.jetty" % "jetty-server" % "9.3.12.v20160915" | |
) |
2. Create a Jetty manager Object
First thing we’ll do is create an Object that will contain the Jetty Server, configuration variables, and the Servlets (hold the route definitions). For this example I named it JettyExample.
Since the server will have two functions:
- Incrementing the counter
- Resetting the counter
we should define the string literals for those routes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object JettyExample { | |
val incrementRoute = "/increment" | |
val resetRoute = "/reset" | |
def main(args: Array[String]) = { | |
} | |
} |
3. Add Helper Functions
Add a createServer() function and assign an internally global server variable to a createServer() call. The Server object will need an import reference as well.
Tip: You could add action methods similar to createServer() that Start or Stop the server, or execute handler functionality programmatically rather than through web requests.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.eclipse.jetty.server.Server | |
object JettyExample { | |
val server = createServer() | |
{…} | |
def createServer() = new Server(0) // 0 for random port | |
} |
Since the server was told to start on a random port, we need a function that grabs this port from the server instance. We will use this to print the port number later. Also, add the import reference to NetworkConnector.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.eclipse.jetty.server.{NetworkConnector, Server} | |
object JettyExample{ | |
{…} | |
def port() = { | |
val conn = server.getConnectors()(0).asInstanceOf[NetworkConnector] | |
conn.getLocalPort() | |
} | |
} |
4. Create the Servlets
Servlet Container
CREATE THE SERVLETS! Wait, wait, wait…. first lets create a Servlet container object inside our JettyExample that will encapsulate our private counter variable as well as our endpoint Servlets. Don’t forget we need a Thread-Safe variable to eliminate race conditions.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object JettyExample{ | |
{…} | |
object CounterServlets{ | |
private var requestCount: Int = AtomicInteger(0) // encapsulate the state in a Thread safe way | |
// TODO: Servlet Classes to go here | |
} | |
} |
IncrementServlet
Add the local requestCount variable, and the Servlet that handles the increment logic then returns html containing the new counter value. This Servlet will serve GET request types. Also, you must include the three imports required for defining an HttpServlet.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} | |
object JettyExample{ | |
{…} | |
object CounterServlets{ | |
private var requestCount: Int = AtomicInteger(0) | |
class IncrementServlet extends HttpServlet { | |
override protected def doGet(request: HttpServletRequest, response: HttpServletResponse):Unit = { | |
requestCount.getAndIncrement() | |
response.setContentType("text/html") | |
response.setStatus(HttpServletResponse.SC_OK) | |
response.getWriter().println(s"<h2>Increment performed. Count is now $requestCount.</h2>") | |
} | |
} | |
} | |
} |
ResetServlet
Add another HttpServlet titled “ResetServlet”. This Servlet will reset the counter, and return an OK message and HTML stating the counter has been reset.
At this point the Servlet Classes are defined, but they are not bound to the server with a ServletHandler as a route definition. We’ll do that next.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{…} | |
object JettyExample{ | |
{…} | |
object CounterServlets{ | |
{…} | |
class ResetServlet extends HttpServlet { | |
override protected def doGet(request: HttpServletRequest, response: HttpServletResponse):Unit = { | |
requestCount.getAndIncrement() | |
response.setContentType("text/html") | |
response.setStatus(HttpServletResponse.SC_OK) | |
response.getWriter().println(s"<h2>Counter reset to 0.</h2>") | |
} | |
} | |
} | |
} |
5. Bind the Servlets with a ServletHandler
Before adding the handler bindings add an import declaration for the CountingServlets internal to the JettyExample so we can have access to the Servlet Classes.
Now add a ServletHandler variable to the JettyExample scope. Within the main method assign this handler to the current server instance, and add the increment Servlet to the server after being mapped to the route “/increment”. Do the same with the reset Servlet. ServletHandler requires one import.
Both endpoints are now configured, the last thing to do is Start the server, and have the server wait to terminate.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.eclipse.jetty.servlet.ServletHandler | |
object JettyExample { | |
{…} | |
import CounterServlets._ | |
val incrementRoute = "/increment" | |
val resetRoute = "/reset" | |
val handler = new ServletHandler() | |
def main(args: Array[String]) = { | |
server.setHandler(handler) | |
handler.addServletWithMapping(classOf[IncrementServlet], incrementRoute) | |
handler.addServletWithMapping(classOf[ResetServlet], resetRoute) | |
} | |
} |
6. Start the Server
Fire it up! Start the server and have it wait for termination. We also should print out some diagnostic info about the port so we can hit the endpoint from a browser or other HTTP client.
Note: server.join() causes the main thread to wait to continue util the server is fully up, and will cause the main thread to wait to terminate until the Jetty thread terminates.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{…} | |
object JettyExample { | |
{…} | |
val server = createServer() | |
def main(args: Array[String]) = { | |
{…} | |
server.start() | |
println(s"Server started on ${port()} with routes: '$incrementRoute'") | |
server.join() | |
} | |
} |
When you start the server you’ll see a terminal printout similar to:
In a browser, go to http://localhost:{your port}/increment. Then refresh the page a few times. You should see something similar to the following:
To reset the count go to http://localhost:{your port}/reset . You should see
Complete Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.eclipse.jetty.servlet.ServletHandler | |
import org.eclipse.jetty.server.{NetworkConnector, Server} | |
import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} | |
object JettyExample { | |
import CounterServlets._ | |
val incrementRoute = "/increment" | |
val resetRoute = "/reset" | |
val server = createServer() | |
val handler = new ServletHandler() | |
def main(args: Array[String]) ={ | |
server.setHandler(handler) | |
handler.addServletWithMapping(classOf[IncrementServlet], incrementRoute) | |
handler.addServletWithMapping(classOf[ResetServlet], resetRoute) | |
server.start() | |
println(s"Server started on ${port()} with endpoints: '$incrementRoute' and '$resetRoute'") | |
server.join() | |
} | |
def port() = { | |
val conn = server.getConnectors()(0).asInstanceOf[NetworkConnector] | |
conn.getLocalPort() | |
} | |
def createServer() = new Server(0) | |
object CounterServlets{ | |
private var requestCount: Int = AtomicInteger(0) | |
class IncrementServlet extends HttpServlet { | |
override protected def doGet(request: HttpServletRequest, response: HttpServletResponse):Unit = { | |
requestCount.getAndIncrement() // Thread-Safe Increment | |
response.setContentType("text/html") | |
response.setStatus(HttpServletResponse.SC_OK) | |
response.getWriter().println(s"<h2>Increment received. Count is now $requestCount.</h2>") | |
} | |
} | |
class ResetServlet extends HttpServlet { | |
override protected def doGet(request: HttpServletRequest, response: HttpServletResponse):Unit = { | |
requestCount.getAndIncrement() // Thread-Safe Increment | |
response.setContentType("text/html") | |
response.setStatus(HttpServletResponse.SC_OK) | |
response.getWriter().println(s"<h2>Counter reset to 0.</h2>") | |
} | |
} | |
} | |
} | |