domingo, 16 de junho de 2013

Embedded war using Jetty and Gradle


Recently I had the necessity to write an embedded war. At a first glance, I thought: “It is a pretty simple task, I’ve done it many times using Jetty”. Just some minutes later I remembered: “Wait a minute... I’ve used embedded Jetty servers into jar files, but how can I do the same using war files?”.

The answer of this question is a bit “tricky”. Actually, you have to create a war file which will have to play two different roles. The first one is responsible to start the embedded Jetty server and the second one is the web app itself. Take a look at the MyEmbeddedWar.war folder structure below to understand what I’m talking about:


  • MyEmbeddedWar.war
    • META-INF
      • MANIFEST.MF
        • containing: Main-Class: com.embedded.JettyStarter
    • Jetty classes (org.eclipse.jetty.*, org.apache.*, javax.*, etc)
    • My main class: com.embedded.JettyStarter
    • WEB-INF
      • classes
        • containing my app classes
      • web.xml
    • index.jsp


The blue files in the root of the war are those responsible to start the embedded Jetty server and the red ones represent the content of web app. Ok, once I’ve understood what need to be done, I’ve started to think how it could be done using gradle (for those which do not know what gradle is, I strongly recommend to take a look at http://www.gradle.org/). So, at the end of my experiments I’ve got the following:

First you need to create the file ‘build.gradle’ as shown below. I’ve added some comments on it to make this post as short and simple as possible.

// Tells gradle it is a war project which will be imported into eclipse wtp IDE
apply plugin: 'war'
apply plugin: 'eclipse-wtp'


// Define souce code compatibility
sourceCompatibility = 1.7
targetCompatibility = 1.7


// Use maven repository
repositories {
   mavenCentral()
}


// Fills out all dependencies which are necessary to start the embedded jetty into our war file
configurations {
embeddedJetty
}
dependencies {
embeddedJetty 'org.eclipse.jetty:jetty-servlet:+'
embeddedJetty 'org.eclipse.jetty:jetty-webapp:+'
embeddedJetty 'org.eclipse.jetty:jetty-jsp:+'
embeddedJetty 'org.eclipse.jetty:jetty-annotations:+'
}


war.baseName = 'MyExecutableWar'
war {
// unzip and add all jetty dependencies into the root of our war file
from {configurations.embeddedJetty.collect {
project.zipTree(it)
}
}
// remove signature and unnecessary files
exclude "META-INF/*.SF", "META-INF/*.RSA", "about.html", "about_files/**", "readme.txt", "plugin.properties", "jetty-dir.css"


// include in the root of the war only the classes which will be used to start the embedded Jetty
from "$buildDir/classes/main"
exclude "com/myapp/"
// sets the main class to run when the generate war be executed using 'java -jar'
manifest { attributes 'Main-Class': 'com.embedded.JettyStarter' }
}


// Once you will need some basic api (servlet api, for example) for compilation, you need to add embeddedJetty dependencies for compilation
sourceSets.main.compileClasspath += configurations.embeddedJetty


// you need to do the same for eclipse classpath, so you can use it to edit your java files
eclipse {
classpath {
plusConfigurations += configurations.embeddedJetty 
// for gradle 2.5 use: plusConfigurations += [configurations.embeddedJetty]
}
}


Then, you have to create the main class to start our embedded Jetty (in this example, I’ve called it ‘com.embedded.JettyStarter'):


public class JettyStarter {
public static void main(String[] args) throws Exception{
                       ProtectionDomain domain = JettyStarter.class.getProtectionDomain();
                       URL location = domain.getCodeSource().getLocation();
       
                       // create a web app and configure it to the root context of the server
                       WebAppContext webapp = new WebAppContext();
                       webapp.setDescriptor("WEB-INF/web.xml");
                       webapp.setConfigurations(new Configuration[]{ new AnnotationConfiguration()
        , new WebXmlConfiguration(), new WebInfConfiguration(), new MetaInfConfiguration()                });
                       webapp.setContextPath("/");
                       webapp.setWar(location.toExternalForm());
       
                       // starts the embedded server and bind it on 8081 port
     Server server = new Server(8081);
                       server.setHandler(webapp);        
                       server.start();
                       server.join();
}
}


Once both the build and the main file are implemented, you can forget it is an embedded war and add as many JSPs and Servlets as you want. In order to show an example of an servlet being executed, I’ve added the following TimeServlet (which simply prints the current date).


@SuppressWarnings("serial")
@WebServlet(urlPatterns = { "/time" })
public class TimeServlet extends HttpServlet {
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                 throws ServletException, IOException
        {
ServletOutputStream out = response.getOutputStream();
out.print(new Date().toString());
out.flush();
        }
}


and the following index.jsp, which prints a jsp expression in order to show a jsp being executed.


<html>
<head>
</head>
<body>
<h1>Hello: <%= "This is a jsp expression" %></h1>
<a href="time">Click here to execute TimeServlet</a>
</body>
</html>


Now, you can build your war and execute it using the following command line: ‘java -jar MyEmbeddedWar.war’. This will start the Jetty server in port 8081, so you can access the http://localhost:8081/ url in the browser.

The source code can be found here.

Good luck!

3 comentários:

  1. Thank you for the article and especially for the code.
    Good stuff.

    Regards,
    Andrey

    ResponderExcluir
  2. Hey thanks for this code, I am looking to do this with Gradle Kotlin;

    Gradle Kotlin



    Broken weird;

    manifest {

    attributes ‘Main-Class’: ‘com.embedded.JettyStarter’

    }



    Working Cool;

    manifest {

    attributes(mapOf("Main-Class" to "com.embedded.JettyStarter"))

    }

    Let me know if you are interested in collaboration!
    Scott

    ResponderExcluir