There comes a time in every Web application’s life—you hope—when the app is ready move out of the house, go to work, and face the world. Or at least the intranet.
A Databinder application can run in a servlet container like a regular Java webapp, or it can serve requests directly from an embedded Jetty server.
The standard deployment option, the one used to serve almost all Java web applications, is a Web Application Archive, or its “exploded” variant. If you already have a servlet container (application server) in production, this is likely the method you’ll want to use.
The template and example applications distributed with Databinder are configured for JAR packaging. This is required for certain IDEs (NetBeans is one) to run Databinder’s embedded web server (or any main() class). You’ll need to change the packaging to WAR, but it’s no great loss as you’ll then be able to use NetBeans’s internal servlet containers, Tomcat and GlassFish. (This is particularly appropriate if one of those is also the deployment server.) In Eclipse you may continue to run the embedded server with WAR packaging.
Be sure to exclude the Jetty libraries, present in the examples and template, from your packaged application. If you don’t run the embedded server in any environment, remove the Jetty dependency from the project pom.xml. If you do want to continue using it from the command line and inside Eclipse, add a dependency scope of “provided” and Maven will exclude the related JARs from the package.
Over a decade has passed since Java servlets were first defined. At the time of inception, having a centralized servlet container managing several web applications made perfect sense. After all, who wants to waste precious server RAM on duplicated container software for the plethora of enterprise applications one would quickly develop and/or purchase? The servlet container would do the hard work, in a single process; it would offer many services to the application, and it would some day be able to load and unload applications with aplomb. This gracefulness would cost money, and the more powerful services the application server provided, the more it would be worth.
Things didn’t exactly work out as planned. Even now, loading and reloading servlet contexts is an uncertain business, especially with anything that eats PermGen memory (like Hibernate). And all of those exported services? Those were J2EE, now Java EE, and JMS and external JPA providers. That is, a lot of things with reputations ranging from bad to borderline.
But scripting technologies, lacking a servlet specification, went ahead and served web requests directly from applications. It turned out that serving HTTP requests is not rocket science. The task can be embedded in an application, and a large server hosting several apps is not going to croak from the weight of a “web server” for each. Fortunately for us, the spunky all-Java Jetty web server has been around since the beginning, quietly supporting application embedding while we were all finding bugs in Tomcat.
As for why one might want to go against the application server grain, let’s not belabor the point. If you’ve been there you know: the containership and eager indirection of centralized app servers add complexity, and complexity means trouble for you. Believe it or not, but the rest of this tutorial is about serving web applications in separate operating system processes, an idea so conventional it’s radical.
DataServer is the embedded server that we ran on the command line and inside an IDE, and the same one can be used for the web. The only trick is having it start up. Up to this point we’ve used a dependency manager for practically everything—but you can’t serve a site through a Maven process … can you?
Well, maybe someone has tried that, but another approach is to use to the Maven-compatible, uber-flexible Buildr. Because Buildr is fully programmable, you can create a build task that spawns a Java server and records the process id. Another build task reads in the process id and asks the process to quit. (The sources for these have been hiding out in the example and template apps all along.)
The server tasks are imported from tasks/databinder.rb, and activated with the embed_server method that the basic template’s buildfile invokes. You’ll need to add any dependencies to the project definition that you’ve added to pom.xml, and also any transitive dependencies of those (Buildr does not yet resolve them). Then give it a whirl.
buildr compile
[ success ? ]
buildr basic:run
[ application running to console? ctrl + c to stop ]
buildr basic:start
[ spawns server process to 8080, records pid ]
buildr basic:stop
[ stops server at pid ]
Once all of that works, you’re in business.
To run multiple applications on a server (and for a number of other reasons) you may want to use an apache reverse proxy. If you already have a working Apache installation this is not difficult to add.
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPass /basic/ http://localhost:8080/basic/
ProxyPassReverse /basic/ http://localhost:8080/basic/
This exports the application on 8080, presumably protected by a firewall, to the web. The reverse proxy is needed to catch internal redirects. Wicket always generates a redirect when configured for its default two-pass rendering, and as far as the application is concerned it’s running at localhost:8080 and will redirect there as well. The ProxyPassReverse directive intervenes and sends a correct URL out to the web.
The Buildr script can spawn a server process all right, but sharp Wicket programmers will notice immediately that it’s running in development mode. That’s not okay. Also, your application might need more memory than the Java defaults.
Because we might have more than one web application to serve, it makes sense to add set global parameters in one place. How about /etc/default/databinder-server ?
export JAVA_HOME=/usr/lib/jvm/java-6-sun
export M2_REPO=/home/myname/.m2/repository
export JAVA_OPTIONS="$JAVA_OPTIONS -Dwicket.configuration=deployment \
-Xms128m -Xmx256m -XX:MaxPermSize=64m"
export LANG=en_US.UTF-8
export TZ=America/New_York
We set JAVA_HOME and M2_REPO here because the the application will be running on system startup without the benefit of the regular user environment. Additionally we set Wicket and Databinder for deployment service and beef up memory, and LANG to make sure there’s no babel. TZ determines the default time zone to be used when a web client’s is unknown.
That’s nice, but who’s going to call this script? /etc/init.d/basic:
#!/bin/sh
. /etc/default/databinder-server
export JETTY_PORT=8070
cd /home/myname/basic
buildr basic:$1
cd - > /dev/null
exit 0
This script reads in the server defaults, chooses a unique port, changes into the project directory, and kicks off Buildr with its first command line argument.
This way we can easily run different applications on different ports and export them all through Apache to the same sever (and deploy and restart them independently without any risk of bringing down the others). The Buildr script passes the port variable as a JVM parameter. It’s aware of a few others: JETTY_CONTEXT for the context path, and JETTY_AJP_PORT for an AJP port:
export JETTY_AJP_PORT=7070
If you specify an AJP port and have Apache 2.1 or higher, you can easily use transparent (and faster) AJP proxying through mod_proxy_ajp:
ProxyPass /basic/ ajp://localhost:7070/basic/
ProxyPass /complex/ ajp://localhost:7071/complex/
This is transparent in that the embedded server acts as if it’s serving from port 80 at the requested domain name. There is no need for a ProxyPassReverse and if the app asks for the client IP address, it will get the best known address instead of the Apache proxy server’s address. Once this is working you can turn off HTTP serving by setting JETTY_PORT to 0.
Did you notice that the start and stop task names map directly to init.d commands? You’re welcome. Make sure this works properly:
sudo /etc/init.d/basic start
Then you can run sudo update-rc.d basic defaults (or whatever is appropriate for your distribution) to have the server start and stop as the system is doing the same. This almost never works the first time, so be sure to test it. Usually it’s thrown by some difference from the user environment, so double-check JAVA_HOME and friends. It’s particularly important that startup work reliably if you’re on a shared server because they do need to restart those occasionally.
As mentioned up top, the embedded approach isn’t for everyone. Significantly, DataServer hasn’t yet been adapted for clustering. It will be, using the wicket-cluster-pagestore, but initial deployments that require clustering will have to do it the regular way, with an app server that synchronizes sessions.
But for a web site of moderate traffic (and realistically this describes most new sites), the embedded DataServer is a straightforward way to put a Databinder application into production. It’s memory efficient and stable, particularly compared to app servers managing multiple contexts. Snag a $30 per month virtual server and join us on the world wide web.