Fix relative paths in HTML served by java servlet (for HTML5 pushstate)

I'm trying to modify a Jetty server which has a servlet (RootResource.java) it somehow magically picks up and uses:

@Singleton
@Path("/")
public class RootResource {
    @Context
    private ServletContext servletContext;

    @GET
    @Path("react-client/{path: .*\\..+$}")
    public Response serveReactClientContent(@Context HttpServletRequest httpServletRequest) {
        // This doesn't work, a resolved relative resource is not relative
        // to the /react-client base. See description of problem.
        final String pathToResource = httpServletRequest.getRequestURI();
        return serveStaticContent(pathToResource);
    }

    @GET
    @Path("react-client/{path: .*$}")
    public Response serveReactClientIndexPage(@Context HttpServletRequest httpServletRequest) {
        return serveStaticContent("/react-client/index.html");
    }

    private Response serveStaticContent(String pathToResource) {
        final String type = this.servletContext.getMimeType(pathToResource);
        final Response.ResponseBuilder response = Response.ok(servletContext.getResourceAsStream(pathToResource)).type(type);
        return response.build();
    }
}

The idea is to take a GET request to react-client/some/path and return the contents of react-client/index.html. Essentially making Jetty behave like a webapp server which uses client side routing.

The issue I'm having is that the relative paths in the index.html only work if the path is one level deep e.g. react-client/products.

<script src="./webapp.js"></script>

In that case the javascript file above in index.html is found because webapp.js is a file which exists at react-client/webapp.js.

As soon as I try a deeper link e.g. react-client/products/97357361 that fails as the servlet tries to find webapp.js in react-client/products/webapp.js which doesn't exist.

How can I make it request the resource always as if it's from /react-client? Thanks

728x90

2 Answers Fix relative paths in HTML served by java servlet (for HTML5 pushstate)

I tried a lot of things such as filters and rewrite handlers but at no point did Jetty ever provide the original unresolved relative import (e.g. ./webapp) so I never had an opportunity of rewriting relative resources.

In the end I had to just detect them with a hack in the knowledge that they were nearly all requested from a /static/ sub directory (I had to add a hard coded exception for favicon.ico and manifest.json.

@GET
@Path("react-client/{path: .*\\..+$}")
public Response serveReactClientContent(@Context HttpServletRequest httpServletRequest) {
    final String pathToResource = httpServletRequest.getRequestURI();

    Pattern p = Pattern.compile("/react-client/(.+?(?=static/|favicon.ico))", Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher(pathToResource);
    String resolveResourcePath = m.replaceAll("/react-client/");
    return serveStaticContent(resolveResourcePath);
}

Not happy with it but it works.

4 months ago

Since your index.html could get access by multiple urls such as

  • /react-client/products
  • /react-client/products/97357361
  • /react-client/some
  • /react-client/some/path

It might be wise to add a <base> tag at the header of your HTML page.

<head>
    ...
    <base href="https://www.yourwebsite.com/react-content/" />
    <script src="./script.js"></script>
    ...
</head>

This will tell the browser to resolve any relative path found in the page as relative to the https://www.yourwebsite.com/react-content/

So, according to the example above, <script src="./script.js"></script> would be requested to the server as "https://www.yourwebsite.com/react-content/script.js" regardless of the current url being use to access this page.

Then, you can revert back those settings on Jetty and keep it simple.

4 months ago