From 11829487de165f9785b6b56e3616284bf72c2b55 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 2 Feb 2024 15:03:25 -0600 Subject: [PATCH] Migrating github.com:jetty-project/embedded-jetty-with-web-resources --- embedded/metainf-resources/README.md | 35 +++++ embedded/metainf-resources/pom.xml | 38 +++++ .../java/examples/MetaInfResourceDemo.java | 74 ++++++++++ .../resources/META-INF/resources/MYREADME.txt | 3 + .../examples/MetaInfResourceDemoTest.java | 139 ++++++++++++++++++ embedded/pom.xml | 1 + 6 files changed, 290 insertions(+) create mode 100755 embedded/metainf-resources/README.md create mode 100644 embedded/metainf-resources/pom.xml create mode 100644 embedded/metainf-resources/src/main/java/examples/MetaInfResourceDemo.java create mode 100755 embedded/metainf-resources/src/main/resources/META-INF/resources/MYREADME.txt create mode 100644 embedded/metainf-resources/src/test/java/examples/MetaInfResourceDemoTest.java diff --git a/embedded/metainf-resources/README.md b/embedded/metainf-resources/README.md new file mode 100755 index 0000000..ea011df --- /dev/null +++ b/embedded/metainf-resources/README.md @@ -0,0 +1,35 @@ +# Using META-INF/resources with ServletContextHandler + +There are many jars available to you on the Global Central Maven Repository System that +provide web resources in the `META-INF/resources` directories contained within those jars. + +Using `META-INF/resources` with embedded-jetty can be accomplished with a full blown +`WebAppContext` and the use of WAR files, but that is often overkill when working +with embedded-jetty. + +Users of embedded-jetty often want to work with the `ServletContextHandler` and +also take advantage of the `META-INF/resources` JAR files. + +This project is an example of combining JAR files from [central.maven.org](https://search.maven.org/) +that contain `META-INF/resources` directories and the `ServletContextHandler` from embedded-jetty. + +What you should pay attention to in this project. + +1. [pom.xml](pom.xml) - the `maven-dependency-plugin` configuration that + unpack's all of the dependencies that have `META-INF/resources` directories + +2. [src/main/resources/META-INF/resources/MYREADME.txt](src/main/resources/META-INF/resources/MYREADME.txt) - + this file is searched for, and should be uniquely named to your project. It can be any resource + a css, an html, a javascript, an image, whatever. Just as long as its name is unique to your + project. + +3. [ExampleServer](src/main/java/examples/MetaInfResourceDemo.java) - this is where + the actual embedded-jetty server resides. + + 1. Create a `ServletContextHandler` + 2. Find the special resource URL location + 3. Create a "Base Resource" that points to the directory location of + the special resource file + 4. Create a `DefaultServlet` that will serve the static files + from the "Base Resource" location you provided. + diff --git a/embedded/metainf-resources/pom.xml b/embedded/metainf-resources/pom.xml new file mode 100644 index 0000000..3f89c39 --- /dev/null +++ b/embedded/metainf-resources/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + org.eclipse.jetty.examples.embedded + jetty-embedded-examples + 9.4.x + + metainf-resources + 9.4.x + jar + Jetty Examples :: Jetty 9.4.x :: Embedded :: Integrating jars with META-INF/resources without WebAppContext + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + org.webjars + bootstrap + 5.3.2 + + + org.eclipse.jetty.toolchain + jetty-test-helper + ${jetty-test-helper.version} + test + + + + diff --git a/embedded/metainf-resources/src/main/java/examples/MetaInfResourceDemo.java b/embedded/metainf-resources/src/main/java/examples/MetaInfResourceDemo.java new file mode 100644 index 0000000..5e1346a --- /dev/null +++ b/embedded/metainf-resources/src/main/java/examples/MetaInfResourceDemo.java @@ -0,0 +1,74 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package examples; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerList; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceCollection; + +public class MetaInfResourceDemo +{ + public static void main(String[] args) throws Exception + { + Server server = MetaInfResourceDemo.newServer(8080); + server.start(); + server.join(); + } + + public static Server newServer(int port) throws Exception + { + Server server = new Server(port); + + HandlerList handlers = new HandlerList(); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + + Resource manifestResources = findManifestResources(MetaInfResourceDemo.class.getClassLoader()); + context.setBaseResource(manifestResources); + + // Add something to serve the static files + // It's named "default" to conform to servlet spec + ServletHolder staticHolder = new ServletHolder("default", DefaultServlet.class); + context.addServlet(staticHolder, "/"); + + handlers.addHandler(context); + handlers.addHandler(new DefaultHandler()); // always last handler + + server.setHandler(handlers); + return server; + } + + private static Resource findManifestResources(ClassLoader classLoader) throws IOException + { + List hits = Collections.list(classLoader.getResources("META-INF/resources")); + int size = hits.size(); + Resource[] resources = new Resource[hits.size()]; + for (int i = 0; i < size; i++) + { + resources[i] = Resource.newResource(hits.get(i)); + } + return new ResourceCollection(resources); + } +} diff --git a/embedded/metainf-resources/src/main/resources/META-INF/resources/MYREADME.txt b/embedded/metainf-resources/src/main/resources/META-INF/resources/MYREADME.txt new file mode 100755 index 0000000..b3ccf95 --- /dev/null +++ b/embedded/metainf-resources/src/main/resources/META-INF/resources/MYREADME.txt @@ -0,0 +1,3 @@ +This file exists in META-INF/resources/ purely so we can find it during Java runtime execution. + +Be sure you pick a filename that is unique and will not be overwritten by your unpacked dependencies. \ No newline at end of file diff --git a/embedded/metainf-resources/src/test/java/examples/MetaInfResourceDemoTest.java b/embedded/metainf-resources/src/test/java/examples/MetaInfResourceDemoTest.java new file mode 100644 index 0000000..55fc5f9 --- /dev/null +++ b/embedded/metainf-resources/src/test/java/examples/MetaInfResourceDemoTest.java @@ -0,0 +1,139 @@ +// +// ======================================================================== +// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package examples; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MetaInfResourceDemoTest +{ + public static Server server; + + public static URI serverBaseURI; + + @BeforeAll + public static void initServer() throws Exception + { + server = MetaInfResourceDemo.newServer(0); + server.start(); + serverBaseURI = server.getURI().resolve("/"); + } + + @AfterAll + public static void stopServer() + { + LifeCycle.stop(server); + } + + @Test + public void testGetMyReadmeResource() throws Exception + { + HttpURLConnection http = (HttpURLConnection)serverBaseURI.resolve("/MYREADME.txt").toURL().openConnection(); + http.connect(); + dumpRequestResponse(http); + assertEquals(HttpURLConnection.HTTP_OK, http.getResponseCode()); + assertEquals("text/plain", http.getHeaderField("Content-Type")); + } + + @Test + public void testGetBootStrapResource() throws Exception + { + String bootstrapFile = findMetaInfResourceFile(MetaInfResourceDemoTest.class.getClassLoader(), "/webjars/bootstrap/", "bootstrap\\.css"); + + HttpURLConnection http = (HttpURLConnection)serverBaseURI.resolve(bootstrapFile).toURL().openConnection(); + http.connect(); + dumpRequestResponse(http); + assertEquals(HttpURLConnection.HTTP_OK, http.getResponseCode()); + assertEquals("text/css", http.getHeaderField("Content-Type")); + } + + /** + * Find the actual version in the jar files, so we don't have to hardcode the version in the testcases. + * + * @param classLoader the classloader to look in + * @param prefix the prefix webjar to look for. + * @param regex the regex to match the first hit against. + * @return the found resource + */ + private String findMetaInfResourceFile(ClassLoader classLoader, String prefix, String regex) throws IOException + { + List hits = Collections.list(classLoader.getResources("META-INF/resources" + prefix)); + for (URL hit : hits) + { + try (Resource res = Resource.newResource(hit)) + { + Resource match = findNestedResource(res, regex); + if (match != null) + { + String rawpath = match.toString(); + int idx; + + // use only part after `!/` + idx = rawpath.lastIndexOf("!/"); + if (idx >= 0) + rawpath = rawpath.substring(idx + 2); + + // find substring starting at prefix + idx = rawpath.indexOf(prefix); + if (idx >= 0) + return rawpath.substring(idx); + return rawpath; + } + } + } + throw new RuntimeException("Unable to find resource [" + regex + "] in " + prefix); + } + + private Resource findNestedResource(Resource res, String regex) throws IOException + { + for (String content : res.list()) + { + Resource subresource = res.addPath(content); + if (content.matches(regex)) + return subresource; + if (subresource.isDirectory()) + { + Resource nested = findNestedResource(subresource, regex); + if (nested != null) + return nested; + } + } + return null; + } + + private static void dumpRequestResponse(HttpURLConnection http) + { + System.out.println(); + System.out.println("----"); + System.out.printf("%s %s HTTP/1.1%n", http.getRequestMethod(), http.getURL()); + System.out.println("----"); + System.out.printf("%s%n", http.getHeaderField(null)); + http.getHeaderFields().entrySet().stream() + .filter(entry -> entry.getKey() != null) + .forEach((entry) -> System.out.printf("%s: %s%n", entry.getKey(), http.getHeaderField(entry.getKey()))); + } +} diff --git a/embedded/pom.xml b/embedded/pom.xml index 4475cb6..d78b07b 100644 --- a/embedded/pom.xml +++ b/embedded/pom.xml @@ -23,6 +23,7 @@ form-post http-config jndi + metainf-resources redirect rewrite servlet-config