Saturday, June 16, 2012

Using embedded Jetty in your application: Example Code

In this post I want to describe how to use the jetty web server in your application. 

                                        freedigitalphotos.net
One of the great things about jetty is that it can be used standalone - that means there is some binary which acts as a "server" and then there are "archives" which can be deployed on that server - (web application archives). 

As a developer, you just have to obey certain rules for creating such an archive, that is to say you have to implement certain interfaces, fill out xml files with some meta information, and you are good to go.

Sometimes you want to distribute your web application and the server it runs on together, like the continuous integration server jenkins does.

Of course you could download the jetty server from it's site, bundle your war file which contains your application logic together, create an installer - but it is easier if you hide all the details from your user and provide just a plain jar file which, when double clicked, does the rest. 

Running an embedded jetty means:
  1. you have to integrate the server somehow with your sources
  2. you have to get from a main() method to the point where you start the web server
  3. you have to put all relevant classes in the jar file - not only your application logic but also all classes which are needed for the server
  4. you might be interested in delivering a compact binary without 'dead code' - so you might not use every feature jetty provides
  5. you want to have a short start up time and your webapplication should load its welcome screen.
  6. you want to stop the server again.
The first two points make it clear that you have to use internal jetty classes to initialize your main application. You have to tell those classes what web application is to be loaded, which port you want to use, the timeout you want to set, and some other things.

Point three and four clearly indicate that this is a job for proguard, since this is exactly what proguards can do very well.

Point five says something about the start up mechanisms. On the first run, all of your classes are contained in the jar file, they have to be unpacked, verified and what not - this can take some time.  It may be more efficient to unpack your jar and start then those classes. Thus you may want to support different scenarios here.

(You could also think about giving your application a nice splash screen, and start a browser in kiosk mode for example. The point is that you have a very fine grained control of the behavior of the server.)

For my example application, I need a configured web application and also means to serve static content.

I'm using jetty8 which means i have to include following dependencies in the maven pom:

<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-util</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-servlet</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-io</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>servlet-api</artifactId>
    <version>3.0.20100224</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-http</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-security</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-xml</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-continuation</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>
   <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-webapp</artifactId>
    <version>8.0.4.v20111024</version>
   </dependency>


To create an application which starts a standard war and serving static content following code suffices:

package net.ladstatt

import java.io.File

import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.bio.SocketConnector
import org.eclipse.jetty.server.handler.HandlerList
import org.eclipse.jetty.servlet.DefaultServlet
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.webapp.WebAppContext

class EmbeddedJettyServer

/**
 * Run application standalone
 */
object EmbeddedJettyServer {
  val staticUri = "/static"
  val idleTime = 1000 * 60 * 60

  def main(args: Array[String]): Unit = {
    println("Usage: java -jar net.ladstatt.web.jar [ ]")
    println("")
    println("Usage: you can either just start the jar file without arguments or provide the path to the unzipped installation providing arguments as follows.")
    println("")
    println("Example: java -jar net.ladstatt.web.jar c:\\temp\\unpacked 4711")
    println("")

    if (args.size == 0) {
      createSelfContainedServer
    } else {
      val jarRoot = new File(args(0))
      val staticRoot = new File(args(1))
      val port = args(2).toInt
      if (!jarRoot.exists) {
        println("Given program directory (%s) doesn't exist!".format(jarRoot))
      } else {
        if (!staticRoot.exists) {
          println("Given static file directory (%s) doesn't exist!".format(staticRoot))
        } else {
          val server = createServer(port, jarRoot, "/myExampleWebApp", staticRoot)
          try {
            server.start();
            System.in.read() // wait for user input
            server.stop()
            server.join()
          } catch {
            case e => e.printStackTrace
          }
        }

      }
    }
  }

  def createSelfContainedServer() {
    lazy val server = createServer(8080)
    try {
      server.start();
      System.in.read()
      server.stop()
      server.join()
    } catch {
      case e => e.printStackTrace
    }
  }

  def createServer(port: Int, basePath: File, baseUri: String, staticPath: File): Server = {
    val server = new Server()
    val connector = new SocketConnector()
    connector.setMaxIdleTime(idleTime)
    connector.setSoLingerTime(-1)
    connector.setPort(port)
    server.setConnectors(Array(connector))

    val handlers = new HandlerList

    val staticHandler = new ServletContextHandler
    staticHandler.setContextPath(staticUri)
    staticHandler.setResourceBase(staticPath.getAbsolutePath)
    staticHandler.addServlet(classOf[DefaultServlet], "/") 

    val webAppHandler = new WebAppContext(server, basePath.getAbsolutePath, baseUri)

    handlers.addHandler(webAppHandler)
    handlers.addHandler(staticHandler)
    server.setHandler(handlers)

    println("Starting webapp installed in %s on url : %s, static content served from %s on path %s".format(basePath.getAbsoluteFile, baseUri, staticPath.getAbsolutePath, staticUri))
    server
  }

  def createServer(port: Int): Server = {
    val server = new Server()
    val connector = new SocketConnector()

    connector.setMaxIdleTime(1000 * 60 * 60)
    connector.setSoLingerTime(-1)
    connector.setPort(port)
    server.setConnectors(Array(connector))

    val context = new WebAppContext()
    context.setServer(server)
    context.setContextPath("/")
    val protectionDomain = classOf[EmbeddedJettyServer].getProtectionDomain()
    val location = protectionDomain.getCodeSource().getLocation()
    println(location.toExternalForm)
    context.setWar(location.toExternalForm())
    server.setHandler(context)
    server
  }
}

As you can see, you can easily start from an jar file (see createServer(port: Int) method, or use a directory with the proper structure for it (means that it has to contain a WEB-INF directory and so on - just unzip a war file and you'll see what is necessary).

The above approach uses a mixture of programmatic configuration of the jetty web server and a declarative one - the web.xml configures the web application - therefore chances are high that above code could work with your war file, too.

Note: When using this approach, you are interested to create a jar file with a main class - this differs from distributing a war file.

This code opens also a door to start and stop a jetty webserver programmatically, which is very useful in respect to executing automated tests.

6 comments:

  1. Can you please tell me how to run the application in case of war and jar? That would give a completeness for the blog

    ReplyDelete
  2. To run WAR inside embedded jetty, it is far straightforward, see this link http://www.eclipse.org/jetty/documentation/current/embedding-jetty.html#d0e18267

    ReplyDelete
  3. how to embedded the jetty web server in android studio???

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hey you helped me out, the individual dependencies pointing server and util to the maven installed 7.6.8.v20121106. Worked in getting over the bioconnector issue with sparkConf. Not sure that's what you were trying to fix. but you did. Newbie thanks sent to you.

    ReplyDelete