Embedded Jetty Servlets and JSP
Java
Thursday, November 03, 2011
Recently I had been using Tomcat as a Java Servlet container for a project at work. This works well in the context of a tightly-integrated set of servlets and JSP pages like a typical website, but the project I was working on is intended to be a self-contained module which presents an HTTP API and should be easily deployable without worrying too much about shared settings and shared libraries on the target box. I also wanted the ability to profile, debug, and run jUnit tests on the component from within Eclipse, without requiring an additional, separate deployment to a Tomcat server (even if it is a local Tomcat server on my dev machine.
So I decided to switch to a design involving embedding a Jetty container into a plain Java app. This would give me the ability to have a clean deployable artifact with no external dependencies, and also to launch the servlet container and the HTTP tests against it from the same jUnit script. Sadly, the documentation for embedding Jetty is not the most comprehensive.
In order to keep things as tight and straightforward as possible, I opted to instantiate the individual parts of Jetty directly rather than just using the WebApp context as shown in the examples. (using the WebApp context would involve additional dependencies and overhead of dealing with WAR files and web.xml, etc.) Getting a basic servlet context running and serving static content was fairly simple, but I couldn’t find any good documentation or example on setting up the JSP servlet without using the WebApp wrapper. After some source-reading and a lot of experimentation, I got it working, but I ran into quite a few issues along the way.
-
The DefaultServlet class serves static content, but not JSP pages.
Solution: use JspServlet.
-
Jetty uses parts of the Glassfish JSP project and parts of the Tomcat Jasper engine, and the Jetty 8.0.3 version tried to mix and match incompatible library versions of these. This results in
java.lang.AbstractMethodError: javax.servlet.jsp.PageContext.getELContext()javax/el/ELContext;Solution: use Jetty 8.0.4
-
When trying to compile JSP pages on the fly, the JspServlet is unable to find the Jetty JARs even though they are in the application’s classpath, resulting in
PWC6033: Error in Javac compilation for JSP,PWC6199: Generated servlet error:string:///index_jsp.java:3: package javax.servlet does not existSolution: set the classpath [InitParam](http://download.eclipse.org/jetty/stable-8/apidocs/org/eclipse/jetty/servlet/Holder.html#setInitParameter(java.lang.String,%20java.lang.String) based on the context’s ClassLoader, which in turn should be set from the current thread.
After fighting through all of these issues, I finally arrived at the clean, programmatically-configured embedded Jetty solution, which I present to you here:
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
class RunServer {
public static void main(String args[]) {
System.out.println("Initializing server...");
final ServletContextHandler context =
new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.setResourceBase("webapp");
context.setClassLoader(
Thread.currentThread().getContextClassLoader()
);
context.addServlet(DefaultServlet.class, "/");
final ServletHolder jsp =
context.addServlet(JspServlet.class, "*.jsp");
jsp.setInitParameter("classpath", context.getClassPath());
// add your own additional servlets like this:
// context.addServlet(JSONServlet.class, "/json");
final Server server = new Server(8080);
server.setHandler(context);
System.out.println("Starting server...");
try {
server.start();
} catch(Exception e) {
System.out.println("Failed to start server!");
return;
}
System.out.println("Server running...");
while(true) {
try {
server.join();
} catch(InterruptedException e) {
System.out.println("Server interrupted!");
}
}
}
}