diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
index eb574295f62..a05a2a78526 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategy.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.server;
+import java.net.URI;
+
/*
* #%L
* HAPI FHIR - Server Framework
@@ -20,68 +22,137 @@ package ca.uhn.fhir.rest.server;
* #L%
*/
+import java.util.Optional;
+
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.ServletServerHttpRequest;
+
+import static java.util.Optional.ofNullable;
+
+import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy;
+
/**
- * Works like the normal {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's an x-forwarded-host present, in which case that's used in place of the server's address.
- *
- * If the Apache Http Server mod_proxy
isn't configured to supply x-forwarded-proto
, the factory method that you use to create the address strategy will determine the default. Note that
- * mod_proxy
doesn't set this by default, but it can be configured via RequestHeader set X-Forwarded-Proto http
(or https)
- *
- *
- * If you want to set the protocol based on something other than the constructor argument, you should be able to do so by overriding protocol
.
- *
- *
- * Note that while this strategy was designed to work with Apache Http Server, and has been tested against it, it should work with any proxy server that sets x-forwarded-host
+ * Works like the normal
+ * {@link ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy} unless there's
+ * an x-forwarded-host present, in which case that's used in place of the
+ * server's address.
+ *
+ * If the Apache Http Server mod_proxy
isn't configured to supply
+ * x-forwarded-proto
, the factory method that you use to create the
+ * address strategy will determine the default. Note that mod_proxy
+ * doesn't set this by default, but it can be configured via
+ * RequestHeader set X-Forwarded-Proto http
(or https)
+ *
+ *
+ * List of supported forward headers:
+ *
+ * - x-forwarded-host - original host requested by the client throw proxy
+ * server
+ *
- x-forwarded-proto - original protocol (http, https) requested by the
+ * client
+ *
- x-forwarded-port - original port request by the client, assume default
+ * port if not defined
+ *
- x-forwarded-prefix - original server prefix / context path requested by
+ * the client
+ *
+ *
+ *
+ * If you want to set the protocol based on something other than the constructor
+ * argument, you should be able to do so by overriding protocol
.
+ *
+ *
+ * Note that while this strategy was designed to work with Apache Http Server,
+ * and has been tested against it, it should work with any proxy server that
+ * sets x-forwarded-host
*
*
- * @author Created by Bill de Beaubien on 3/30/2015.
*/
public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
- private boolean myUseHttps = false;
+ private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
+ private static final String X_FORWARDED_PROTO = "x-forwarded-proto";
+ private static final String X_FORWARDED_HOST = "x-forwarded-host";
+ private static final String X_FORWARDED_PORT = "x-forwarded-port";
- protected ApacheProxyAddressStrategy(boolean theUseHttps) {
- myUseHttps = theUseHttps;
+ private static final Logger LOG = LoggerFactory
+ .getLogger(ApacheProxyAddressStrategy.class);
+
+ private final boolean useHttps;
+
+ /**
+ * @param useHttps
+ * Is used when the {@code x-forwarded-proto} is not set in the
+ * request.
+ */
+ public ApacheProxyAddressStrategy(boolean useHttps) {
+ this.useHttps = useHttps;
}
@Override
- public String determineServerBase(ServletContext theServletContext, HttpServletRequest theRequest) {
- String forwardedHost = getForwardedHost(theRequest);
- if (forwardedHost != null) {
- return forwardedServerBase(theServletContext, theRequest, forwardedHost);
- }
- return super.determineServerBase(theServletContext, theRequest);
+ public String determineServerBase(ServletContext servletContext,
+ HttpServletRequest request) {
+ String serverBase = super.determineServerBase(servletContext, request);
+ ServletServerHttpRequest requestWrapper = new ServletServerHttpRequest(
+ request);
+ HttpHeaders headers = requestWrapper.getHeaders();
+ Optional forwardedHost = headers
+ .getValuesAsList(X_FORWARDED_HOST).stream().findFirst();
+ return forwardedHost
+ .map(s -> forwardedServerBase(serverBase, headers, s))
+ .orElse(serverBase);
}
- public String forwardedServerBase(ServletContext theServletContext, HttpServletRequest theRequest, String theForwardedHost) {
- String serverBase = super.determineServerBase(theServletContext, theRequest);
- String host = theRequest.getHeader("host");
- if (host != null) {
- serverBase = serverBase.replace(host, theForwardedHost);
- serverBase = serverBase.substring(serverBase.indexOf("://"));
- return protocol(theRequest) + serverBase;
- }
- return serverBase;
+ private String forwardedServerBase(String originalServerBase,
+ HttpHeaders headers, String forwardedHost) {
+ Optional forwardedPrefix = getForwardedPrefix(headers);
+ LOG.debug("serverBase: {}, forwardedHost: {}, forwardedPrefix: {}",
+ originalServerBase, forwardedHost, forwardedPrefix);
+ LOG.debug("request header: {}", headers);
+
+ String host = protocol(headers) + "://" + forwardedHost;
+ String hostWithOptionalPort = port(headers).map(p -> (host + ":" + p))
+ .orElse(host);
+
+ String path = forwardedPrefix
+ .orElseGet(() -> pathFrom(originalServerBase));
+ return joinStringsWith(hostWithOptionalPort, path, "/");
}
- private String getForwardedHost(HttpServletRequest theRequest) {
- String forwardedHost = theRequest.getHeader("x-forwarded-host");
- if (forwardedHost != null) {
- int commaPos = forwardedHost.indexOf(',');
- if (commaPos >= 0) {
- forwardedHost = forwardedHost.substring(0, commaPos - 1);
- }
- }
- return forwardedHost;
+ private Optional port(HttpHeaders headers) {
+ return ofNullable(headers.getFirst(X_FORWARDED_PORT));
}
- protected String protocol(HttpServletRequest theRequest) {
- String protocol = theRequest.getHeader("x-forwarded-proto");
+ private String pathFrom(String serverBase) {
+ String serverBasePath = URI.create(serverBase).getPath();
+ return StringUtils.defaultIfBlank(serverBasePath, "");
+ }
+
+ private static String joinStringsWith(String left, String right,
+ String joiner) {
+ if (left.endsWith(joiner) && right.startsWith(joiner)) {
+ return left + right.substring(1);
+ } else if (left.endsWith(joiner) || right.startsWith(joiner)) {
+ return left + right;
+ } else {
+ return left + joiner + right;
+ }
+ }
+
+ private Optional getForwardedPrefix(HttpHeaders headers) {
+ return ofNullable(headers.getFirst(X_FORWARDED_PREFIX));
+ }
+
+ private String protocol(HttpHeaders headers) {
+ String protocol = headers.getFirst(X_FORWARDED_PROTO);
if (protocol != null) {
return protocol;
}
- return myUseHttps ? "https" : "http";
+ return useHttps ? "https" : "http";
}
/**
@@ -97,4 +168,4 @@ public class ApacheProxyAddressStrategy extends IncomingRequestAddressStrategy {
public static ApacheProxyAddressStrategy forHttps() {
return new ApacheProxyAddressStrategy(true);
}
-}
+}
\ No newline at end of file
diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java
new file mode 100644
index 00000000000..8f5c2f3fc47
--- /dev/null
+++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/ApacheProxyAddressStrategyTest.java
@@ -0,0 +1,101 @@
+package ca.uhn.fhir.rest.server;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+public class ApacheProxyAddressStrategyTest {
+
+ @Test
+ public void testWithoutForwarded() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://localhost/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedHostWithoutForwardedProtoHttps() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedHostWithoutForwardedProtoHttp() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ false);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("http://my.example.host/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwarded() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Proto", "https");
+ request.addHeader("X-Forwarded-Prefix", "server-prefix/fhir");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/server-prefix/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedWithHostPrefixWithSlash() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+ request.addHeader("host", "localhost");
+
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Proto", "https");
+ request.addHeader("X-Forwarded-Prefix", "/server-prefix/fhir");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/server-prefix/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedWithoutPrefix() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Proto", "https");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host/imagingstudy/fhir", serverBase);
+ }
+
+ @Test
+ public void testWithForwardedHostAndPort() {
+ ApacheProxyAddressStrategy addressStrategy = new ApacheProxyAddressStrategy(
+ true);
+ MockHttpServletRequest request = prepareRequest();
+
+ request.addHeader("X-Forwarded-Host", "my.example.host");
+ request.addHeader("X-Forwarded-Port", "345");
+ String serverBase = addressStrategy.determineServerBase(null, request);
+ assertEquals("https://my.example.host:345/imagingstudy/fhir",
+ serverBase);
+ }
+
+ private MockHttpServletRequest prepareRequest() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod("POST");
+ request.setScheme("https");
+ request.setServerPort(443);
+ request.setServletPath("/fhir");
+ request.setServerName("localhost");
+ request.setRequestURI("/imagingstudy/fhir/imagingstudy?_format=json");
+ request.setContextPath("/imagingstudy");
+ return request;
+ }
+}
\ No newline at end of file