perwendel / spark

A simple expressive web framework for java. Spark has a kotlin DSL https://github.com/perwendel/spark-kotlin
Apache License 2.0
9.65k stars 1.56k forks source link

Embedded Jetty - Set context path #888

Open fgaule opened 7 years ago

fgaule commented 7 years ago

Using jetty, you can set a global context path for your application by calling:

        ServletContextHandler handler = new ServletContextHandler();
        handler.setContextPath("/my/app");
        Server server = new Server(9290);
        server.setHandler(compression);

How can this be done with the embedded jetty provided by Spark?

tipsy commented 7 years ago

You can set a custom server:

EmbeddedServers.add(EmbeddedServers.Identifiers.JETTY, new EmbeddedJettyFactory((i, j, k) -> {
    Server server = new Server();
    return server;
}));

Please reopen if context-path doesn't work with this appraoch.

fgaule commented 7 years ago

Hi @tipsy When you try to configure a handler for jetty containing a context path, it doesn't work. To configure a contextPath you need to add a handler to jetty: http://www.eclipse.org/jetty/documentation/9.3.x/embedding-jetty.html

public class OneServletContext
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server(8080);
        ServletContextHandler context = new ServletContextHandler(
                ServletContextHandler.SESSIONS);
        context.setContextPath("/this-is-my-path");
        server.setHandler(context);

        server.start();
        server.join();
    }
}

It seems that Spark creates other handler (spark.embeddedserver.jetty.JettyHandler) overriding the one i have created with its contextPath.

Does this make sense?

tipsy commented 7 years ago

Yeah, I checked the code, you're right.

ato commented 6 years ago

In case it's useful to someone else here's a more complete example of configuring a custom context path. I don't know if this is the best way to do it but it seems to work. My use case for this is running a Spark application behind nginx so that it shares a hostname with other content/applications.

In addition to wrapping Spark's handler in a ServletContextHandler we also need to strip the context path prefix off the path used for routing. As a convenience for anyone connecting to the backend application directly (for development, debugging etc) I also like to add a redirect from / to the context-path.

    static void contextPath(String path) {
        if (path == null || path.isEmpty() || path.equals("/")) {
            return;
        }

        EmbeddedServers.add(EmbeddedServers.Identifiers.JETTY, new EmbeddedJettyFactory() {
            @Override
            public EmbeddedServer create(Routes routeMatcher, StaticFilesConfiguration staticFilesConfiguration, boolean hasMultipleHandler) {
                // Spark uses the full ServletRequest.getRequestURI() rather than a context relative path like
                // getPathInfo as the path for routing so we must wrap the request and strip the context path
                // off the front of it.
                MatcherFilter matcherFilter = new MatcherFilter(routeMatcher, staticFilesConfiguration, false, hasMultipleHandler);
                matcherFilter.init(null);
                JettyHandler handler = new JettyHandler(matcherFilter) {
                    @Override
                    public void doHandle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
                        request = new HttpServletRequestWrapper(request) {
                            @Override
                            public String getRequestURI() {
                                return super.getRequestURI().substring(path.length());
                            }
                        };
                        super.doHandle(target, baseRequest, request, response);
                    }
                };
                ServletContextHandler servletContextHandler = new ServletContextHandler();
                servletContextHandler.setContextPath(path);
                servletContextHandler.setHandler(handler);

                // For convenience also install a redirect from / to the context path
                HandlerWrapper wrapper = new HandlerWrapper() {
                    @Override
                    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
                        if (target.equals("/")) {
                            response.sendRedirect(path);
                        } else {
                            super.handle(target, baseRequest, request, response);
                        }
                    }
                };
                wrapper.setHandler(servletContextHandler);

                // unfortunately JettyServer is package-private so we have to duplicate it
                return new EmbeddedJettyServer(new JettyServerFactory() {
                    @Override
                    public Server create(int i, int j, int k) {
                        // XXX: we're ignoring the the thread limits
                        return new Server();
                    }

                    @Override
                    public Server create(ThreadPool threadPool) {
                        return new Server(threadPool);
                    }
                }, wrapper);
            }
        });
    }