Merge branch 'gh-4018'
This commit is contained in:
commit
1e8017232b
|
|
@ -38,6 +38,7 @@ import org.apache.coyote.AbstractProtocol;
|
||||||
import org.apache.coyote.ProtocolHandler;
|
import org.apache.coyote.ProtocolHandler;
|
||||||
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
import org.apache.coyote.http11.AbstractHttp11Protocol;
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties.Session.Cookie;
|
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.Compression;
|
||||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
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.JspServlet;
|
||||||
import org.springframework.boot.context.embedded.ServletContextInitializer;
|
import org.springframework.boot.context.embedded.ServletContextInitializer;
|
||||||
import org.springframework.boot.context.embedded.Ssl;
|
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.TomcatConnectorCustomizer;
|
||||||
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
|
||||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
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.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||||
|
import org.springframework.context.EnvironmentAware;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,7 +73,8 @@ import org.springframework.util.StringUtils;
|
||||||
* @author Marcos Barbero
|
* @author Marcos Barbero
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
|
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
|
||||||
public class ServerProperties implements EmbeddedServletContainerCustomizer, Ordered {
|
public class ServerProperties implements EmbeddedServletContainerCustomizer,
|
||||||
|
EnvironmentAware, Ordered {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server HTTP port.
|
* Server HTTP port.
|
||||||
|
|
@ -102,6 +107,11 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
*/
|
*/
|
||||||
private final Map<String, String> contextParameters = new HashMap<String, String>();
|
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();
|
private Session session = new Session();
|
||||||
|
|
||||||
@NestedConfigurationProperty
|
@NestedConfigurationProperty
|
||||||
|
|
@ -115,13 +125,22 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
|
|
||||||
private final Tomcat tomcat = new Tomcat();
|
private final Tomcat tomcat = new Tomcat();
|
||||||
|
|
||||||
|
private final Jetty jetty = new Jetty();
|
||||||
|
|
||||||
private final Undertow undertow = new Undertow();
|
private final Undertow undertow = new Undertow();
|
||||||
|
|
||||||
|
private Environment environment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOrder() {
|
public int getOrder() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnvironment(Environment environment) {
|
||||||
|
this.environment = environment;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customize(ConfigurableEmbeddedServletContainer container) {
|
public void customize(ConfigurableEmbeddedServletContainer container) {
|
||||||
if (getPort() != null) {
|
if (getPort() != null) {
|
||||||
|
|
@ -150,11 +169,16 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
container.setCompression(getCompression());
|
container.setCompression(getCompression());
|
||||||
}
|
}
|
||||||
if (container instanceof TomcatEmbeddedServletContainerFactory) {
|
if (container instanceof TomcatEmbeddedServletContainerFactory) {
|
||||||
getTomcat()
|
getTomcat().customizeTomcat(this,
|
||||||
.customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
|
(TomcatEmbeddedServletContainerFactory) container);
|
||||||
}
|
}
|
||||||
|
if (container instanceof JettyEmbeddedServletContainerFactory) {
|
||||||
|
getJetty().customizeJetty(this,
|
||||||
|
(JettyEmbeddedServletContainerFactory) container);
|
||||||
|
}
|
||||||
|
|
||||||
if (container instanceof UndertowEmbeddedServletContainerFactory) {
|
if (container instanceof UndertowEmbeddedServletContainerFactory) {
|
||||||
getUndertow().customizeUndertow(
|
getUndertow().customizeUndertow(this,
|
||||||
(UndertowEmbeddedServletContainerFactory) container);
|
(UndertowEmbeddedServletContainerFactory) container);
|
||||||
}
|
}
|
||||||
container.addInitializers(new SessionConfiguringInitializer(this.session));
|
container.addInitializers(new SessionConfiguringInitializer(this.session));
|
||||||
|
|
@ -267,6 +291,22 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
return this.contextParameters;
|
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.
|
* Get the session timeout.
|
||||||
* @return the session timeout
|
* @return the session timeout
|
||||||
|
|
@ -320,6 +360,10 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
return this.tomcat;
|
return this.tomcat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Jetty getJetty() {
|
||||||
|
return this.jetty;
|
||||||
|
}
|
||||||
|
|
||||||
public Undertow getUndertow() {
|
public Undertow getUndertow() {
|
||||||
return this.undertow;
|
return this.undertow;
|
||||||
}
|
}
|
||||||
|
|
@ -488,9 +532,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
|
* 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.
|
* 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.
|
* 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
|
* Name of the http header from which the remote ip is extracted..
|
||||||
* RemoteIpValve only if protocolHeader is also set.
|
|
||||||
*/
|
*/
|
||||||
private String remoteIpHeader = "x-forwarded-for";
|
private String remoteIpHeader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tomcat base directory. If not specified a temporary directory will be used.
|
* 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;
|
this.uriEncoding = uriEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
|
void customizeTomcat(ServerProperties serverProperties,
|
||||||
|
TomcatEmbeddedServletContainerFactory factory) {
|
||||||
if (getBasedir() != null) {
|
if (getBasedir() != null) {
|
||||||
factory.setBaseDirectory(getBasedir());
|
factory.setBaseDirectory(getBasedir());
|
||||||
}
|
}
|
||||||
customizeBackgroundProcessorDelay(factory);
|
customizeBackgroundProcessorDelay(factory);
|
||||||
customizeHeaders(factory);
|
customizeRemoteIpValve(serverProperties, factory);
|
||||||
if (this.maxThreads > 0) {
|
if (this.maxThreads > 0) {
|
||||||
customizeMaxThreads(factory);
|
customizeMaxThreads(factory);
|
||||||
}
|
}
|
||||||
|
|
@ -691,14 +734,20 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void customizeHeaders(TomcatEmbeddedServletContainerFactory factory) {
|
private void customizeRemoteIpValve(ServerProperties properties,
|
||||||
String remoteIpHeader = getRemoteIpHeader();
|
TomcatEmbeddedServletContainerFactory factory) {
|
||||||
String protocolHeader = getProtocolHeader();
|
String protocolHeader = getProtocolHeader();
|
||||||
if (StringUtils.hasText(remoteIpHeader)
|
String remoteIpHeader = getRemoteIpHeader();
|
||||||
&& StringUtils.hasText(protocolHeader)) {
|
// 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();
|
RemoteIpValve valve = new RemoteIpValve();
|
||||||
valve.setRemoteIpHeader(remoteIpHeader);
|
valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader
|
||||||
valve.setProtocolHeader(protocolHeader);
|
: "X-Forwarded-Proto");
|
||||||
|
if (StringUtils.hasLength(remoteIpHeader)) {
|
||||||
|
valve.setRemoteIpHeader(remoteIpHeader);
|
||||||
|
}
|
||||||
// The internal proxies default to a white list of "safe" internal IP
|
// The internal proxies default to a white list of "safe" internal IP
|
||||||
// addresses
|
// addresses
|
||||||
valve.setInternalProxies(getInternalProxies());
|
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 {
|
public static class Undertow {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -958,7 +1016,8 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
getAccesslog().setDir(accessLogDir);
|
getAccesslog().setDir(accessLogDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
void customizeUndertow(UndertowEmbeddedServletContainerFactory factory) {
|
void customizeUndertow(ServerProperties serverProperties,
|
||||||
|
UndertowEmbeddedServletContainerFactory factory) {
|
||||||
factory.setBufferSize(this.bufferSize);
|
factory.setBufferSize(this.bufferSize);
|
||||||
factory.setBuffersPerRegion(this.buffersPerRegion);
|
factory.setBuffersPerRegion(this.buffersPerRegion);
|
||||||
factory.setIoThreads(this.ioThreads);
|
factory.setIoThreads(this.ioThreads);
|
||||||
|
|
@ -967,6 +1026,7 @@ public class ServerProperties implements EmbeddedServletContainerCustomizer, Ord
|
||||||
factory.setAccessLogDirectory(this.accesslog.dir);
|
factory.setAccessLogDirectory(this.accesslog.dir);
|
||||||
factory.setAccessLogPattern(this.accesslog.pattern);
|
factory.setAccessLogPattern(this.accesslog.pattern);
|
||||||
factory.setAccessLogEnabled(this.accesslog.enabled);
|
factory.setAccessLogEnabled(this.accesslog.enabled);
|
||||||
|
factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Accesslog {
|
public static class Accesslog {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,10 @@ import org.springframework.beans.MutablePropertyValues;
|
||||||
import org.springframework.boot.bind.RelaxedDataBinder;
|
import org.springframework.boot.bind.RelaxedDataBinder;
|
||||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||||
import org.springframework.boot.context.embedded.ServletContextInitializer;
|
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.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.equalTo;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
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.atLeastOnce;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,6 +62,7 @@ import static org.mockito.Mockito.verify;
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
public class ServerPropertiesTests {
|
public class ServerPropertiesTests {
|
||||||
|
|
||||||
|
|
@ -239,10 +244,8 @@ public class ServerPropertiesTests {
|
||||||
Map<String, String> map = new HashMap<String, String>();
|
Map<String, String> map = new HashMap<String, String>();
|
||||||
map.put("server.display-name", "MyBootApp");
|
map.put("server.display-name", "MyBootApp");
|
||||||
bindProperties(map);
|
bindProperties(map);
|
||||||
|
|
||||||
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
|
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
|
||||||
this.properties.customize(container);
|
this.properties.customize(container);
|
||||||
|
|
||||||
assertEquals("MyBootApp", container.getDisplayName());
|
assertEquals("MyBootApp", container.getDisplayName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,27 +255,44 @@ public class ServerPropertiesTests {
|
||||||
map.put("server.tomcat.remote_ip_header", "");
|
map.put("server.tomcat.remote_ip_header", "");
|
||||||
map.put("server.tomcat.protocol_header", "");
|
map.put("server.tomcat.protocol_header", "");
|
||||||
bindProperties(map);
|
bindProperties(map);
|
||||||
|
|
||||||
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
|
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
|
||||||
this.properties.customize(container);
|
this.properties.customize(container);
|
||||||
|
|
||||||
assertEquals(0, container.getValves().size());
|
assertEquals(0, container.getValves().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultTomcatRemoteIpValve() throws Exception {
|
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();
|
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
|
||||||
this.properties.customize(container);
|
this.properties.customize(container);
|
||||||
|
|
||||||
assertEquals(1, container.getValves().size());
|
assertEquals(1, container.getValves().size());
|
||||||
Valve valve = container.getValves().iterator().next();
|
Valve valve = container.getValves().iterator().next();
|
||||||
assertThat(valve, instanceOf(RemoteIpValve.class));
|
assertThat(valve, instanceOf(RemoteIpValve.class));
|
||||||
RemoteIpValve remoteIpValve = (RemoteIpValve) valve;
|
RemoteIpValve remoteIpValve = (RemoteIpValve) valve;
|
||||||
assertEquals("x-forwarded-proto", remoteIpValve.getProtocolHeader());
|
assertEquals("X-Forwarded-Proto", remoteIpValve.getProtocolHeader());
|
||||||
assertEquals("https", remoteIpValve.getProtocolHeaderHttpsValue());
|
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
|
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
|
+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
|
||||||
+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/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\\.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\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|"
|
||||||
+ "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}";
|
+ "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}";
|
||||||
|
|
||||||
assertEquals(expectedInternalProxies, remoteIpValve.getInternalProxies());
|
assertEquals(expectedInternalProxies, remoteIpValve.getInternalProxies());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -308,6 +327,52 @@ public class ServerPropertiesTests {
|
||||||
assertEquals("192.168.0.1", remoteIpValve.getInternalProxies());
|
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) {
|
private void bindProperties(Map<String, String> map) {
|
||||||
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(
|
new RelaxedDataBinder(this.properties, "server").bind(new MutablePropertyValues(
|
||||||
map));
|
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.jsp-servlet.registered=true # Whether or not the JSP servlet is registered
|
||||||
server.servlet-path= # the servlet path, defaults to '/'
|
server.servlet-path= # the servlet path, defaults to '/'
|
||||||
server.display-name= # the display name of the application
|
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.persistent=false # true if session should be saved across restarts
|
||||||
server.session.timeout= # session timeout in seconds
|
server.session.timeout= # session timeout in seconds
|
||||||
server.session.tracking-modes= # tracking modes (one or more of "cookie" ,"url", "ssl")
|
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]]
|
[[howto-configure-tomcat]]
|
||||||
=== Configure Tomcat
|
=== Configure Tomcat
|
||||||
Generally you can follow the advice from
|
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]]
|
[[howto-use-jetty-instead-of-tomcat]]
|
||||||
=== Use Jetty instead of Tomcat
|
=== Use Jetty instead of Tomcat
|
||||||
The Spring Boot starters (`spring-boot-starter-web` in particular) use Tomcat as an
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.boot.cloudfoundry;
|
||||||
|
|
||||||
import java.util.Collection;
|
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
|
||||||
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.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.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
|
* 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 Dave Syer
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @deprecated since 1.3.0 in favor of CloudFoundryVcapEnvironmentPostProcessor
|
||||||
*/
|
*/
|
||||||
public class VcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
|
@Deprecated
|
||||||
|
public class VcapEnvironmentPostProcessor extends
|
||||||
private static final Log logger = LogFactory
|
CloudFoundryVcapEnvironmentPostProcessor {
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ import java.util.Set;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.server.AbstractConnector;
|
import org.eclipse.jetty.server.AbstractConnector;
|
||||||
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
|
|
@ -97,6 +99,8 @@ public class JettyEmbeddedServletContainerFactory extends
|
||||||
|
|
||||||
private List<Configuration> configurations = new ArrayList<Configuration>();
|
private List<Configuration> configurations = new ArrayList<Configuration>();
|
||||||
|
|
||||||
|
private boolean useForwardHeaders;
|
||||||
|
|
||||||
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<JettyServerCustomizer>();
|
private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<JettyServerCustomizer>();
|
||||||
|
|
||||||
private ResourceLoader resourceLoader;
|
private ResourceLoader resourceLoader;
|
||||||
|
|
@ -153,6 +157,9 @@ public class JettyEmbeddedServletContainerFactory extends
|
||||||
for (JettyServerCustomizer customizer : getServerCustomizers()) {
|
for (JettyServerCustomizer customizer : getServerCustomizers()) {
|
||||||
customizer.customize(server);
|
customizer.customize(server);
|
||||||
}
|
}
|
||||||
|
if (this.useForwardHeaders) {
|
||||||
|
new ForwardHeadersCustomizer().customize(server);
|
||||||
|
}
|
||||||
return getJettyEmbeddedServletContainer(server);
|
return getJettyEmbeddedServletContainer(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,6 +456,15 @@ public class JettyEmbeddedServletContainerFactory extends
|
||||||
this.resourceLoader = resourceLoader;
|
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}
|
* Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server}
|
||||||
* before it is started. Calling this method will replace any existing configurations.
|
* 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 String contextPath;
|
||||||
|
|
||||||
|
private final boolean useForwardHeaders;
|
||||||
|
|
||||||
private final boolean autoStart;
|
private final boolean autoStart;
|
||||||
|
|
||||||
private final Compression compression;
|
private final Compression compression;
|
||||||
|
|
@ -81,9 +83,16 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
|
||||||
|
|
||||||
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
|
public UndertowEmbeddedServletContainer(Builder builder, DeploymentManager manager,
|
||||||
String contextPath, int port, boolean autoStart, Compression compression) {
|
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.builder = builder;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.contextPath = contextPath;
|
this.contextPath = contextPath;
|
||||||
|
this.useForwardHeaders = useForwardHeaders;
|
||||||
this.autoStart = autoStart;
|
this.autoStart = autoStart;
|
||||||
this.compression = compression;
|
this.compression = compression;
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +114,11 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine
|
||||||
private Undertow createUndertowServer() {
|
private Undertow createUndertowServer() {
|
||||||
try {
|
try {
|
||||||
HttpHandler httpHandler = this.manager.start();
|
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();
|
return this.builder.build();
|
||||||
}
|
}
|
||||||
catch (ServletException ex) {
|
catch (ServletException ex) {
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,8 @@ public class UndertowEmbeddedServletContainerFactory extends
|
||||||
|
|
||||||
private boolean accessLogEnabled = false;
|
private boolean accessLogEnabled = false;
|
||||||
|
|
||||||
|
private boolean useForwardHeaders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link UndertowEmbeddedServletContainerFactory} instance.
|
* Create a new {@link UndertowEmbeddedServletContainerFactory} instance.
|
||||||
*/
|
*/
|
||||||
|
|
@ -220,7 +222,7 @@ public class UndertowEmbeddedServletContainerFactory extends
|
||||||
int port = getPort();
|
int port = getPort();
|
||||||
Builder builder = createBuilder(port);
|
Builder builder = createBuilder(port);
|
||||||
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
|
return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(),
|
||||||
port, port >= 0, getCompression());
|
port, this.useForwardHeaders, port >= 0, getCompression());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Builder createBuilder(int port) {
|
private Builder createBuilder(int port) {
|
||||||
|
|
@ -519,6 +521,15 @@ public class UndertowEmbeddedServletContainerFactory extends
|
||||||
return this.accessLogEnabled;
|
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.
|
* Undertow {@link ResourceManager} for JAR resources.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -28,5 +28,5 @@ org.springframework.boot.logging.LoggingApplicationListener
|
||||||
|
|
||||||
# Environment Post Processors
|
# Environment Post Processors
|
||||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||||
org.springframework.boot.cloudfoundry.VcapEnvironmentPostProcessor,\
|
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
|
||||||
org.springframework.boot.context.config.ConfigFileEnvironmentPostProcessor
|
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.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.boot.cloudfoundry;
|
package org.springframework.boot.cloud.cloudfoundry;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
|
||||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
|
@ -25,14 +26,14 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link VcapEnvironmentPostProcessor}.
|
* Tests for {@link CloudFoundryVcapEnvironmentPostProcessor}.
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Andy Wilkinson
|
* @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();
|
private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
|
|
||||||
|
|
@ -638,8 +638,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
||||||
return "http://localhost:" + port + resourcePath;
|
return "http://localhost:" + port + resourcePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getResponse(String url) throws IOException, URISyntaxException {
|
protected String getResponse(String url, String... headers) throws IOException,
|
||||||
ClientHttpResponse response = getClientResponse(url);
|
URISyntaxException {
|
||||||
|
ClientHttpResponse response = getClientResponse(url, headers);
|
||||||
try {
|
try {
|
||||||
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
|
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
|
||||||
}
|
}
|
||||||
|
|
@ -649,9 +650,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getResponse(String url,
|
protected String getResponse(String url,
|
||||||
HttpComponentsClientHttpRequestFactory requestFactory) throws IOException,
|
HttpComponentsClientHttpRequestFactory requestFactory, String... headers)
|
||||||
URISyntaxException {
|
throws IOException, URISyntaxException {
|
||||||
ClientHttpResponse response = getClientResponse(url, requestFactory);
|
ClientHttpResponse response = getClientResponse(url, requestFactory, headers);
|
||||||
try {
|
try {
|
||||||
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
|
return StreamUtils.copyToString(response.getBody(), Charset.forName("UTF-8"));
|
||||||
}
|
}
|
||||||
|
|
@ -660,8 +661,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ClientHttpResponse getClientResponse(String url) throws IOException,
|
protected ClientHttpResponse getClientResponse(String url, String... headers)
|
||||||
URISyntaxException {
|
throws IOException, URISyntaxException {
|
||||||
return getClientResponse(url, new HttpComponentsClientHttpRequestFactory() {
|
return getClientResponse(url, new HttpComponentsClientHttpRequestFactory() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -669,19 +670,32 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
|
||||||
return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext;
|
return AbstractEmbeddedServletContainerFactoryTests.this.httpClientContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
}, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ClientHttpResponse getClientResponse(String url,
|
protected ClientHttpResponse getClientResponse(String url,
|
||||||
HttpComponentsClientHttpRequestFactory requestFactory) throws IOException,
|
HttpComponentsClientHttpRequestFactory requestFactory, String... headers)
|
||||||
URISyntaxException {
|
throws IOException, URISyntaxException {
|
||||||
ClientHttpRequest request = requestFactory.createRequest(new URI(url),
|
ClientHttpRequest request = requestFactory.createRequest(new URI(url),
|
||||||
HttpMethod.GET);
|
HttpMethod.GET);
|
||||||
request.getHeaders().add("Cookie", "JSESSIONID=" + "123");
|
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();
|
ClientHttpResponse response = request.execute();
|
||||||
return response;
|
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 AbstractEmbeddedServletContainerFactory getFactory();
|
||||||
|
|
||||||
protected abstract Object getJspServlet();
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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";
|
String content = "Hello World";
|
||||||
if (this.echoRequestInfo) {
|
if (this.echoRequestInfo) {
|
||||||
content += " scheme=" + request.getScheme();
|
content += " scheme=" + request.getScheme();
|
||||||
|
content += " remoteaddr=" + request.getRemoteAddr();
|
||||||
}
|
}
|
||||||
response.getWriter().write(content);
|
response.getWriter().write(content);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,13 @@ public class JettyEmbeddedServletContainerFactoryTests extends
|
||||||
assertThat(getJspServlet().getInitParameters(), is(equalTo(initParameters)));
|
assertThat(getJspServlet().getInitParameters(), is(equalTo(initParameters)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useForwardHeaders() throws Exception {
|
||||||
|
JettyEmbeddedServletContainerFactory factory = getFactory();
|
||||||
|
factory.setUseForwardHeaders(true);
|
||||||
|
assertForwardHeaderIsUsed(factory);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
// Workaround for Jetty issue - https://bugs.eclipse.org/bugs/show_bug.cgi?id=470646
|
// 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.Wrapper;
|
||||||
import org.apache.catalina.connector.Connector;
|
import org.apache.catalina.connector.Connector;
|
||||||
import org.apache.catalina.startup.Tomcat;
|
import org.apache.catalina.startup.Tomcat;
|
||||||
|
import org.apache.catalina.valves.RemoteIpValve;
|
||||||
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
|
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
|
|
@ -333,6 +334,13 @@ public class TomcatEmbeddedServletContainerFactoryTests extends
|
||||||
assertThat(jspServlet.findInitParameter("a"), is(equalTo("alpha")));
|
assertThat(jspServlet.findInitParameter("a"), is(equalTo("alpha")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useForwardHeaders() throws Exception {
|
||||||
|
TomcatEmbeddedServletContainerFactory factory = getFactory();
|
||||||
|
factory.addContextValves(new RemoteIpValve());
|
||||||
|
assertForwardHeaderIsUsed(factory);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Wrapper getJspServlet() {
|
protected Wrapper getJspServlet() {
|
||||||
Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()
|
Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,13 @@ public class UndertowEmbeddedServletContainerFactoryTests extends
|
||||||
assertEquals("/", contextPath.get());
|
assertEquals("/", contextPath.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useForwardHeaders() throws Exception {
|
||||||
|
UndertowEmbeddedServletContainerFactory factory = getFactory();
|
||||||
|
factory.setUseForwardHeaders(true);
|
||||||
|
assertForwardHeaderIsUsed(factory);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object getJspServlet() {
|
protected Object getJspServlet() {
|
||||||
return null; // Undertow does not support JSPs
|
return null; // Undertow does not support JSPs
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue