javaspringjavalin

How can I properly set up this endpoint?


I'm making an URL shortener with the Javalin framework and have this endpoint set up:

app.routes(()->{
            path("",()->{
                get("/:id", ctx->{
                   //do stuff
                   ctx.redirect("somewhere.com");
                });
            });
        });

Problem is when I need to serve a javascript file to load into my html files. It tries to load from http://localhost:7000/qrcode.min.js but ends up going to the endpoint mentioned above. From what I read in the documentation this is normal behaviour, Javalin first runs the endpoint handler and then (if it doesn't find an endpoint) runs the file handler.

So how can I fix this? should I define a GET request at "/qrcode.min.js"?, I dont think the javalin context handler has a function that lets me return a .js file.


Solution

  • As Matt already suggested in a comment, it would be way cleaner if you'd prefix either path. That way, you could have /r/:id (or /u/:id with "u" for "URL") and the static files would not get in your way, or you could prefix your static files with e.g. /static/, or even just /s/ for brevity, and your shortened URLs would not get in your way.

    If you, however, prefer to stick with your current scheme, you can simply filter out JavaScript files (or any other non-id request) in the handler and instead provide the file (however, if you previously had auto-generated ETags, you'd lose caching if you don't want to handle that yourself).

    The latter solution would look like so:

    app.routes (() -> {
        path ("", () -> {
            get ("/:id", ctx -> {
                String id = ctx.pathParam ("id");
                if (id.endsWith (".js")) {
                    String resourcePath = "your/classpath/resources/folder/" + id;
                    try {
                        InputStream resultStream = Thread.currentThread ()
                                                     .getContextClassLoader ()
                                                     .getResourceAsStream (resourcePath);
    
                        if (resultStream == null)
                            throw new NullPointerException ("Script not found");
    
                        ctx.contentType ("application/javascript");
                        ctx.result (resultStream);
                    } catch (NullPointerException e) { // script does not exist
                        e.printStackTrace (); // for development only!
                        ctx.status (404);
                    }
    
                    return;
                }
                // do stuff
                ctx.redirect ("somewhere.com");
            });
        });
    });
    

    Depending on your preference, you can also handle the resultStream == null case where my code is currently throwing an NPE to be caught by the outer try/catch and omit the try/catch completely.

    Setting the Content-Type is essential so that the browser knows that you're actually responding with JavaScript code. Also, I'm typically using Thread.currentThread ().getContextClassLoader () because we'd want the resource to be resolved based upon the current HTTP handler thread, which could, in theory, have a different class path/class loader than the class we're currently in.

    Please note that, as stated above, this will not support client-side caching as the handler simply ignores all ETag headers sent with the request* and instead respond with the complete file which, with many requests in a short amount of time and large scripts, will certainly put way more stress on your disks and CPUs.

    Thus, I'd actually recommend to prefix the static files route and let Javalin/Jetty handle all the caching and files magic.


    * Actually, the header sent by the client is If-None-Match most of the time. The server would respond with an ETag to allow for caching in the browser.