Merge branch 'gh-4018'

pull/4057/merge
Phillip Webb 9 years ago
commit 1e8017232b

@ -38,6 +38,7 @@ import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.boot.autoconfigure.web.ServerProperties.Session.Cookie;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
@ -47,6 +48,7 @@ import org.springframework.boot.context.embedded.InitParameterConfiguringServlet
import org.springframework.boot.context.embedded.JspServlet;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
@ -54,7 +56,9 @@ import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServle
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
@ -69,7 +73,8 @@ import org.springframework.util.StringUtils;
* @author Marcos Barbero
*/
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties implements EmbeddedServletContainerCustomizer, Ordered {
public class ServerProperties implements EmbeddedServletContainerCustomizer,
EnvironmentAware, Ordered {
/**
* Server HTTP port.
@ -102,6 +107,11 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
*/
private final Map<String, String> contextParameters = new HashMap<String, String>();
/**
* If X-Forwarded-* headers should be applied to the HttpRequest.
*/
private Boolean useForwardHeaders;
private Session session = new Session();
@NestedConfigurationProperty
@ -115,13 +125,22 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
private final Tomcat tomcat = new Tomcat();
private final Jetty jetty = new Jetty();
private final Undertow undertow = new Undertow();
private Environment environment;
@Override
public int getOrder() {
return 0;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (getPort() != null) {
@ -150,11 +169,16 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
container.setCompression(getCompression());
}
if (container instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat()
.customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
getTomcat().customizeTomcat(this,
(TomcatEmbeddedServletContainerFactory) container);
}
if (container instanceof JettyEmbeddedServletContainerFactory) {
getJetty().customizeJetty(this,
(JettyEmbeddedServletContainerFactory) container);
}
if (container instanceof UndertowEmbeddedServletContainerFactory) {
getUndertow().customizeUndertow(
getUndertow().customizeUndertow(this,
(UndertowEmbeddedServletContainerFactory) container);
}
container.addInitializers(new SessionConfiguringInitializer(this.session));
@ -267,6 +291,22 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
return this.contextParameters;
}
public Boolean isUseForwardHeaders() {
return this.useForwardHeaders;
}
public void setUseForwardHeaders(Boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders;
}
protected final boolean getOrDeduceUseForwardHeaders() {
if (this.useForwardHeaders != null) {
return this.useForwardHeaders;
}
CloudPlatform platform = CloudPlatform.getActive(this.environment);
return (platform == null ? false : platform.isUsingForwardHeaders());
}
/**
* Get the session timeout.
* @return the session timeout
@ -320,6 +360,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
return this.tomcat;
}
private Jetty getJetty() {
return this.jetty;
}
public Undertow getUndertow() {
return this.undertow;
}
@ -488,9 +532,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
/**
* Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
* Configures a RemoteIpValve only if remoteIpHeader is also set.
*/
private String protocolHeader = "x-forwarded-proto";
private String protocolHeader;
/**
* Value of the protocol header that indicates that the incoming request uses SSL.
@ -500,13 +543,12 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
/**
* Name of the HTTP header used to override the original port value.
*/
private String portHeader = "x-forwarded-port";
private String portHeader = "X-Forwarded-Port";
/**
* Name of the http header from which the remote ip is extracted. Configures a
* RemoteIpValve only if protocolHeader is also set.
* Name of the http header from which the remote ip is extracted..
*/
private String remoteIpHeader = "x-forwarded-for";
private String remoteIpHeader;
/**
* Tomcat base directory. If not specified a temporary directory will be used.
@ -659,12 +701,13 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
this.uriEncoding = uriEncoding;
}
void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
void customizeTomcat(ServerProperties serverProperties,
TomcatEmbeddedServletContainerFactory factory) {
if (getBasedir() != null) {
factory.setBaseDirectory(getBasedir());
}
customizeBackgroundProcessorDelay(factory);
customizeHeaders(factory);
customizeRemoteIpValve(serverProperties, factory);
if (this.maxThreads > 0) {
customizeMaxThreads(factory);
}
@ -691,14 +734,20 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
});
}
private void customizeHeaders(TomcatEmbeddedServletContainerFactory factory) {
String remoteIpHeader = getRemoteIpHeader();
private void customizeRemoteIpValve(ServerProperties properties,
TomcatEmbeddedServletContainerFactory factory) {
String protocolHeader = getProtocolHeader();
if (StringUtils.hasText(remoteIpHeader)
&& StringUtils.hasText(protocolHeader)) {
String remoteIpHeader = getRemoteIpHeader();
// For back compatibility the valve is also enabled if protocol-header is set
if (StringUtils.hasText(protocolHeader)
|| StringUtils.hasText(remoteIpHeader)
|| properties.getOrDeduceUseForwardHeaders()) {
RemoteIpValve valve = new RemoteIpValve();
valve.setRemoteIpHeader(remoteIpHeader);
valve.setProtocolHeader(protocolHeader);
valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader
: "X-Forwarded-Proto");
if (StringUtils.hasLength(remoteIpHeader)) {
valve.setRemoteIpHeader(remoteIpHeader);
}
// The internal proxies default to a white list of "safe" internal IP
// addresses
valve.setInternalProxies(getInternalProxies());
@ -822,6 +871,15 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
}
private static class Jetty {
void customizeJetty(ServerProperties serverProperties,
JettyEmbeddedServletContainerFactory factory) {
factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders());
}
}
public static class Undertow {
/**
@ -958,7 +1016,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
getAccesslog().setDir(accessLogDir);
}
void customizeUndertow(UndertowEmbeddedServletContainerFactory factory) {
void customizeUndertow(ServerProperties serverProperties,
UndertowEmbeddedServletContainerFactory factory) {
factory.setBufferSize(this.bufferSize);
factory.setBuffersPerRegion(this.buffersPerRegion);
factory.setIoThreads(this.ioThreads);
@ -967,6 +1026,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
factory.setAccessLogDirectory(this.accesslog.dir);
factory.setAccessLogPattern(this.accesslog.pattern);
factory.setAccessLogEnabled(this.accesslog.enabled);
factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders());
}
public static class Accesslog {

@ -39,7 +39,10 @@ import org.springframework.beans.MutablePropertyValues;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.mock.env.MockEnvironment;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
@ -50,6 +53,7 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
@ -58,6 +62,7 @@ import static org.mockito.Mockito.verify;
* @author Dave Syer
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
*/
public class ServerPropertiesTests {
@ -239,10 +244,8 @@ public class ServerPropertiesTests {
Map<String, String> map = new HashMap<String, String>();
map.put("server.display-name", "MyBootApp");
bindProperties(map);
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container);
assertEquals("MyBootApp", container.getDisplayName());
}
@ -252,27 +255,44 @@ public class ServerPropertiesTests {
map.put("server.tomcat.remote_ip_header", "");
map.put("server.tomcat.protocol_header", "");
bindProperties(map);
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container);
assertEquals(0, container.getValves().size());
}
@Test
public void defaultTomcatRemoteIpValve() throws Exception {
// Since 1.3.0 no need to explicitly set header names
Map<String, String> map = new HashMap<String, String>();
// Since 1.1.7 you need to specify at least the protocol
map.put("server.tomcat.protocol_header", "X-Forwarded-Proto");
map.put("server.tomcat.remote_ip_header", "X-Forwarded-For");
bindProperties(map);
testRemoteIpValveConfigured();
}
@Test
public void setUseForwardHeadersTomcat() throws Exception {
// Since 1.3.0 no need to explicitly set header names if use-forward-header=true
this.properties.setUseForwardHeaders(true);
testRemoteIpValveConfigured();
}
@Test
public void deduceUseForwardHeadersTomcat() throws Exception {
this.properties.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
testRemoteIpValveConfigured();
}
private void testRemoteIpValveConfigured() {
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
this.properties.customize(container);
assertEquals(1, container.getValves().size());
Valve valve = container.getValves().iterator().next();
assertThat(valve, instanceOf(RemoteIpValve.class));
RemoteIpValve remoteIpValve = (RemoteIpValve) valve;
assertEquals("x-forwarded-proto", remoteIpValve.getProtocolHeader());
assertEquals("X-Forwarded-Proto", remoteIpValve.getProtocolHeader());
assertEquals("https", remoteIpValve.getProtocolHeaderHttpsValue());
assertEquals("x-forwarded-for", remoteIpValve.getRemoteIpHeader());
assertEquals("X-Forwarded-For", remoteIpValve.getRemoteIpHeader());
String expectedInternalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
@ -280,7 +300,6 @@ public class ServerPropertiesTests {
+ "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
+ "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|"
+ "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}";
assertEquals(expectedInternalProxies, remoteIpValve.getInternalProxies());
}
@ -308,6 +327,52 @@ public class ServerPropertiesTests {
assertEquals("192.168.0.1", remoteIpValve.getInternalProxies());
}
@Test
public void defaultUseForwardHeadersUndertow() throws Exception {
UndertowEmbeddedServletContainerFactory container = spy(new UndertowEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(false);
}
@Test
public void setUseForwardHeadersUndertow() throws Exception {
this.properties.setUseForwardHeaders(true);
UndertowEmbeddedServletContainerFactory container = spy(new UndertowEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(true);
}
@Test
public void deduceUseForwardHeadersUndertow() throws Exception {
this.properties.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
UndertowEmbeddedServletContainerFactory container = spy(new UndertowEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(true);
}
@Test
public void defaultUseForwardHeadersJetty() throws Exception {
JettyEmbeddedServletContainerFactory container = spy(new JettyEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(false);
}
@Test
public void setUseForwardHeadersJetty() throws Exception {
this.properties.setUseForwardHeaders(true);
JettyEmbeddedServletContainerFactory container = spy(new JettyEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(true);
}
@Test
public void deduceUseForwardHeadersJetty() throws Exception {
this.properties.setEnvironment(new MockEnvironment().withProperty("DYNO", "-"));
JettyEmbeddedServletContainerFactory container = spy(new JettyEmbeddedServletContainerFactory());
this.properties.customize(container);
verify(container).setUseForwardHeaders(true);
}
private void bindProperties(Map<String, String> map) {
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(
map));

@ -83,6 +83,7 @@ content into your application; rather pick only the properties that you need.
server.jsp-servlet.registered=true # Whether or not the JSP servlet is registered
server.servlet-path= # the servlet path, defaults to '/'
server.display-name= # the display name of the application
server.use-forward-headers= # if X-Forwarded-* headers should be used (default is off unless running in a known cloud)
server.session.persistent=false # true if session should be saved across restarts
server.session.timeout= # session timeout in seconds
server.session.tracking-modes= # tracking modes (one or more of "cookie" ,"url", "ssl")

@ -491,6 +491,59 @@ sample project for an example.
[[howto-use-behind-a-proxy-server]]
[[howto-use-tomcat-behind-a-proxy-server]]
=== Use behind a front-end proxy server
Your application might need to send `302` redirects or render content with absolute links
back to itself. When running behind a proxy, the caller wants a link to the proxy, and not
to the physical address of the machine hosting your app. Typically such situations are
handled via a contract with the proxy, which will add headers to tell the back end how to
construct links to itself.
If the proxy adds conventional `X-Forwarded-For` and `X-Forwarded-Proto` headers (most do
this out of the box) the absolute links should be rendered correctly as long as
`server.use-forward-headers` is set to `true` in your `application.properties`.
NOTE: If your application is running in Cloud Foundry or Heroku the
`server.use-forward-headers` property will default to `true` if not specified. In all
other instances it defaults to `false`.
[[howto-customize-tomcat-behind-a-proxy-server]]
==== Customize Tomcat's proxy configuration
If you are using Tomcat you can additionally configure the names of the headers used to
carry "`forwarded`" information:
[indent=0]
----
server.tomcat.remote-ip-header=x-your-remote-ip-header
server.tomcat.protocol-header=x-your-protocol-header
----
Tomcat is also configured with a default regular expression that matches internal
proxies that are to be trusted. By default, IP addresses in `10/8`, `192.168/16`,
`169.254/16` and `127/8` are trusted. You can customize the valve's configuration by
adding an entry to `application.properties`, e.g.
[indent=0]
----
server.tomcat.internal-proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
----
NOTE: The double backslashes are only required when you're using a properties file for
configuration. If you are using YAML, single backslashes are sufficient and a value
that's equivalent to the one shown above would be `192\.168\.\d{1,3}\.\d{1,3}`.
NOTE: You can trust all proxies by setting the `internal-proxies` to empty (but don't do
this in production).
You can take complete control of the configuration of Tomcat's `RemoteIpValve` by
switching the automatic one off (i.e. set `server.use-forward-headers=false`) and adding
a new valve instance in a `TomcatEmbeddedServletContainerFactory` bean.
[[howto-configure-tomcat]]
=== Configure Tomcat
Generally you can follow the advice from
@ -544,66 +597,6 @@ HTTPS connector:
[[howto-use-tomcat-behind-a-proxy-server]]
=== Use Tomcat behind a front-end proxy server
Your app might need to send 302 redirects, or render UI templates with
absolute links to itself, or hypermedia links back to itself in the
case of a RESTful service. If the app is behind a proxy, the caller
wants a link to the proxy not to the physical address of the app, so
something has to be done in the backend. Typically this is handled via
a contract with the proxy, which will add headers to tell the back end
how to construct links to itself. If the proxy adds conventional
headers (most do this out of the box) the absolute links should be
rendered correctly by default using the Tomcat server.
Spring Boot using Tomcat automatically adds a `RemoteIpValve`. This
transparently takes the standard `x-forwarded-for` and
`x-forwarded-proto` headers and uses them to change local URLs created
in the `HttpServletRequest`. You can configure the header names in
Spring Boot and the valve is switched on unless one or both of these
properties is empty. These values are the defaults and are the
conventional values used by most proxies, so you don't need to set
them unless you need different values:
[indent=0]
----
server.tomcat.remote-ip-header=x-forwarded-for
server.tomcat.protocol-header=x-forwarded-proto
----
If your proxy uses different headers you can customize the valve's configuration by adding
some entries to `application.properties`, e.g.
[indent=0]
----
server.tomcat.remote-ip-header=x-your-remote-ip-header
server.tomcat.protocol-header=x-your-protocol-header
----
The valve is also configured with a default regular expression that matches internal
proxies that are to be trusted. By default, IP addresses in 10/8, 192.168/16, 169.254/16
and 127/8 are trusted. You can customize the valve's configuration by adding an entry
to `application.properties`, e.g.
[indent=0]
----
server.tomcat.internal_proxies=192\\.168\\.\\d{1,3}\\.\\d{1,3}
----
NOTE: The double backslashes are only required when you're using a properties file for
configuration. If you are using YAML, single backslashes are sufficient and a value
that's equivalent to the one shown above would be `192\.168\.\d{1,3}\.\d{1,3}`.
NOTE: You can trust all proxies by setting the `internal_proxies` to empty (but don't do
this in production).
You can take complete control of the configuration of the
`RemoteIpValve` by switching the automatic one off (i.e. set one of
the headers to empty) and adding a new valve instance in a
`TomcatEmbeddedServletContainerFactory` bean.
[[howto-use-jetty-instead-of-tomcat]]
=== Use Jetty instead of Tomcat
The Spring Boot starters (`spring-boot-starter-web` in particular) use Tomcat as an

@ -0,0 +1,241 @@
/*
* Copyright 2010-2015 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.cloud;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.util.StringUtils;
/**
* An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud
* Foundry) meta data in the existing environment. It parses out the VCAP_APPLICATION and
* VCAP_SERVICES meta data and dumps it in a form that is easily consumed by
* {@link Environment} users. If the app is running in Cloud Foundry then both meta data
* items are JSON objects encoded in OS environment variables. VCAP_APPLICATION is a
* shallow hash with basic information about the application (name, instance id, instance
* index, etc.), and VCAP_SERVICES is a hash of lists where the keys are service labels
* and the values are lists of hashes of service instance meta data. Examples are:
*
* <pre class="code">
* VCAP_APPLICATION: {"instance_id":"2ce0ac627a6c8e47e936d829a3a47b5b","instance_index":0,
* "version":"0138c4a6-2a73-416b-aca0-572c09f7ca53","name":"foo",
* "uris":["foo.cfapps.io"], ...}
* VCAP_SERVICES: {"rds-mysql-1.0":[{"name":"mysql","label":"rds-mysql-1.0","plan":"10mb",
* "credentials":{"name":"d04fb13d27d964c62b267bbba1cffb9da","hostname":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com",
* "host":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com","port":3306,"user":"urpRuqTf8Cpe6",
* "username":"urpRuqTf8Cpe6","password":"pxLsGVpsC9A5S"}
* }]}
* </pre>
*
* These objects are flattened into properties. The VCAP_APPLICATION object goes straight
* to {@code vcap.application.*} in a fairly obvious way, and the VCAP_SERVICES object is
* unwrapped so that it is a hash of objects with key equal to the service instance name
* (e.g. "mysql" in the example above), and value equal to that instances properties, and
* then flattened in the same way. E.g.
*
* <pre class="code">
* vcap.application.instance_id: 2ce0ac627a6c8e47e936d829a3a47b5b
* vcap.application.version: 0138c4a6-2a73-416b-aca0-572c09f7ca53
* vcap.application.name: foo
* vcap.application.uris[0]: foo.cfapps.io
*
* vcap.services.mysql.name: mysql
* vcap.services.mysql.label: rds-mysql-1.0
* vcap.services.mysql.credentials.name: d04fb13d27d964c62b267bbba1cffb9da
* vcap.services.mysql.credentials.port: 3306
* vcap.services.mysql.credentials.host: mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com
* vcap.services.mysql.credentials.username: urpRuqTf8Cpe6
* vcap.services.mysql.credentials.password: pxLsGVpsC9A5S
* ...
* </pre>
*
* N.B. this initializer is mainly intended for informational use (the application and
* instance ids are particularly useful). For service binding you might find that Spring
* Cloud is more convenient and more robust against potential changes in Cloud Foundry.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final Log logger = LogFactory
.getLog(CloudFoundryVcapEnvironmentPostProcessor.class);
private static final String VCAP_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES";
// Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1;
private final JsonParser parser = JsonParserFactory.getJsonParser();
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
Properties properties = new Properties();
addWithPrefix(properties, getPropertiesFromApplication(environment),
"vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment),
"vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources
.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
}
private void addWithPrefix(Properties properties, Properties other, String prefix) {
for (String key : other.stringPropertyNames()) {
String prefixed = prefix + key;
properties.setProperty(prefixed, other.getProperty(key));
}
}
private Properties getPropertiesFromApplication(Environment environment) {
Properties properties = new Properties();
try {
String property = environment.getProperty(VCAP_APPLICATION, "{}");
Map<String, Object> map = this.parser.parseMap(property);
extractPropertiesFromApplication(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_APPLICATION", ex);
}
return properties;
}
private Properties getPropertiesFromServices(Environment environment) {
Properties properties = new Properties();
try {
String property = environment.getProperty(VCAP_SERVICES, "{}");
Map<String, Object> map = this.parser.parseMap(property);
extractPropertiesFromServices(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_SERVICES", ex);
}
return properties;
}
private void extractPropertiesFromApplication(Properties properties,
Map<String, Object> map) {
if (map != null) {
flatten(properties, map, "");
}
}
private void extractPropertiesFromServices(Properties properties,
Map<String, Object> map) {
if (map != null) {
for (Object services : map.values()) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) services;
for (Object object : list) {
@SuppressWarnings("unchecked")
Map<String, Object> service = (Map<String, Object>) object;
String key = (String) service.get("name");
if (key == null) {
key = (String) service.get("label");
}
flatten(properties, service, key);
}
}
}
}
@SuppressWarnings("unchecked")
private void flatten(Properties properties, Map<String, Object> input, String path) {
for (Entry<String, Object> entry : input.entrySet()) {
String key = getFullKey(path, entry.getKey());
Object value = entry.getValue();
if (value instanceof Map) {
// Need a compound key
flatten(properties, (Map<String, Object>) value, key);
}
else if (value instanceof Collection) {
// Need a compound key
Collection<Object> collection = (Collection<Object>) value;
properties.put(key,
StringUtils.collectionToCommaDelimitedString(collection));
int count = 0;
for (Object item : collection) {
String itemKey = "[" + (count++) + "]";
flatten(properties, Collections.singletonMap(itemKey, item), key);
}
}
else if (value instanceof String) {
properties.put(key, value);
}
else if (value instanceof Number) {
properties.put(key, value.toString());
}
else if (value instanceof Boolean) {
properties.put(key, value.toString());
}
else {
properties.put(key, value == null ? "" : value);
}
}
}
private String getFullKey(String path, String key) {
if (!StringUtils.hasText(path)) {
return key;
}
if (key.startsWith("[")) {
return path + key;
}
return path + "." + key;
}
}

@ -0,0 +1,88 @@
/*
* Copyright 2012-2015 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.cloud;
import org.springframework.core.env.Environment;
/**
* Simple detection for well known cloud platforms. For more advanced cloud provider
* integration consider the Spring Clould project.
*
* @author Phillip Webb
* @since 1.3.0
* @see "http://cloud.spring.io"
*/
public enum CloudPlatform {
/**
* Cloud Foundry platform.
*/
CLOUD_FOUNDRY {
@Override
public boolean isActive(Environment environment) {
return environment.containsProperty("VCAP_APPLICATION")
|| environment.containsProperty("VCAP_SERVICES");
}
},
/**
* Heroku platform.
*/
HEROKU {
@Override
public boolean isActive(Environment environment) {
return environment.containsProperty("DYNO");
}
};
/**
* Determines if the platform is active (i.e. the application is running in it).
* @param environment the environment
* @return if the platform is active.
*/
public abstract boolean isActive(Environment environment);
/**
* Returns if the platform is behind a load balancer and uses
* {@literal X-Forwarded-For} headers.
* @return if {@literal X-Forwarded-For} headers are used
*/
public boolean isUsingForwardHeaders() {
return true;
}
/**
* Returns the active {@link CloudPlatform} or {@code null} if one cannot be deduced.
* @param environment the environment
* @return the {@link CloudPlatform} or {@code null}
*/
public static CloudPlatform getActive(Environment environment) {
if (environment != null) {
for (CloudPlatform cloudPlatform : values()) {
if (cloudPlatform.isActive(environment)) {
return cloudPlatform;
}
}
}
return null;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2015 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.
@ -15,7 +15,7 @@
*/
/**
* Support for Cloud Foundry PAAS based deployment.
* Low level support for Cloud deployments.
*/
package org.springframework.boot.cloudfoundry;
package org.springframework.boot.cloud;

@ -16,27 +16,9 @@
package org.springframework.boot.cloudfoundry;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor;
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser;
import org.springframework.boot.json.JsonParserFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.util.StringUtils;
/**
* An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud
@ -87,156 +69,10 @@ import org.springframework.util.StringUtils;
*
* @author Dave Syer
* @author Andy Wilkinson
* @deprecated since 1.3.0 in favor of CloudFoundryVcapEnvironmentPostProcessor
*/
public class VcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final Log logger = LogFactory
.getLog(VcapEnvironmentPostProcessor.class);
private static final String VCAP_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES";
// Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1;
private final JsonParser parser = JsonParserFactory.getJsonParser();
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
if (!environment.containsProperty(VCAP_APPLICATION)
&& !environment.containsProperty(VCAP_SERVICES)) {
return;
}
Properties properties = new Properties();
addWithPrefix(properties, getPropertiesFromApplication(environment),
"vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment),
"vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
private void addWithPrefix(Properties properties, Properties other, String prefix) {
for (String key : other.stringPropertyNames()) {
String prefixed = prefix + key;
properties.setProperty(prefixed, other.getProperty(key));
}
}
private Properties getPropertiesFromApplication(Environment environment) {
Properties properties = new Properties();
try {
Map<String, Object> map = this.parser.parseMap(environment.getProperty(
VCAP_APPLICATION, "{}"));
extractPropertiesFromApplication(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_APPLICATION", ex);
}
return properties;
}
private Properties getPropertiesFromServices(Environment environment) {
Properties properties = new Properties();
try {
Map<String, Object> map = this.parser.parseMap(environment.getProperty(
VCAP_SERVICES, "{}"));
extractPropertiesFromServices(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_SERVICES", ex);
}
return properties;
}
private void extractPropertiesFromApplication(Properties properties,
Map<String, Object> map) {
if (map != null) {
flatten(properties, map, "");
}
}
private void extractPropertiesFromServices(Properties properties,
Map<String, Object> map) {
if (map != null) {
for (Object services : map.values()) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) services;
for (Object object : list) {
@SuppressWarnings("unchecked")
Map<String, Object> service = (Map<String, Object>) object;
String key = (String) service.get("name");
if (key == null) {
key = (String) service.get("label");
}
flatten(properties, service, key);
}
}
}
}
@SuppressWarnings("unchecked")
private void flatten(Properties properties, Map<String, Object> input, String path) {
for (Entry<String, Object> entry : input.entrySet()) {
String key = getFullKey(path, entry.getKey());
Object value = entry.getValue();
if (value instanceof Map) {
// Need a compound key
flatten(properties, (Map<String, Object>) value, key);
}
else if (value instanceof Collection) {
// Need a compound key
Collection<Object> collection = (Collection<Object>) value;
properties.put(key,
StringUtils.collectionToCommaDelimitedString(collection));
int count = 0;
for (Object item : collection) {
String itemKey = "[" + (count++) + "]";
flatten(properties, Collections.singletonMap(itemKey, item), key);
}
}
else if (value instanceof String) {
properties.put(key, value);
}
else if (value instanceof Number) {
properties.put(key, value.toString());
}
else if (value instanceof Boolean) {
properties.put(key, value.toString());
}
else {
properties.put(key, value == null ? "" : value);
}
}
}
private String getFullKey(String path, String key) {
if (!StringUtils.hasText(path)) {
return key;
}
if (key.startsWith("[")) {
return path + key;
}
return path + "." + key;
}
@Deprecated
public class VcapEnvironmentPostProcessor extends
CloudFoundryVcapEnvironmentPostProcessor {
}

@ -30,7 +30,9 @@ import java.util.Set;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.AbstractConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
@ -97,6 +99,8 @@ public class JettyEmbeddedServletContainerFactory extends
private List<Configuration> configurations = new ArrayList<Configuration>();
private boolean useForwardHeaders;
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<JettyServerCustomizer>();
private ResourceLoader resourceLoader;
@ -153,6 +157,9 @@ public class JettyEmbeddedServletContainerFactory extends
for (JettyServerCustomizer customizer : getServerCustomizers()) {
customizer.customize(server);
}
if (this.useForwardHeaders) {
new ForwardHeadersCustomizer().customize(server);
}
return getJettyEmbeddedServletContainer(server);
}
@ -449,6 +456,15 @@ public class JettyEmbeddedServletContainerFactory extends
this.resourceLoader = resourceLoader;
}
/**
* Set if x-forward-* headers should be processed.
* @param useForwardHeaders if x-forward headers should be used
* @since 1.3.0
*/
public void setUseForwardHeaders(boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders;
}
/**
* Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server}
* before it is started. Calling this method will replace any existing configurations.
@ -669,4 +685,26 @@ public class JettyEmbeddedServletContainerFactory extends
}
/**
* {@link JettyServerCustomizer} to add {@link ForwardedRequestCustomizer}. Only
* supported with Jetty 9 (hence the inner class)
*/
private static class ForwardHeadersCustomizer implements JettyServerCustomizer {
@Override
public void customize(Server server) {
ForwardedRequestCustomizer customizer = new ForwardedRequestCustomizer();
for (Connector connector : server.getConnectors()) {
for (ConnectionFactory connectionFactory : connector
.getConnectionFactories()) {
if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
((HttpConfiguration.ConnectionFactory) connectionFactory)
.getHttpConfiguration().addCustomizer(customizer);
}
}
}
}
}
}

@ -71,6 +71,8 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
private final String contextPath;
private final boolean useForwardHeaders;
private final boolean autoStart;
private final Compression compression;
@ -81,9 +83,16 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
String contextPath, int port, boolean autoStart, Compression compression) {
this(builder, manager, contextPath, port, false, autoStart, compression);
}
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
String contextPath, int port, boolean useForwardHeaders, boolean autoStart,
Compression compression) {
this.builder = builder;
this.manager = manager;
this.contextPath = contextPath;
this.useForwardHeaders = useForwardHeaders;
this.autoStart = autoStart;
this.compression = compression;
}
@ -105,7 +114,11 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
private Undertow createUndertowServer() {
try {
HttpHandler httpHandler = this.manager.start();
this.builder.setHandler(getContextHandler(httpHandler));
httpHandler = getContextHandler(httpHandler);
if (this.useForwardHeaders) {
httpHandler = Handlers.proxyPeerAddress(httpHandler);
}
this.builder.setHandler(httpHandler);
return this.builder.build();
}
catch (ServletException ex) {

@ -121,6 +121,8 @@ public class UndertowEmbeddedServletContainerFactory extends
private boolean accessLogEnabled = false;
private boolean useForwardHeaders;
/**
* Create a new {@link UndertowEmbeddedServletContainerFactory} instance.
*/
@ -220,7 +222,7 @@ public class UndertowEmbeddedServletContainerFactory extends
int port = getPort();
Builder builder = createBuilder(port);
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
port, port >= 0, getCompression());
port, this.useForwardHeaders, port >= 0, getCompression());
}
private Builder createBuilder(int port) {
@ -519,6 +521,15 @@ public class UndertowEmbeddedServletContainerFactory extends
return this.accessLogEnabled;
}
/**
* Set if x-forward-* headers should be processed.
* @param useForwardHeaders if x-forward headers should be used
* @since 1.3.0
*/
public void setUseForwardHeaders(boolean useForwardHeaders) {
this.useForwardHeaders = useForwardHeaders;
}
/**
* Undertow {@link ResourceManager} for JAR resources.
*/

@ -28,5 +28,5 @@ org.springframework.boot.logging.LoggingApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloudfoundry.VcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor

@ -0,0 +1,75 @@
/*
* Copyright 2012-2015 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.cloud;
import org.junit.Test;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link CloudPlatform}.
*
* @author Phillip Webb
*/
public class CloudPlatformTests {
@Test
public void getActiveWhenEnvironmentIsNullShouldReturnNull() throws Exception {
CloudPlatform platform = CloudPlatform.getActive(null);
assertThat(platform, nullValue());
}
@Test
public void getActiveWhenNotInCloudShouldReturnNull() throws Exception {
Environment environment = new MockEnvironment();
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, nullValue());
}
@Test
public void getActiveWhenHasVcapApplicationShouldReturnCloudFoundry()
throws Exception {
Environment environment = new MockEnvironment().withProperty("VCAP_APPLICATION",
"---");
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, equalTo(CloudPlatform.CLOUD_FOUNDRY));
assertThat(platform.isActive(environment), equalTo(true));
}
@Test
public void getActiveWhenHasVcapServicesShouldReturnCloudFoundry() throws Exception {
Environment environment = new MockEnvironment().withProperty("VCAP_SERVICES",
"---");
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, equalTo(CloudPlatform.CLOUD_FOUNDRY));
assertThat(platform.isActive(environment), equalTo(true));
}
@Test
public void getActiveWhenHasDynoShouldReturnHeroku() throws Exception {
Environment environment = new MockEnvironment().withProperty("DYNO", "---");
CloudPlatform platform = CloudPlatform.getActive(environment);
assertThat(platform, equalTo(CloudPlatform.HEROKU));
assertThat(platform.isActive(environment), equalTo(true));
}
}

@ -14,9 +14,10 @@
* limitations under the License.
*/
package org.springframework.boot.cloudfoundry;
package org.springframework.boot.cloud.cloudfoundry;
import org.junit.Test;
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@ -25,14 +26,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* Tests for {@link VcapEnvironmentPostProcessor}.
* Tests for {@link CloudFoundryVcapEnvironmentPostProcessor}.
*
* @author Dave Syer
* @author Andy Wilkinson
*/
public class VcapEnvironmentPostProcessorTests {
public class CloudFoundryVcapEnvironmentPostProcessorTests {
private final VcapEnvironmentPostProcessor initializer = new VcapEnvironmentPostProcessor();
private final CloudFoundryVcapEnvironmentPostProcessor initializer = new CloudFoundryVcapEnvironmentPostProcessor();
private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();

@ -638,8 +638,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
return "http://localhost:" + port + resourcePath;
}
protected String getResponse(String url) throws IOException, URISyntaxException {
ClientHttpResponse response = getClientResponse(url);
protected String getResponse(String url, String... headers) throws IOException,
URISyntaxException {
ClientHttpResponse response = getClientResponse(url, headers);
try {
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
}
@ -649,9 +650,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
}
protected String getResponse(String url,
HttpComponentsClientHttpRequestFactory requestFactory) throws IOException,
URISyntaxException {
ClientHttpResponse response = getClientResponse(url, requestFactory);
HttpComponentsClientHttpRequestFactory requestFactory, String... headers)
throws IOException, URISyntaxException {
ClientHttpResponse response = getClientResponse(url, requestFactory, headers);
try {
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
}
@ -660,8 +661,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
}
}
protected ClientHttpResponse getClientResponse(String url) throws IOException,
URISyntaxException {
protected ClientHttpResponse getClientResponse(String url, String... headers)
throws IOException, URISyntaxException {
return getClientResponse(url, new HttpComponentsClientHttpRequestFactory() {
@Override
@ -669,19 +670,32 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext;
}
});
}, headers);
}
protected ClientHttpResponse getClientResponse(String url,
HttpComponentsClientHttpRequestFactory requestFactory) throws IOException,
URISyntaxException {
HttpComponentsClientHttpRequestFactory requestFactory, String... headers)
throws IOException, URISyntaxException {
ClientHttpRequest request = requestFactory.createRequest(new URI(url),
HttpMethod.GET);
request.getHeaders().add("Cookie", "JSESSIONID=" + "123");
for (String header : headers) {
String[] parts = header.split(":");
request.getHeaders().add(parts[0], parts[1]);
}
ClientHttpResponse response = request.execute();
return response;
}
protected void assertForwardHeaderIsUsed(EmbeddedServletContainerFactory factory)
throws IOException, URISyntaxException {
this.container = factory.getEmbeddedServletContainer(new ServletRegistrationBean(
new ExampleServlet(true), "/hello"));
this.container.start();
assertThat(getResponse(getLocalUrl("/hello"), "X-Forwarded-For:140.211.11.130"),
containsString("remoteaddr=140.211.11.130"));
}
protected abstract AbstractEmbeddedServletContainerFactory getFactory();
protected abstract Object getJspServlet();

@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-2015 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.
@ -47,6 +47,7 @@ public class ExampleServlet extends GenericServlet {
String content = "Hello World";
if (this.echoRequestInfo) {
content += " scheme=" + request.getScheme();
content += " remoteaddr=" + request.getRemoteAddr();
}
response.getWriter().write(content);
}

@ -183,6 +183,13 @@ public class JettyEmbeddedServletContainerFactoryTests extends
assertThat(getJspServlet().getInitParameters(), is(equalTo(initParameters)));
}
@Test
public void useForwardHeaders() throws Exception {
JettyEmbeddedServletContainerFactory factory = getFactory();
factory.setUseForwardHeaders(true);
assertForwardHeaderIsUsed(factory);
}
@Override
@SuppressWarnings("serial")
// Workaround for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646

@ -35,6 +35,7 @@ import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.junit.Test;
import org.mockito.InOrder;
@ -333,6 +334,13 @@ public class TomcatEmbeddedServletContainerFactoryTests extends
assertThat(jspServlet.findInitParameter("a"), is(equalTo("alpha")));
}
@Test
public void useForwardHeaders() throws Exception {
TomcatEmbeddedServletContainerFactory factory = getFactory();
factory.addContextValves(new RemoteIpValve());
assertForwardHeaderIsUsed(factory);
}
@Override
protected Wrapper getJspServlet() {
Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()

@ -149,6 +149,13 @@ public class UndertowEmbeddedServletContainerFactoryTests extends
assertEquals("/", contextPath.get());
}
@Test
public void useForwardHeaders() throws Exception {
UndertowEmbeddedServletContainerFactory factory = getFactory();
factory.setUseForwardHeaders(true);
assertForwardHeaderIsUsed(factory);
}
@Override
protected Object getJspServlet() {
return null; // Undertow does not support JSPs

Loading…
Cancel
Save