I've got a REST app that uses embedded Jetty as the server. Most of the endpoints need to be publicly-visible (and have appropriate authentication built in), but a few are for internal-use only. I'd like to avoid the overhead of authentication on those and instead use the firewall to restrict access:
Externally-visible endpoints are served on port 10000, which the external firewall leaves open. Internally-visible endpoints are served on port 20000, which the external firewall blocks.
However, I can't figure out how to achieve this with embedded Jetty. I've tried instantiating two Server
objects, one on port 10000 with the appropriate servlet handlers registered and one on port 20000 with the appropriate servlet handlers registered. However, only the server instance that is started second works; requests to endpoints hosted by the one started first result in 404 responses.
The Jetty documentation talks about how to do this with *.xml configurations , but not for an embedded instance.
Any thoughts or ideas? Or is there a better way to achieve the internal/external endpoint isolation I'm after? The hard requirement is that both internal and external endpoints need to "run" in the same JVM.
Turns out that the problem was related to using Guice and the Guice-servlets extension (issues 618 and 635). Running two embedded Jetty instances works fine as described in James Kingsbery's answer below.
Guice uses a filter (GuiceFilter) registered with server context to get ahold of requests that need request-scoped dependency injection (DI) and to construct servlets and filters that require DI. Unfortunately, it uses a static object to manage the list of servlets and filters associated with it.
In a typical setup, the guice-servlet.jar containing GuiceFilter
is included per-application and thus loaded by a different classloader for each application--- and everything works fine. No so with embedded Jetty, where essentially everything is loaded by the default system classloader.
The latest master (commit fbbb52dcc92e) of Guice contains an updated GuiceFilter
with support for a dynamic reference to the FilterPipeline
object (the static object causing the problems). Unfortunately, the constructor to inject the FilterPipeline
instance is package-private. So, to use it you need to create a wrapper class in the com.google.inject.servlet
package that exposes that constructor:
package com.google.inject.servlet;
import com.google.inject.Inject;
public class NonStaticGuiceFilter extends GuiceFilter {
/**
* Do not use. Must inject a {@link FilterPipeline} via the constructor.
*/
@SuppressWarnings("unused")
private NonStaticGuiceFilter() {
throw new IllegalStateException();
}
@Inject
public NonStaticGuiceFilter(FilterPipeline filterPipeline) {
super(filterPipeline);
}
}
To use this class, create an instance using the injector with your ServletModule
installed and register it with your Jetty Context
:
// Create the context handler
ServletContextHandler handler = new ServletContextHandler(myServer, "/context");
// Create the injector, registering your ServletModule
final Injector injector = Guice.createInjector(new MyServletModule());
// Add the Guice listener for this injector
handler.addEventListener(new GuiceServletContextListener() {
@Override
protected Injector getInjector() {
return injector;
}
});
// Filter all requests through Guice via NonStaticGuiceFilter.
// Guice will construct the FilterPipeline instance needed by
// NonStaticGuiceFilter.
handler.addFilter(
new FilterHolder(injector
.getInstance(NonStaticGuiceFilter.class)), "/*", null);
My usual crutch in getting started with an embedded Jetty project is the Wicket maven archetype. Here is a class based on that archetype that should do pretty much what you need:
package net.kingsbery;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.webapp.WebAppContext;
public class Start {
public static void main(String[] args) throws Exception {
Server server = new Server();
SocketConnector connector = new SocketConnector();
// Set some timeout options to make debugging easier.
connector.setMaxIdleTime(1000 * 60 * 60);
connector.setSoLingerTime(-1);
connector.setPort(10080);
server.setConnectors(new Connector[] { connector });
WebAppContext bb = new WebAppContext();
bb.setServer(server);
bb.setContextPath("/");
bb.setWar("src/main/secret-webapp");
server.addHandler(bb);
Server server2 = new Server();
SocketConnector connector2 = new SocketConnector();
// Set some timeout options to make debugging easier.
connector2.setMaxIdleTime(1000 * 60 * 60);
connector2.setSoLingerTime(-1);
connector2.setPort(20000);
server2.setConnectors(new Connector[] { connector });
WebAppContext bb2 = new WebAppContext();
bb2.setServer(server);
bb2.setContextPath("/");
bb2.setWar("src/main/webapp");
server.addHandler(bb);
server2.addHandler(bb2);
try {
server.start();
server2.start();
} catch (Exception e) {
e.printStackTrace();
System.exit(100);
}
}
}
If you use some other handler, replace that with the webapp handler.
That being said, I'm not sure that this is the right way of doing it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With