11

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.


Edit

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.

Solution to Guice Problem

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);
Community
  • 1
  • 1
David B.
  • 5,700
  • 5
  • 31
  • 52

1 Answers1

5

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.

James Kingsbery
  • 7,298
  • 2
  • 38
  • 67
  • Hmm, this is essentially what I tried--- creating two separate servers. I am using Guice to do dependency injection at request-scope, so all requests are filtered through `GuiceFilter.class` and the DI is configured on a `ServletContextHandler` by adding a `GuiceServletContextListener` EventListener to the context handler. This Guice chain may have some unfortunate JVM-scoped singletons that are breaking things. – David B. Jan 31 '12 at 06:02
  • Interesting... if it's easy to switch to Spring, I don't believe Spring has any such JVM-scoped singletons. Actually, I would find it hard to believe that they're JVM-scope singletons, since I'm sure that if you had multiple web apps on the same non-embedded jetty, that would work fine. Are they ClassLoader scoped singletons? – James Kingsbery Feb 01 '12 at 03:52
  • Ya, they are classloader-scoped to be precise ;) I've worked around the issue in Guice; I'll post my solution (to my actual problem, not the question I asked) as an edit and accept your answer, which does answer the asked question. – David B. Feb 01 '12 at 06:50