Merge pull request #5290 from tsachev:gh-367

* pr/5290:
  Support JSPs in Embedded Jetty
pull/6081/merge
Brian Clozel 9 years ago
commit 8daaf6d93e

@ -1546,6 +1546,27 @@
<artifactId>jetty-io</artifactId> <artifactId>jetty-io</artifactId>
<version>${jetty.version}</version> <version>${jetty.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${jetty.version}</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jstl</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jmx</artifactId> <artifactId>jetty-jmx</artifactId>

@ -2128,7 +2128,8 @@ packaged as an executable archive), there are some limitations in the JSP suppor
and will also be deployable to a standard container (not limited to, but including and will also be deployable to a standard container (not limited to, but including
Tomcat). An executable jar will not work because of a hard coded file pattern in Tomcat. Tomcat). An executable jar will not work because of a hard coded file pattern in Tomcat.
* Jetty does not currently work as an embedded container with JSPs. * With Jetty it should work if you use war packaging, i.e. an executable war will work,
and will also be deployable to any standard container.
* Undertow does not support JSPs. * Undertow does not support JSPs.

@ -111,6 +111,9 @@ The following sample applications are provided:
| link:spring-boot-sample-jetty-ssl[spring-boot-sample-jetty-ssl] | link:spring-boot-sample-jetty-ssl[spring-boot-sample-jetty-ssl]
| Embedded Jetty configured to use SSL | Embedded Jetty configured to use SSL
| link:spring-boot-sample-jetty-jsp[spring-boot-sample-jetty-jsp]
| Web application that uses JSP templates with Jetty
| link:spring-boot-sample-jetty8[spring-boot-sample-jetty8] | link:spring-boot-sample-jetty8[spring-boot-sample-jetty8]
| Embedded Jetty 8 | Embedded Jetty 8

@ -55,6 +55,7 @@
<module>spring-boot-sample-jersey</module> <module>spring-boot-sample-jersey</module>
<module>spring-boot-sample-jersey1</module> <module>spring-boot-sample-jersey1</module>
<module>spring-boot-sample-jetty</module> <module>spring-boot-sample-jetty</module>
<module>spring-boot-sample-jetty-jsp</module>
<module>spring-boot-sample-jetty-ssl</module> <module>spring-boot-sample-jetty-ssl</module>
<module>spring-boot-sample-jetty8</module> <module>spring-boot-sample-jetty8</module>
<module>spring-boot-sample-jetty8-ssl</module> <module>spring-boot-sample-jetty8-ssl</module>

@ -0,0 +1,83 @@
<?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>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-jetty-jsp</artifactId>
<packaging>war</packaging>
<name>Spring Boot Jetty JSP Sample</name>
<description>Spring Boot Jetty JSP Sample</description>
<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>
<m2eclipse.wtp.contextRoot>/</m2eclipse.wtp.contextRoot>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>apache-jsp</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,25 @@
/*
* 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 sample.jetty.jsp;
public class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}

@ -0,0 +1,31 @@
/*
* 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 sample.jetty.jsp;
public class MyRestResponse {
private String message;
public MyRestResponse(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
}

@ -0,0 +1,36 @@
/*
* 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 sample.jetty.jsp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
@SpringBootApplication
public class SampleJettyJspApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SampleJettyJspApplication.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleJettyJspApplication.class, args);
}
}

@ -0,0 +1,59 @@
/*
* 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 sample.jetty.jsp;
import java.util.Date;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@Controller
public class WelcomeController {
@Value("${application.message:Hello World}")
private String message = "Hello World";
@RequestMapping("/")
public String welcome(Map<String, Object> model) {
model.put("time", new Date());
model.put("message", this.message);
return "welcome";
}
@RequestMapping("/fail")
public String fail() {
throw new MyException("Oh dear!");
}
@RequestMapping("/fail2")
public String fail2() {
throw new IllegalStateException();
}
@ExceptionHandler(MyException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public @ResponseBody MyRestResponse handleMyRuntimeException(MyException exception) {
return new MyRestResponse("Some data I want to send back to the client.");
}
}

@ -0,0 +1,3 @@
spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp
application.message: Hello Spring Boot

@ -0,0 +1,18 @@
<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html lang="en">
<body>
<c:url value="/resources/text.txt" var="url"/>
<spring:url value="/resources/text.txt" htmlEscape="true" var="springUrl" />
Spring URL: ${springUrl} at ${time}
<br>
JSTL URL: ${url}
<br>
Message: ${message}
</body>
</html>

@ -0,0 +1,54 @@
/*
* 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 sample.jetty.jsp;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Basic integration tests for JSP application.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@DirtiesContext
public class SampleWebJspApplicationTests {
@LocalServerPort
private int port;
@Test
public void testJspWithEl() throws Exception {
ResponseEntity<String> entity = new TestRestTemplate()
.getForEntity("http://localhost:" + this.port, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("/resources/text.txt");
}
}

@ -0,0 +1,165 @@
/*
* 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.jetty;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import javax.servlet.ServletContainerInitializer;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.util.ClassUtils;
/**
* Jetty {@link AbstractLifeCycle} to initialize jasper.
*
* @author Vladimir Tsanev
*/
public class JasperInitializer extends AbstractLifeCycle {
private final WebAppContext context;
private final ServletContainerInitializer initializer;
JasperInitializer(WebAppContext context) {
this.context = context;
this.initializer = newInitializer();
}
private static ServletContainerInitializer newInitializer() {
try {
try {
return (ServletContainerInitializer) ClassUtils
.forName("org.eclipse.jetty.apache.jsp.JettyJasperInitializer",
null)
.newInstance();
}
catch (Exception ex) {
// try the original initializer
return (ServletContainerInitializer) ClassUtils
.forName("org.apache.jasper.servlet.JasperInitializer", null)
.newInstance();
}
}
catch (Exception ex) {
return null;
}
}
@Override
protected void doStart() throws Exception {
if (this.initializer == null) {
return;
}
try {
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("war".equals(protocol)) {
return new WarUrlStreamHandler();
}
return null;
}
});
}
catch (Error ex) {
// Ignore
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(this.context.getClassLoader());
try {
try {
setExtendedListenerTypes(true);
this.initializer.onStartup(null, this.context.getServletContext());
}
finally {
setExtendedListenerTypes(false);
}
}
finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
}
private void setExtendedListenerTypes(boolean extended) {
try {
this.context.getServletContext().setExtendedListenerTypes(extended);
}
catch (NoSuchMethodError ex) {
// Not available on Jetty 8
}
}
/**
* {@link URLStreamHandler} for {@literal war} protocol compatible with jasper's
* {@link URL urls} produced by
* {@link org.apache.tomcat.util.scan.JarFactory#getJarEntryURL(URL, String)}.
*/
static class WarUrlStreamHandler extends URLStreamHandler {
@Override
protected void parseURL(URL u, String spec, int start, int limit) {
String path = "jar:" + spec.substring("war:".length());
int separator = path.indexOf("*/");
if (separator >= 0) {
path = path.substring(0, separator) + "!/"
+ path.substring(separator + 2);
}
setURL(u, u.getProtocol(), "", -1, null, null, path, null, null);
}
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new WarURLConnection(u);
}
}
/**
* {@link URLConnection} to support {@literal war} protocol.
*/
static class WarURLConnection extends URLConnection {
private final URLConnection connection;
protected WarURLConnection(URL url) throws IOException {
super(url);
this.connection = new URL(url.getFile()).openConnection();
}
@Override
public void connect() throws IOException {
if (!this.connected) {
this.connection.connect();
this.connected = true;
}
}
@Override
public InputStream getInputStream() throws IOException {
connect();
return this.connection.getInputStream();
}
}
}

@ -352,6 +352,7 @@ public class JettyEmbeddedServletContainerFactory
} }
if (shouldRegisterJspServlet()) { if (shouldRegisterJspServlet()) {
addJspServlet(context); addJspServlet(context);
context.addBean(new JasperInitializer(context), true);
} }
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
Configuration[] configurations = getWebAppContextConfigurations(context, Configuration[] configurations = getWebAppContextConfigurations(context,

Loading…
Cancel
Save