Jetty: default servlet context path

2019-01-20 04:18发布

问题:

I need to set Servlet (only servlet not handler because of some reasons) to work with files outside war. Here https://stackoverflow.com/a/28735121/5057736 I found the following solution:

Server server = new Server(8080);

ServletContextHandler ctx = new ServletContextHandler();
ctx.setContextPath("/");

DefaultServlet defaultServlet = new DefaultServlet();
ServletHolder holderPwd = new ServletHolder("default", defaultServlet);
holderPwd.setInitParameter("resourceBase", "./src/webapp/");

ctx.addServlet(holderPwd, "/*");//LINE N
ctx.addServlet(InfoServiceSocketServlet.class, "/info");

server.setHandler(ctx);

This solutions works and this is what I need. However, it stops working as soon as I change LINE N to ctx.addServlet(holderPwd, "/foo/*");. I tried "/foo/","/foo" but result is the same - I get not found. Why? How can I make it work with this certain context? I use jetty 9.2.15 because of the same reasons.

回答1:

The DefaultServlet is designed to look at the request URI after the contextPath.

In your example code when you changed the url-pattern of your servlet from / to /foo/* the resulting file being looked for on disk is now includes the /foo/ portion.

In other words, a request URI of /css/main.css results in the file (on disk) it expects to find as ./src/webapp/foo/css/main.css

Your example has a few flaws. It's not a wise have an empty resource base for your ServletContextHandler, as the ServletContext itself needs access to that configuration value.

You would fix that by removing ...

holderPwd.setInitParameter("resourceBase", "./src/webapp/");

and using ServletContextHandler.setBaseResource(Resource) instead ...

ctx.setResourceBase(Resource.newResource(new File("./src/webapp")));

This will allow the following ServletContext methods (used by countless servlet libraries) to work as well

  • String getRealPath(String path)
  • URL getResource(String path)
  • InputStream getResourceAsStream(String path)
  • Set<String> getResources(String path)

Finally, to make this setup sane in the ServletContextHandler, you'll add the default Servlet name, on the "default url-pattern", which happens to be implemented as the DefaultServlet.

Like this:

// Lastly, the default servlet for root content
// It is important that this is added last.
String defName = "default"; // the important "default" name
ServletHolder holderDef = new ServletHolder(defName, DefaultServlet.class);
holderDef.setInitParameter("dirAllowed","true");
ctx.addServlet(holderDef,"/"); // the servlet spec "default url-pattern"

Now, if you also have a need to serve static content from request URI /foo/* out of a directory not belonging to the webapp, you can do that too. That will require you to setup another DefaultServlet that does not participate in the ServletContext.

An example of this setup is ...

package jetty;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.PathResource;

import java.io.File;
import java.nio.file.Path;

public class ManyDefaultServlet
{
    public static void main(String[] args) throws Exception {
        Server server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(8080);
        server.addConnector(connector);

        // The filesystem paths we will map
        Path homePath = new File(System.getProperty("user.home")).toPath().toRealPath();
        Path pwdPath = new File(System.getProperty("user.dir")).toPath().toRealPath();

        // Setup the basic application "context" for this application at "/"
        // This is also known as the handler tree (in jetty speak)
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        context.setBaseResource(new PathResource(pwdPath));
        server.setHandler(context);

        // Fist, add special pathspec of "/home/" content mapped to the homePath
        ServletHolder holderHome = new ServletHolder("static-home", DefaultServlet.class);
        holderHome.setInitParameter("resourceBase",homePath.toUri().toASCIIString());
        holderHome.setInitParameter("dirAllowed","true");
        // Use request pathInfo, don't calculate from contextPath
        holderHome.setInitParameter("pathInfoOnly","true");
        context.addServlet(holderHome,"/foo/*"); // must end in "/*" for pathInfo to work

        // Lastly, the default servlet for root content
        // It is important that this is last.
        String defName = "default"; // the important "default" name
        ServletHolder holderDef = new ServletHolder(defName, DefaultServlet.class);
        holderDef.setInitParameter("dirAllowed","true");
        context.addServlet(holderDef,"/"); // the servlet spec "default url-pattern"

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

This uses a second DefaultServlet, using a unique resource base for that DefaultServlet only, and mapped to a url-pattern that ends in /*.

Finally, the init-parameter for this second DefaultServlet is told to use the pathInfo of the Request URI and not split on the contextPath like it normally does.

For more information on what this whole pathInfo, request URI, contextPath, and url-patterns ending in /* are all about, see the useful answer by @30thh

This stand alone DefaultServlet declaration does not participate in the ServletContext and libraries will not be able to see or access the content from that DefaultServlet via the ServletContext methods. However all incoming HTTP client requests can request the content easily via that url-pattern.