4

I am using Grizzly to serve my REST service which can have multiple "modules". I'd like to be able to use the same base URL for the service and for static content so I can access all these urls:

The code I'm trying to set this up with looks like this:

private HttpServer createServer(String host, int port, ResourceConfig config)
{               
    HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create("http://" + host + ":" + port + "/"), config, false);

    HttpHandler httpHandler = new CLStaticHttpHandler(HttpServer.class.getClassLoader(), "docs/");
    server.getServerConfiguration().addHttpHandler(httpHandler, "/");

    return server;
}

With this code, I am only able to see the html pages and I get a "Resource identified by path does not exist" response when I try to get my resources. When I comment out the code to add the HttpHandler, then I am able to access my resources (but don't have the docs of course). What do I need to do to access both my resources and my static content?

Amber
  • 2,413
  • 1
  • 15
  • 20

2 Answers2

3

I ended up writing a service to handle static resources myself. I decided to serve my files from the file system, but this approach would also work for serving them from a jar - you'd just have to get the file as a resource instead of creating the File directly.

@Path("/")
public class StaticService
{

    @GET
    @Path("/{docPath:.*}.{ext}")
    public Response getHtml(@PathParam("docPath") String docPath, @PathParam("ext") String ext, @HeaderParam("accept") String accept)
    {
        File file = new File(cleanDocPath(docPath) + "." + ext);
        return Response.ok(file).build();
    }

    @GET
    @Path("{docPath:.*}")
    public Response getFolder(@PathParam("docPath") String docPath)
    {
        File file = null;
        if ("".equals(docPath) || "/".equals(docPath))
        {
            file = new File("index.html");
        }
        else
        {
            file = new File(cleanDocPath(docPath) + "/index.html"); 
        }
        return Response.ok(file).build();
    }

    private String cleanDocPath(String docPath)
    {
        if (docPath.startsWith("/"))
        {
            return docPath.substring(1);
        }
        else
        {
            return docPath;
        }
    }
}
Amber
  • 2,413
  • 1
  • 15
  • 20
2

One thing you can do is run Grizzly as a servlet container. That way you can run Jersey as servlet filter, and add a default servlet to handle the static content. For example

public class Main {
    public static HttpServer createServer() {
        WebappContext context = new WebappContext("GrizzlyContext", "");
        createJerseyFilter(context);
        createDefaultServlet(context);
        HttpServer server = GrizzlyHttpServerFactory
                .createHttpServer(URI.create("http://localhost:8080/"));
        context.deploy(server);
        return server;
    }

    private static void createJerseyFilter(WebappContext context) {
        ResourceConfig rc = new ResourceConfig().packages("com.grizzly.test");
        // This causes Jersey to forward 404s to default servlet
        // which will catch all the static content requests.
        rc.property(ServletProperties.FILTER_FORWARD_ON_404, true);
        FilterRegistration reg = context.addFilter("JerseyApp", new ServletContainer(rc));
        reg.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), "/*");
    }

    private static void createDefaultServlet(WebappContext context) {
        ArraySet<File> baseDir = new ArraySet<>(File.class);
        baseDir.add(new File("."));
        ServletRegistration defaultServletReg 
                = context.addServlet("DefaultServlet", new DefaultServlet(baseDir) {});
        defaultServletReg.addMapping("/*");
    }

    public static void main(String[] args) throws IOException {
        HttpServer server = createServer();      
        System.in.read();
        server.stop();
    }
}

You will need to add the Jersey Grizzly servlet dependency

<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-grizzly2-servlet</artifactId>
    <version>${jersey2.version}</version>
</dependency>

The only problem with this approach is that the default servlet is meant to serve files from the file system, not from the classpath, as you are currently trying to do. You can see in the createDefaultServlet method I just set the base directory to the current working directory. So that's where all your files would need to be. You can change it to "docs" so all your files would be in the docs folder, which would be in the current working directory.

If you want to read files from the classpath, you may need to implement your own servlet. You can look at the source code for DefaultServlet and try to modify it to serve from the classpath. You can also check out Dropwizard's AssetServlet, which already does serve content from the classpath.

Or you can just say forget it, and just serve from the file system :-)

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • I didn't actually try this since I ended up coming up with another work around. If I run into any problems with it I'll give this a try. Thanks for the answer! – Amber May 16 '16 at 15:18