implementing grizzly in milton

05/01/2015

With Kademi we provide web developers a cloud based web development platform. Its mult-tenanted so every account needs to be completely configurable through user managed data.

We've been using tomcat8 but hit a few problems:

  • We need per user SSL configuration, which means implementing Server Name Indication (SNI), which tomcat8 doesnt support and has been described as a low priority by the tomcat team
  • Uploads to tomcat are buffered, which means the webapp doesnt get a chance to check authorisation or parse incoming data until the request is complete. Our users upload a lot of large videos and this upload buffering totally breaks the user experience.
  • Web sockets is supported in tomcat8, but we like to develop using maven plugins and there is no tomcat8 plugin, only tomcat7. We could use the cargo plugin, but that requires a seperate tomcat installation which breaks the whole point of maven. We often use the jetty plugin but that has a different web sockets implementation.
  • We want our java code to be in a private repo, but our web assets to be public. But there is no efficient way to work with tomcat projects where java and web assets are in different repositories
Having a look at grizzly we decided we could solve all of those problems. Thats simple because grizzly is a lower level framework then tomcat, and low level frameworks generally give you more flexibility.

 

Implementing Grizzly was pretty straight forward:

  • Implement a milton Request class that wraps the grizzly request object
  • Ditto for response
  • Build a start class that creates the grizzly server and delegates requests to milton
And there was one more bit we needed:
  • Allow web assets to be served from a seperate project

Building Request and Response wrappers

This bit was almost trivial, and was mostly just a copy and paste of the servlet request/response wrappers. You can see the request here and the response class here

The java main method

Like a lot of people i like to work with instances, so first thing is to write the main method and create an instance and invoke a method on it:

public class Kademi {

    private static final Logger log = LoggerFactory.getLogger(Kademi.class);

    public static void main(String[] args) throws IOException, InterruptedException {
        Kademi k = new Kademi();
        k.start();
        System.out.println("Press any key to stop the server...");
        System.in.read();
    }

 

Integrating with spring

App config is very important to me and i almost always use spring. This is really just a copy and paste from MiltonSpringFilter

   ....

    private HttpServer httpServer;
    private StaticApplicationContext parent;
    private HttpManager httpManager;
    private MailServer mailServer;

    public Kademi() {

    }

    public void start() throws IOException {
        ConfigurableApplicationContext ctx = initSpringApplicationContext();
        Object milton = ctx.getBean("milton.http.manager");
        if (milton instanceof HttpManager) {
            this.httpManager = (HttpManager) milton;
        } else if (milton instanceof HttpManagerBuilder) {
            HttpManagerBuilder builder = (HttpManagerBuilder) milton;
            ResourceFactory rf = builder.getMainResourceFactory();
            this.httpManager = builder.buildHttpManager();
        }
        ...
    }

    protected ConfigurableApplicationContext initSpringApplicationContext() {

        log.info("No root spring context");
        parent = new StaticApplicationContext();

        ConfigurableApplicationContext ctx = null;
        String[] contextFiles = new String[]{"applicationContext.xml"};
        parent.refresh();
        try {
            ctx = new ClassPathXmlApplicationContext(contextFiles, parent);
        } catch (BeansException e) {
            log.error("Unable to create a child context for Milton", e);
        }
        return ctx;
    }
}

 

Starting the server

This was pretty simple, although the websockets and SSL took a bit of figuring out. The SSL/SNI bit is still a work in progress, but should be no problem now i've got a hook to work with..


        httpServer = HttpServer.createSimpleServer(null, 8080);
        {
            SSLEngineConfigurator ssle = new SSLEngineConfigurator(serverSslContext);
            NetworkListener listener = new NetworkListener("ssl", NetworkListener.DEFAULT_NETWORK_HOST, new PortRange(8443));
            listener.setSSLEngineConfig(ssle);
            listener.setSecure(true);
            httpServer.addListener(listener);
        }

        httpServer.getServerConfiguration().addHttpHandler(
                new HttpHandler() {
                    @Override
                    public void service(Request request, Response response) throws Exception {
                        log.info("service");
                        GrizzlyMiltonRequest req = new GrizzlyMiltonRequest(request);
                        GrizzlyMiltonResponse resp = new GrizzlyMiltonResponse(response);
                        httpManager.process(req, resp);
                    }
                },
                "/");

        final WebSocketAddOn addon = new WebSocketAddOn();
        for (NetworkListener listener : httpServer.getListeners()) {
            listener.registerAddOn(addon);
        }

        MCRootContext mCRootContext = ctx.getBean(MCRootContext.class);
        SessionManager sessionManager = ctx.getBean(SessionManager.class);

        KademiWebsocketApplication kademiWebsocketApplication = new KademiWebsocketApplication(new OptimizedBroadcaster(), mCRootContext, sessionManager);
        WebSocketEngine.getEngine().register("", "/comments/*", kademiWebsocketApplication);

        SSLEngineConfigurator serverEngineConfig = new SSLEngineConfigurator(serverSslContext);

        SSLEngineConfigurator clientEngineConfig = new SSLEngineConfigurator(clientSslContext);

        SNIFilter sniFilter = new SNIFilter(serverEngineConfig, clientEngineConfig);

        sniFilter.setServerSSLConfigResolver(new SNIServerConfigResolver() {

            @Override
            public SNIConfig resolve(Connection connection, String hostname) {
                System.out.println("resolve sni " + hostname);
                SSLEngineConfigurator sslEngineConfig = null; //host2SSLConfigMap.get(hostname);

                // if sslEngineConfig is null - default server-side configuration,
                // which was passed in the SNIFilter constructor, will be used.
                return SNIConfig.newServerConfig(sslEngineConfig);
            }
        });

        httpServer.start();

}


So thats pretty simple, huh?

 

I've striipped out the Kademi and work-in-progress stuff and committed a working, albeit simple, grizzly server to milton, which you can see here

 


Questions and answers