0

I have a number of static resources that are included in a jar I build. It is a jetty server, and the static resources are an index.html and some other js/css files. I need to edit the index.html at runtime and I'm wondering how I could go about this, either within my Bootstrap file that starts the server, or from the command line using sed or something similar when the startup script is run.

Any ideas of the best way to go about this? I would prefer the latter solution, one which I can do via shell command.

thanks!

jaibhavaya
  • 108
  • 1
  • 7
  • Vim can do this. –  Apr 27 '18 at 15:04
  • Possible duplicate of [Modifying a file inside a jar](https://stackoverflow.com/questions/1224817/modifying-a-file-inside-a-jar) –  Apr 27 '18 at 15:05
  • I looked over that answer and it didn't really speak to my specific issue. – jaibhavaya Apr 27 '18 at 15:11
  • also @LutzHorn I need to be able to do this programmatically, not manually. – jaibhavaya Apr 27 '18 at 15:12
  • Then check out the file using the `jar` tool, modify it, check it back in. The answers to the linked question talk about how to do this. –  Apr 27 '18 at 15:13
  • 1
    If you want to dynamically change the content at runtime, then the content shouldn't be in the jar, but on the local file system (or you need to copy it from the jar to the local filesystem to use and edit). Once built, jars should be considered immutable. – Mark Rotteveel Apr 27 '18 at 15:22

1 Answers1

1

This would be problematic.

The JAR itself would change.

The runtime would not see the change (as JAR contents are cached in Java)

The binary would need to be restart to pick up the changes.

If you have content that can change at runtime, either put that content outside of the jar in the filesystem, or provide a filesystem override for static jar contents.

For serving content outside of the JAR, see prior answer ...

Serving static files from alternate path in embedded Jetty

For having alternate paths for override, just use a org.eclipse.jetty.util.resource.ResourceCollection for your context.setBaseResource()

Like this ...

package jetty;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

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 org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;

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

        // Jar Resource
        String resourceExample = "/webroot/index.html";
        URL jarUrl = server.getClass().getResource(resourceExample);
        if(jarUrl == null)
        {
            throw new FileNotFoundException("Unable to find JAR Resource: " + resourceExample);
        }
        URI jarUriBase = jarUrl.toURI().resolve("./");
        Resource jarResource = Resource.newResource(jarUriBase);

        // FileSystem Resource
        // Example here uses Path: $TEMP/resource/
        // You can pick a location on disk of your own choice (use a System property if you want)
        Path fsPath = new File(System.getProperty("java.io.tmpdir")).toPath().resolve("resource");
        if(!Files.exists(fsPath))
            Files.createDirectory(fsPath);
        Resource fsResource = new PathResource(fsPath);

        // Resource Collection
        ResourceCollection resourceCollection = new ResourceCollection(
                fsResource, // check FileSystem first
                jarResource // fall back to jar for all content not on filesystem
        );

        System.out.println("Resource Collection: " + resourceCollection);

        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");
        context.setBaseResource(resourceCollection);
        server.setHandler(context);
        context.setWelcomeFiles(new String[] { "index.html" });

        // TODO: Your servlets and filters here

        // Lastly, the default servlet for root content (always needed, to satisfy servlet spec)
        // It is important that this is last.
        ServletHolder holderPwd = new ServletHolder("default", DefaultServlet.class);
        holderPwd.setInitParameter("dirAllowed","true");
        context.addServlet(holderPwd,"/");

        server.setDumpAfterStart(true);
        server.start();
        server.join(); // wait on server to stop
    }

}

This sets up a ResourceCollection with 2 entries.

  1. Check the FileSystem first $TEMP/resource/<requestedResource>
  2. Check the JAR file $JARBASEURI/<requestedResource>

The output of above tells you how it works.

The server dump has details of what the ServletContextHandler resource base is running with.

2018-04-27 10:25:02.314:INFO::main: Logging initialized @338ms to org.eclipse.jetty.util.log.StdErrLog
Resource Collection: [file:///C:/Users/joakim/AppData/Local/Temp/resource/, jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/]
2018-04-27 10:25:02.422:INFO:oejs.Server:main: jetty-9.4.9.v20180320; built: 2018-03-20T07:21:10-05:00; git: 1f8159b1e4a42d3f79997021ea1609f2fbac6de5; jvm 9.0.4+11
2018-04-27 10:25:02.477:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@13c10b87{/,[file:///C:/Users/joakim/AppData/Local/Temp/resource/, jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/],AVAILABLE}
2018-04-27 10:25:02.609:INFO:oejs.AbstractConnector:main: Started ServerConnector@c267ef4{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
org.eclipse.jetty.server.Server@473b46c3[9.4.9.v20180320] - STARTING
...(snip)...
 += o.e.j.s.ServletContextHandler@13c10b87{/,[file:///C:/Users/joakim/AppData/Local/Temp/resource/, jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/],AVAILABLE} - STARTED
 |   += org.eclipse.jetty.servlet.ServletHandler@1aa7ecca - STARTED
 |   |   += default@5c13d641==org.eclipse.jetty.servlet.DefaultServlet,jsp=null,order=-1,inst=false - STARTED
 |   |   |   +- dirAllowed=true
 |   |   +- [/]=>default
 |   +> No ClassLoader
 |   +> Handler attributes o.e.j.s.ServletContextHandler@13c10b87{/,[file:///C:/Users/joakim/AppData/Local/Temp/resource/, jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/],AVAILABLE}
 |   |   +- org.eclipse.jetty.server.Executor=QueuedThreadPool[qtp1018298342]@3cb1ffe6{STARTED,8<=8<=200,i=3,q=0}
 |   +> Context attributes o.e.j.s.ServletContextHandler@13c10b87{/,[file:///C:/Users/joakim/AppData/Local/Temp/resource/, jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/],AVAILABLE}
 |   |   +- org.eclipse.jetty.util.DecoratedObjectFactory=org.eclipse.jetty.util.DecoratedObjectFactory[decorators=1]
 |   +> Initparams o.e.j.s.ServletContextHandler@13c10b87{/,[file:///C:/Users/joakim/AppData/Local/Temp/resource/, jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/],AVAILABLE}
2018-04-27 10:25:02.651:INFO:oejs.Server:main: Started @682ms

In this execution I can see that the ResourceCollection has 2 bases.

  • FileSystem Base: file:///C:/Users/joakim/AppData/Local/Temp/resource/
  • Jar Base: jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/

So if a request arrives for say https://myapp.com/js/config.js then the following happens ...

  1. Does file:///C:/Users/joakim/AppData/Local/Temp/resource/js/config.js exist? If so, serve it.
  2. Does jar:file:///C:/code/jetty-examples/target/project.jar!/webroot/js/config.js exist? If so, serve it.
  3. Report 404 Not Found
Joakim Erdfelt
  • 46,896
  • 7
  • 86
  • 136