Make static resource handling consistent across embedded containers
Previously, there were a number of inconsistencies in the embedded containers' handling of static resources. The Servlet spec requires that static resources can be served from the META-INF/resources/ directory of jars nested inside a war in WEB-INF/lib/. The intention was also to extend this to cover jar packaging when jars are nested in BOOT-INF/lib/. This worked when using Tomcat as long as Jasper was on the classpath. If you didn't have Jasper on the classpath or you were using Jetty or Undertow it did not work. This commit updates the configuration of embedded Jetty, Tomcat, and Undertow so that all three containers handle static resources in the same way, serving them from jars in WEB-INF/lib/ or /BOOT-INF/lib/. Numerous intergration tests have been added to verify the behaviour, including tests for Tomcat 8.0 and 7.0 which is supported in addition to the default 8.5.x. Note that static resource handling only works with Jetty 9.3.x and 9.2 and earlier does not support nested jars ( see https://github.com/eclipse/jetty.project/issues/518 for details). Closes gh-8299
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
</properties>
|
||||
<modules>
|
||||
<module>spring-boot-devtools-tests</module>
|
||||
<module>spring-boot-integration-tests-embedded-servlet-container</module>
|
||||
<module>spring-boot-gradle-tests</module>
|
||||
<module>spring-boot-launch-script-tests</module>
|
||||
<module>spring-boot-security-tests</module>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-integration-tests</artifactId>
|
||||
<version>1.4.5.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-boot-integration-tests-embedded-servlet-container</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring Boot Embedded Servlet Container Integration Tests</name>
|
||||
<url>http://projects.spring.io/spring-boot/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>http://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.samskivert</groupId>
|
||||
<artifactId>jmustache</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-invoker</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<maven.home>${maven.home}</maven.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.system.EmbeddedServerPortFileWriter;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Test application for verifying an embedded container's static resource handling.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class ResourceHandlingApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(ResourceHandlingApplication.class)
|
||||
.properties("server.port:0")
|
||||
.listeners(new EmbeddedServerPortFileWriter("target/server.port"))
|
||||
.run(args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean resourceServletRegistration() {
|
||||
ServletRegistrationBean registration = new ServletRegistrationBean(
|
||||
new HttpServlet() {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
URL resource = getServletContext()
|
||||
.getResource(req.getQueryString());
|
||||
if (resource == null) {
|
||||
resp.sendError(404);
|
||||
}
|
||||
else {
|
||||
resp.getWriter().println(resource);
|
||||
resp.getWriter().flush();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
registration.addUrlMappings("/servletContext");
|
||||
return registration;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.lang.ProcessBuilder.Redirect;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Base {@link ExternalResource} for launching a Spring Boot application as part of a
|
||||
* JUnit test.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
abstract class AbstractApplicationLauncher extends ExternalResource {
|
||||
|
||||
private final File serverPortFile = new File("target/server.port");
|
||||
|
||||
private final ApplicationBuilder applicationBuilder;
|
||||
|
||||
private Process process;
|
||||
|
||||
private int httpPort;
|
||||
|
||||
protected AbstractApplicationLauncher(ApplicationBuilder applicationBuilder) {
|
||||
this.applicationBuilder = applicationBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void before() throws Throwable {
|
||||
this.process = startApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void after() {
|
||||
this.process.destroy();
|
||||
}
|
||||
|
||||
public final int getHttpPort() {
|
||||
return this.httpPort;
|
||||
}
|
||||
|
||||
protected abstract List<String> getArguments(File archive);
|
||||
|
||||
private Process startApplication() throws Exception {
|
||||
this.serverPortFile.delete();
|
||||
File archive = this.applicationBuilder.buildApplication();
|
||||
List<String> arguments = new ArrayList<String>();
|
||||
arguments.add(System.getProperty("java.home") + "/bin/java");
|
||||
arguments.addAll(getArguments(archive));
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(
|
||||
arguments.toArray(new String[arguments.size()]));
|
||||
processBuilder.redirectOutput(Redirect.INHERIT);
|
||||
processBuilder.redirectError(Redirect.INHERIT);
|
||||
Process process = processBuilder.start();
|
||||
this.httpPort = awaitServerPort(process);
|
||||
return process;
|
||||
}
|
||||
|
||||
private int awaitServerPort(Process process) throws Exception {
|
||||
long end = System.currentTimeMillis() + 30000;
|
||||
while (this.serverPortFile.length() == 0) {
|
||||
if (System.currentTimeMillis() > end) {
|
||||
throw new IllegalStateException(
|
||||
"server.port file was not written within 30 seconds");
|
||||
}
|
||||
if (!process.isAlive()) {
|
||||
throw new IllegalStateException("Application failed to launch");
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
return Integer.parseInt(
|
||||
FileCopyUtils.copyToString(new FileReader(this.serverPortFile)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.codehaus.plexus.util.StringUtils;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriTemplateHandler;
|
||||
|
||||
/**
|
||||
* Base class for embedded servlet container integration tests.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class AbstractEmbeddedServletContainerIntegrationTests {
|
||||
|
||||
@ClassRule
|
||||
public static final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Rule
|
||||
public final AbstractApplicationLauncher launcher;
|
||||
|
||||
protected final RestTemplate rest = new RestTemplate();
|
||||
|
||||
public static Object[] parameters(String packaging) {
|
||||
List<Object> parameters = new ArrayList<Object>();
|
||||
parameters.addAll(createParameters(packaging, "jetty", "current"));
|
||||
parameters.addAll(
|
||||
createParameters(packaging, "tomcat", "current", "8.0.41", "7.0.75"));
|
||||
parameters.addAll(createParameters(packaging, "undertow", "current"));
|
||||
return parameters.toArray(new Object[parameters.size()]);
|
||||
}
|
||||
|
||||
private static List<Object> createParameters(String packaging, String container,
|
||||
String... versions) {
|
||||
List<Object> parameters = new ArrayList<Object>();
|
||||
for (String version : versions) {
|
||||
ApplicationBuilder applicationBuilder = new ApplicationBuilder(
|
||||
temporaryFolder, packaging, container, version);
|
||||
parameters.add(new Object[] {
|
||||
StringUtils.capitalise(container) + " " + version + " packaged "
|
||||
+ packaging,
|
||||
new PackagedApplicationLauncher(applicationBuilder) });
|
||||
parameters.add(new Object[] {
|
||||
StringUtils.capitalise(container) + " " + version + " exploded "
|
||||
+ packaging,
|
||||
new ExplodedApplicationLauncher(applicationBuilder) });
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
protected AbstractEmbeddedServletContainerIntegrationTests(String name,
|
||||
AbstractApplicationLauncher launcher) {
|
||||
this.launcher = launcher;
|
||||
this.rest.setErrorHandler(new ResponseErrorHandler() {
|
||||
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse response) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
this.rest.setUriTemplateHandler(new UriTemplateHandler() {
|
||||
|
||||
@Override
|
||||
public URI expand(String uriTemplate, Object... uriVariables) {
|
||||
return URI.create(
|
||||
"http://localhost:" + launcher.getHttpPort() + uriTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI expand(String uriTemplate, Map<String, ?> uriVariables) {
|
||||
return URI.create(
|
||||
"http://localhost:" + launcher.getHttpPort() + uriTemplate);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import com.samskivert.mustache.Mustache;
|
||||
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
|
||||
import org.apache.maven.shared.invoker.DefaultInvoker;
|
||||
import org.apache.maven.shared.invoker.InvocationRequest;
|
||||
import org.apache.maven.shared.invoker.InvocationResult;
|
||||
import org.apache.maven.shared.invoker.MavenInvocationException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Builds a Spring Boot application using Maven. To use this class, the {@code maven.home}
|
||||
* system property must be set.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ApplicationBuilder {
|
||||
|
||||
private final TemporaryFolder temp;
|
||||
|
||||
private final String packaging;
|
||||
|
||||
private final String container;
|
||||
|
||||
private final String containerVersion;
|
||||
|
||||
ApplicationBuilder(TemporaryFolder temp, String packaging, String container,
|
||||
String containerVersion) {
|
||||
this.temp = temp;
|
||||
this.packaging = packaging;
|
||||
this.container = container;
|
||||
this.containerVersion = containerVersion;
|
||||
}
|
||||
|
||||
File buildApplication() throws Exception {
|
||||
File containerFolder = new File(this.temp.getRoot(),
|
||||
this.container + "-" + this.containerVersion);
|
||||
if (containerFolder.exists()) {
|
||||
return new File(containerFolder, "app/target/app-0.0.1." + this.packaging);
|
||||
}
|
||||
return doBuildApplication(containerFolder);
|
||||
}
|
||||
|
||||
private File doBuildApplication(File containerFolder)
|
||||
throws IOException, FileNotFoundException, MavenInvocationException {
|
||||
File resourcesJar = createResourcesJar();
|
||||
File appFolder = new File(containerFolder, "app");
|
||||
appFolder.mkdirs();
|
||||
writePom(appFolder, resourcesJar);
|
||||
copyApplicationSource(appFolder);
|
||||
packageApplication(appFolder);
|
||||
return new File(appFolder, "target/app-0.0.1." + this.packaging);
|
||||
}
|
||||
|
||||
private File createResourcesJar() throws IOException, FileNotFoundException {
|
||||
File resourcesJar = new File(this.temp.getRoot(), "resources.jar");
|
||||
if (resourcesJar.exists()) {
|
||||
return resourcesJar;
|
||||
}
|
||||
JarOutputStream resourcesJarStream = new JarOutputStream(
|
||||
new FileOutputStream(resourcesJar));
|
||||
resourcesJarStream.putNextEntry(new ZipEntry("META-INF/resources/"));
|
||||
resourcesJarStream.closeEntry();
|
||||
resourcesJarStream.putNextEntry(
|
||||
new ZipEntry("META-INF/resources/nested-meta-inf-resource.txt"));
|
||||
resourcesJarStream.write("nested".getBytes());
|
||||
resourcesJarStream.closeEntry();
|
||||
resourcesJarStream.close();
|
||||
return resourcesJar;
|
||||
}
|
||||
|
||||
private void writePom(File appFolder, File resourcesJar)
|
||||
throws FileNotFoundException, IOException {
|
||||
Map<String, Object> context = new HashMap<String, Object>();
|
||||
context.put("packaging", this.packaging);
|
||||
context.put("container", this.container);
|
||||
context.put("bootVersion", Versions.getBootVersion());
|
||||
context.put("resourcesJarPath", resourcesJar.getAbsolutePath());
|
||||
context.put("containerVersion",
|
||||
"current".equals(this.containerVersion) ? ""
|
||||
: String.format("<%s.version>%s</%s.version>", this.container,
|
||||
this.containerVersion, this.container));
|
||||
context.put("additionalDependencies", getAdditionalDependencies());
|
||||
FileWriter out = new FileWriter(new File(appFolder, "pom.xml"));
|
||||
Mustache.compiler().escapeHTML(false)
|
||||
.compile(new FileReader("src/test/resources/pom-template.xml"))
|
||||
.execute(context, out);
|
||||
out.close();
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getAdditionalDependencies() {
|
||||
List<Map<String, String>> additionalDependencies = new ArrayList<Map<String, String>>();
|
||||
if ("tomcat".equals(this.container) && !"current".equals(this.containerVersion)) {
|
||||
Map<String, String> juli = new HashMap<String, String>();
|
||||
juli.put("groupId", "org.apache.tomcat");
|
||||
juli.put("artifactId", "tomcat-juli");
|
||||
juli.put("version", "${tomcat.version}");
|
||||
additionalDependencies.add(juli);
|
||||
}
|
||||
return additionalDependencies;
|
||||
}
|
||||
|
||||
private void copyApplicationSource(File appFolder) throws IOException {
|
||||
File examplePackage = new File(appFolder, "src/main/java/com/example");
|
||||
examplePackage.mkdirs();
|
||||
FileCopyUtils.copy(
|
||||
new File("src/test/java/com/example/ResourceHandlingApplication.java"),
|
||||
new File(examplePackage, "ResourceHandlingApplication.java"));
|
||||
if ("war".equals(this.packaging)) {
|
||||
File srcMainWebapp = new File(appFolder, "src/main/webapp");
|
||||
srcMainWebapp.mkdirs();
|
||||
FileCopyUtils.copy("webapp resource",
|
||||
new FileWriter(new File(srcMainWebapp, "webapp-resource.txt")));
|
||||
}
|
||||
}
|
||||
|
||||
private void packageApplication(File appFolder) throws MavenInvocationException {
|
||||
InvocationRequest invocation = new DefaultInvocationRequest();
|
||||
invocation.setBaseDirectory(appFolder);
|
||||
invocation.setGoals(Collections.singletonList("package"));
|
||||
InvocationResult execute = new DefaultInvoker().execute(invocation);
|
||||
assertThat(execute.getExitCode()).isEqualTo(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring Boot's embedded servlet container support using jar
|
||||
* packaging.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class EmbeddedServletContainerJarPackagingIntegrationTests
|
||||
extends AbstractEmbeddedServletContainerIntegrationTests {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Object[] parameters() {
|
||||
return AbstractEmbeddedServletContainerIntegrationTests.parameters("jar");
|
||||
}
|
||||
|
||||
public EmbeddedServletContainerJarPackagingIntegrationTests(String name,
|
||||
AbstractApplicationLauncher launcher) {
|
||||
super(name, launcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMetaInfResourceIsAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMetaInfResourceIsAvailableViaServletContext() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedJarIsNotAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/BOOT-INF/lib/resources-1.0.jar", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applicationClassesAreNotAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest.getForEntity(
|
||||
"/BOOT-INF/classes/com/example/ResourceHandlingApplication.class",
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void launcherIsNotAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest.getForEntity(
|
||||
"/org/springframework/boot/loader/Launcher.class", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for Spring Boot's embedded servlet container support using war
|
||||
* packaging.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class EmbeddedServletContainerWarPackagingIntegrationTests
|
||||
extends AbstractEmbeddedServletContainerIntegrationTests {
|
||||
|
||||
@Parameters(name = "{0}")
|
||||
public static Object[] parameters() {
|
||||
return AbstractEmbeddedServletContainerIntegrationTests.parameters("war");
|
||||
}
|
||||
|
||||
public EmbeddedServletContainerWarPackagingIntegrationTests(String name,
|
||||
AbstractApplicationLauncher launcher) {
|
||||
super(name, launcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMetaInfResourceIsAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedMetaInfResourceIsAvailableViaServletContext() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/nested-meta-inf-resource.txt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedJarIsNotAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest
|
||||
.getForEntity("/WEB-INF/lib/resources-1.0.jar", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applicationClassesAreNotAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest.getForEntity(
|
||||
"/WEB-INF/classes/com/example/ResourceHandlingApplication.class",
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void webappResourcesAreAvailableViaHttp() throws Exception {
|
||||
ResponseEntity<String> entity = this.rest.getForEntity("/webapp-resource.txt",
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* {@link AbstractApplicationLauncher} that launches an exploded Spring Boot application
|
||||
* using Spring Boot's Jar or War launcher.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ExplodedApplicationLauncher extends AbstractApplicationLauncher {
|
||||
|
||||
private final File exploded = new File("target/exploded");
|
||||
|
||||
ExplodedApplicationLauncher(ApplicationBuilder applicationBuilder) {
|
||||
super(applicationBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getArguments(File archive) {
|
||||
String mainClass = archive.getName().endsWith(".war")
|
||||
? "org.springframework.boot.loader.WarLauncher"
|
||||
: "org.springframework.boot.loader.JarLauncher";
|
||||
try {
|
||||
explodeArchive(archive);
|
||||
return Arrays.asList("-cp", this.exploded.getAbsolutePath(), mainClass);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void explodeArchive(File archive) throws IOException {
|
||||
FileSystemUtils.deleteRecursively(this.exploded);
|
||||
JarFile jarFile = new JarFile(archive);
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry jarEntry = entries.nextElement();
|
||||
File extracted = new File(this.exploded, jarEntry.getName());
|
||||
if (jarEntry.isDirectory()) {
|
||||
extracted.mkdirs();
|
||||
}
|
||||
else {
|
||||
FileOutputStream extractedOutputStream = new FileOutputStream(extracted);
|
||||
StreamUtils.copy(jarFile.getInputStream(jarEntry), extractedOutputStream);
|
||||
extractedOutputStream.close();
|
||||
}
|
||||
}
|
||||
jarFile.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link AbstractApplicationLauncher} that launches a packaged Spring Boot application
|
||||
* using {@code java -jar}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class PackagedApplicationLauncher extends AbstractApplicationLauncher {
|
||||
|
||||
PackagedApplicationLauncher(ApplicationBuilder applicationBuilder) {
|
||||
super(applicationBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getArguments(File archive) {
|
||||
return Arrays.asList("-jar", archive.getAbsolutePath());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded;
|
||||
|
||||
import java.io.FileReader;
|
||||
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathExpression;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
/**
|
||||
* Provides access to dependency versions by querying the project's pom.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class Versions {
|
||||
|
||||
private Versions() {
|
||||
}
|
||||
|
||||
public static String getBootVersion() {
|
||||
return evaluateExpression(
|
||||
"/*[local-name()='project']/*[local-name()='parent']/*[local-name()='version']"
|
||||
+ "/text()");
|
||||
}
|
||||
|
||||
private static String evaluateExpression(String expression) {
|
||||
try {
|
||||
XPathFactory xPathFactory = XPathFactory.newInstance();
|
||||
XPath xpath = xPathFactory.newXPath();
|
||||
XPathExpression expr = xpath.compile(expression);
|
||||
String version = expr.evaluate(new InputSource(new FileReader("pom.xml")));
|
||||
return version;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to evaluate expression", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>{{bootVersion}}</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>app</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<packaging>{{packaging}}</packaging>
|
||||
|
||||
<properties>
|
||||
<resourcesJarPath>{{resourcesJarPath}}</resourcesJarPath>
|
||||
{{containerVersion}}
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-{{container}}</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>resources</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${resourcesJarPath}</systemPath>
|
||||
</dependency>
|
||||
{{#additionalDependencies}}
|
||||
<dependency>
|
||||
<groupId>{{groupId}}</groupId>
|
||||
<artifactId>{{artifactId}}</artifactId>
|
||||
<version>{{version}}</version>
|
||||
</dependency>
|
||||
{{/additionalDependencies}}
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<includeSystemScope>true</includeSystemScope>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -20,9 +20,13 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.security.CodeSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -86,6 +90,31 @@ public abstract class AbstractEmbeddedServletContainerFactory
|
||||
return getExplodedWarFileDocumentRoot(getCodeSourceArchive());
|
||||
}
|
||||
|
||||
protected List<URL> getUrlsOfJarsWithMetaInfResources() {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
List<URL> staticResourceUrls = new ArrayList<URL>();
|
||||
if (classLoader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
|
||||
try {
|
||||
URLConnection connection = url.openConnection();
|
||||
if (connection instanceof JarURLConnection) {
|
||||
JarURLConnection jarConnection = (JarURLConnection) connection;
|
||||
JarFile jar = jarConnection.getJarFile();
|
||||
if (jar.getName().endsWith(".jar")
|
||||
&& jar.getJarEntry("META-INF/resources") != null) {
|
||||
staticResourceUrls.add(url);
|
||||
}
|
||||
jar.close();
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return staticResourceUrls;
|
||||
}
|
||||
|
||||
File getExplodedWarFileDocumentRoot(File codeSourceFile) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Code archive: " + codeSourceFile);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -58,6 +58,7 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.servlet.ServletMapping;
|
||||
import org.eclipse.jetty.util.resource.JarResource;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
import org.eclipse.jetty.webapp.AbstractConfiguration;
|
||||
@@ -404,14 +405,21 @@ public class JettyEmbeddedServletContainerFactory
|
||||
File root = getValidDocumentRoot();
|
||||
root = (root != null ? root : createTempDir("jetty-docbase"));
|
||||
try {
|
||||
if (!root.isDirectory()) {
|
||||
Resource resource = JarResource
|
||||
.newJarResource(Resource.newResource(root));
|
||||
handler.setBaseResource(resource);
|
||||
}
|
||||
else {
|
||||
handler.setBaseResource(Resource.newResource(root.getCanonicalFile()));
|
||||
List<Resource> resources = new ArrayList<Resource>();
|
||||
resources.add(
|
||||
root.isDirectory() ? Resource.newResource(root.getCanonicalFile())
|
||||
: JarResource.newJarResource(Resource.newResource(root)));
|
||||
for (URL resourceJarUrl : this.getUrlsOfJarsWithMetaInfResources()) {
|
||||
Resource resource = Resource
|
||||
.newResource(resourceJarUrl + "META-INF/resources");
|
||||
// Jetty 9.2 and earlier do not support nested jars. See
|
||||
// https://github.com/eclipse/jetty.project/issues/518
|
||||
if (resource.exists() && resource.isDirectory()) {
|
||||
resources.add(resource);
|
||||
}
|
||||
}
|
||||
handler.setBaseResource(new ResourceCollection(
|
||||
resources.toArray(new Resource[resources.size()])));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2016 the original author or authors.
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -187,7 +187,7 @@ public class TomcatEmbeddedServletContainerFactory
|
||||
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
|
||||
File docBase = getValidDocumentRoot();
|
||||
docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
|
||||
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
|
||||
final TomcatEmbeddedContext context = new TomcatEmbeddedContext();
|
||||
context.setName(getContextPath());
|
||||
context.setDisplayName(getDisplayName());
|
||||
context.setPath(getContextPath());
|
||||
@@ -217,6 +217,17 @@ public class TomcatEmbeddedServletContainerFactory
|
||||
addJasperInitializer(context);
|
||||
context.addLifecycleListener(new StoreMergedWebXmlListener());
|
||||
}
|
||||
context.addLifecycleListener(new LifecycleListener() {
|
||||
|
||||
@Override
|
||||
public void lifecycleEvent(LifecycleEvent event) {
|
||||
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
|
||||
TomcatResources.get(context)
|
||||
.addResourceJars(getUrlsOfJarsWithMetaInfResources());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
|
||||
configureContext(context, initializersToUse);
|
||||
host.addChild(context);
|
||||
@@ -802,7 +813,6 @@ public class TomcatEmbeddedServletContainerFactory
|
||||
if (servletContext.getAttribute(MERGED_WEB_XML) == null) {
|
||||
servletContext.setAttribute(MERGED_WEB_XML, getEmptyWebXml());
|
||||
}
|
||||
TomcatResources.get(context).addClasspathResources();
|
||||
}
|
||||
|
||||
private String getEmptyWebXml() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,10 @@
|
||||
|
||||
package org.springframework.boot.context.embedded.tomcat;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.servlet.ServletContext;
|
||||
@@ -37,6 +36,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
abstract class TomcatResources {
|
||||
|
||||
@@ -46,28 +46,16 @@ abstract class TomcatResources {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add resources from the classpath.
|
||||
*/
|
||||
public void addClasspathResources() {
|
||||
ClassLoader loader = getClass().getClassLoader();
|
||||
if (loader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) loader).getURLs()) {
|
||||
String file = url.getFile();
|
||||
if (file.endsWith(".jar") || file.endsWith(".jar!/")) {
|
||||
String jar = url.toString();
|
||||
if (!jar.startsWith("jar:")) {
|
||||
// A jar file in the file system. Convert to Jar URL.
|
||||
jar = "jar:" + jar + "!/";
|
||||
}
|
||||
addJar(jar);
|
||||
}
|
||||
else if (url.toString().startsWith("file:")) {
|
||||
String dir = url.toString().substring("file:".length());
|
||||
if (new File(dir).isDirectory()) {
|
||||
addDir(dir, url);
|
||||
}
|
||||
void addResourceJars(List<URL> resourceJarUrls) {
|
||||
for (URL url : resourceJarUrls) {
|
||||
String file = url.getFile();
|
||||
if (file.endsWith(".jar") || file.endsWith(".jar!/")) {
|
||||
String jar = url.toString();
|
||||
if (!jar.startsWith("jar:")) {
|
||||
// A jar file in the file system. Convert to Jar URL.
|
||||
jar = "jar:" + jar + "!/";
|
||||
}
|
||||
addJar(jar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context.embedded.undertow;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.undertow.UndertowMessages;
|
||||
import io.undertow.server.handlers.resource.Resource;
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener;
|
||||
import io.undertow.server.handlers.resource.ResourceManager;
|
||||
|
||||
/**
|
||||
* A {@ResourceManager} that delegates to multiple {@code ResourceManager} instances.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class CompositeResourceManager implements ResourceManager {
|
||||
|
||||
private final List<ResourceManager> resourceManagers;
|
||||
|
||||
CompositeResourceManager(ResourceManager... resourceManagers) {
|
||||
this.resourceManagers = Arrays.asList(resourceManagers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
for (ResourceManager resourceManager : this.resourceManagers) {
|
||||
resourceManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource getResource(String path) throws IOException {
|
||||
for (ResourceManager resourceManager : this.resourceManagers) {
|
||||
Resource resource = resourceManager.getResource(path);
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceChangeListenerSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerResourceChangeListener(ResourceChangeListener listener) {
|
||||
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeResourceChangeListener(ResourceChangeListener listener) {
|
||||
throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.boot.context.embedded.undertow;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
@@ -49,7 +50,10 @@ import io.undertow.server.handlers.accesslog.AccessLogHandler;
|
||||
import io.undertow.server.handlers.accesslog.AccessLogReceiver;
|
||||
import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver;
|
||||
import io.undertow.server.handlers.resource.FileResourceManager;
|
||||
import io.undertow.server.handlers.resource.Resource;
|
||||
import io.undertow.server.handlers.resource.ResourceChangeListener;
|
||||
import io.undertow.server.handlers.resource.ResourceManager;
|
||||
import io.undertow.server.handlers.resource.URLResource;
|
||||
import io.undertow.server.session.SessionManager;
|
||||
import io.undertow.servlet.Servlets;
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
@@ -464,13 +468,11 @@ public class UndertowEmbeddedServletContainerFactory
|
||||
|
||||
private ResourceManager getDocumentRootResourceManager() {
|
||||
File root = getCanonicalDocumentRoot();
|
||||
if (root.isDirectory()) {
|
||||
return new FileResourceManager(root, 0);
|
||||
}
|
||||
if (root.isFile()) {
|
||||
return new JarResourceManager(root);
|
||||
}
|
||||
return ResourceManager.EMPTY_RESOURCE_MANAGER;
|
||||
List<URL> metaInfResourceJarUrls = getUrlsOfJarsWithMetaInfResources();
|
||||
ResourceManager rootResourceManager = root.isDirectory()
|
||||
? new FileResourceManager(root, 0) : new JarResourceManager(root);
|
||||
return new CompositeResourceManager(rootResourceManager,
|
||||
new MetaInfResourcesResourceManager(metaInfResourceJarUrls));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -597,6 +599,49 @@ public class UndertowEmbeddedServletContainerFactory
|
||||
this.useForwardHeaders = useForwardHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ResourceManager} that exposes resource in {@code META-INF/resources}
|
||||
* directory of nested (in {@code BOOT-INF/lib} or {@code WEB-INF/lib}) jars.
|
||||
*/
|
||||
private static final class MetaInfResourcesResourceManager
|
||||
implements ResourceManager {
|
||||
|
||||
private final List<URL> metaInfResourceJarUrls;
|
||||
|
||||
private MetaInfResourcesResourceManager(List<URL> metaInfResourceJarUrls) {
|
||||
this.metaInfResourceJarUrls = metaInfResourceJarUrls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource getResource(String path) throws IOException {
|
||||
for (URL url : this.metaInfResourceJarUrls) {
|
||||
URL resourceUrl = new URL(url + "META-INF/resources" + path);
|
||||
URLConnection connection = resourceUrl.openConnection();
|
||||
if (connection.getContentLength() >= 0) {
|
||||
return new URLResource(resourceUrl, connection, path);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourceChangeListenerSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerResourceChangeListener(ResourceChangeListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeResourceChangeListener(ResourceChangeListener listener) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ServletContainerInitializer} to initialize {@link ServletContextInitializer
|
||||
* ServletContextInitializers}.
|
||||
|
||||
Reference in New Issue
Block a user