Jetty 12 ResourceHandler configuration and MIME-types issues

117 Views Asked by At

I'm migrating to jetty 12, and I'm encountering some issues with the ResourceHandler. I'm using three, one for /robots.txt, one for /sitemap.xml and one for all other files at /files/.

I'm having two issues:

  • /robots.txt and /sitemap.xml doesn't work. It gives a 301 and rewrites the url with appended slash (/robots.txt/) and then gives a 404.
  • /files/ does send the correct files, but never sends any Content-Type headers no matter the MimeTypes I supplied

I noticed the ResourceHandler now needs to have a ContextHandler wrapped, or else I get a nullPointerException immediately. I also couldn't get ResourceHandler#setBaseResourceAsString to work at all, but it seems to work with /files/ when using ContextHandler#setBaseResourceAsString.

Any ideas what I'm doing wrong?

@Override
public void run() {
    //...

    // Configure jetty
    jettyServer = new Server();

    //...

    // Calls all added handlers in list order
    Handler.Sequence root = new Handler.Sequence();
    jettyServer.setHandler(root);

    root.addHandler(new GreeterHandler(this));

    // Create a PathMappingsHandler to hold mappings
    PathMappingsHandler mapHandler = new PathMappingsHandler();
    root.addHandler(mapHandler);

    // Add handlers to ContextHandlerCollection
    // First, all dynamic handlers for user facing pages
    mapHandler.addMapping(PathSpec.from(pageDescriptions.get(PAGE_INDEX).url()), new IndexHandler(this));
    mapHandler.addMapping(PathSpec.from(pageDescriptions.get(PAGE_LOGIN).url()), new LoginHandler(this));
    mapHandler.addMapping(PathSpec.from(pageDescriptions.get(PAGE_LOGOUT).url()), new LogoutHandler(this));
    mapHandler.addMapping(PathSpec.from(pageDescriptions.get(PAGE_REGISTER).url()), new RegisterHandler(this);

    mapHandler.addMapping(PathSpec.from("/robots.txt"), singleFileHandler("/robots.txt", "resources/private/robots.txt"));
    mapHandler.addMapping(PathSpec.from("/sitemap.xml"), singleFileHandler("/sitemap.xml", "resources/private/sitemap.xml"));

    // the rest of the /public folder
    ResourceHandler publicFilesHandler = new ResourceHandler();
    publicFilesHandler.setServer(jettyServer);
    publicFilesHandler.setDirAllowed(true);
    //publicFilesHandler.setUseFileMapping(true);

    MimeTypes.Mutable types = new MimeTypes.Mutable();
    types.addMimeMapping("js", "text/javascript; charset=utf-8");
    types.addMimeMapping("css", "text/css; charset=utf-8");
    types.addMimeMapping("svg", "image/svg+xml");

    publicFilesHandler.setMimeTypes(types);

    ContextHandler h = new ContextHandler(publicFilesHandler, "/files");
    h.setBaseResourceAsString("resources/public/");
    mapHandler.addMapping(PathSpec.from("/files/*"), h);

    jettyServer.setRequestLog(new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.NCSA_FORMAT + " - %D"));

    //logger.info(root.dump());

    try {
        jettyServer.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * Creates a ResourceHandler that serves a single file.
 */
private Handler singleFileHandler(String contextPath, String filePath) {
    ResourceHandler handler = new ResourceHandler();
    handler.setServer(jettyServer);

    ContextHandler result = new ContextHandler(handler);
    result.setBaseResourceAsString(filePath);

    return result;
}

EDIT: I also tried the following simplest setup from the docs, but I couldn't get it do produce anything other than 404s:

ResourceHandler rh = new ResourceHandler();   
Resource r = ResourceFactory.of(rh).newResource("resources/public/");
if (!Resources.isReadableDirectory(r)) {
     throw new IOException("Resource is not a readable directory");
}
rh.setBaseResource(r);
rh.setDirAllowed(true);
rh.setUseFileMapping(true);
rh.setServer(jettyServer);
mapHandler.addMapping(PathSpec.from("/files2/*"), rh);
1

There are 1 best solutions below

4
Joakim Erdfelt On

A ResourceHandler has a Resource as its base.

That base Resource has to exist, must be a directory, and must be readable by the ResourceHandler.

When you call ResourceFactory.newResource("resources/public/") that call can result in a null if the resource isn't found.

The string "resources/public/" is an incomplete path that is subject to all kinds of relative path behaviors.

Double check that the result of newResource(String) is a Resource and not null.

If it is null, use a Path object and newResource(Path) instead.

Here's some examples to work from.

Using a Path object

import java.nio.file.Path;
import java.nio.file.Files;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.Resources;

ResourceHandler rh = new ResourceHandler();   
Path baseDir = Path.of("resources/public/");
if (!Files.isDirectory(baseDir))
    throw new IOException("Path is not a directory: " + baseDir);
if (!Files.isReadable(baseDir))
    throw new IOException("Path is not readable: " + baseDir);
Resource base = ResourceFactory.of(rh).newResource(baseDir);
if (!Resources.isReadableDirectory(base))
    throw new IOException("Resource is not a readable directory"); + 
rh.setBaseResource(base);
// ...

Using Resource object

import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.Resources;

ResourceHandler rh = new ResourceHandler();   
Resource base = ResourceFactory.of(rh).newResource("resources/public/");
if (!Resources.isReadableDirectory(base))
    throw new IOException("Resource is not a readable directory"); + 
rh.setBaseResource(base);
// ...

Using URI object

import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.Resources;

ResourceHandler rh = new ResourceHandler();   
URI uri = getURIToBase(); // must be an absolute URI
Resource base = ResourceFactory.of(rh).newResource(uri);
if (!Resources.isReadableDirectory(base))
    throw new IOException("Resource is not a readable directory"); + 
rh.setBaseResource(base);
// ...

Referencing a ClassLoader loader resource

import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.Resources;

ResourceHandler rh = new ResourceHandler();   
boolean searchSystemClassLoader = true;
Resource base = ResourceFactory.of(rh)
    .newClassLoaderResource("resources/public/", searchSystemClassLoader);
if (!Resources.isReadableDirectory(base))
    throw new IOException("Resource is not a readable directory"); + 
rh.setBaseResource(base);
// ...