diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/HardcodedServerAddressStrategy.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/HardcodedServerAddressStrategy.java new file mode 100644 index 00000000000..57495b7df04 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/HardcodedServerAddressStrategy.java @@ -0,0 +1,33 @@ +package ca.uhn.fhir.rest.server; + +import javax.servlet.http.HttpServletRequest; + +/** + * Server address strategy which simply returns a hardcoded URL + */ +public class HardcodedServerAddressStrategy implements IServerAddressStrategy { + + private String myValue; + + public HardcodedServerAddressStrategy() { + //nothing + } + + public HardcodedServerAddressStrategy(String theValue) { + myValue=theValue; + } + + @Override + public String determineServerBase(HttpServletRequest theRequest) { + return myValue; + } + + public String getValue() { + return myValue; + } + + public void setValue(String theValue) { + myValue = theValue; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IServerAddressStrategy.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IServerAddressStrategy.java new file mode 100644 index 00000000000..e8dbdb47e61 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IServerAddressStrategy.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.rest.server; + +import javax.servlet.http.HttpServletRequest; + +/** + * Provides the server base for a given incoming request. This can be used to account for + * multi-homed servers or other unusual network configurations. + */ +public interface IServerAddressStrategy { + + /** + * Determine the server base for a given request + */ + public String determineServerBase(HttpServletRequest theRequest); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IncomingRequestAddressStrategy.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IncomingRequestAddressStrategy.java new file mode 100644 index 00000000000..bc4d059e415 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IncomingRequestAddressStrategy.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.rest.server; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; + +/** + * Determines the server's base using the incoming request + */ +public class IncomingRequestAddressStrategy implements IServerAddressStrategy { + + @Override + public String determineServerBase(HttpServletRequest theRequest) { + String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI()); + String servletPath = StringUtils.defaultString(theRequest.getServletPath()); + StringBuffer requestUrl = theRequest.getRequestURL(); + String servletContextPath = ""; + if (theRequest.getServletContext() != null) { + servletContextPath = StringUtils.defaultString(theRequest.getServletContext().getContextPath()); + // } else { + // servletContextPath = servletPath; + } + + String requestPath = requestFullPath.substring(servletContextPath.length() + servletPath.length()); + if (requestPath.length() > 0 && requestPath.charAt(0) == '/') { + requestPath = requestPath.substring(1); + } + + int contextIndex; + if (servletPath.length() == 0) { + if (requestPath.length() == 0) { + contextIndex = requestUrl.length(); + } else { + contextIndex = requestUrl.indexOf(requestPath); + } + } else { + contextIndex = requestUrl.indexOf(servletPath); + } + + String fhirServerBase; + int length = contextIndex + servletPath.length(); + fhirServerBase = requestUrl.substring(0, length); + return fhirServerBase; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index a8b58cf9151..7fb9ed887b3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -91,13 +91,14 @@ public class RestfulServer extends HttpServlet { private Map myResourceNameToProvider = new HashMap(); private Collection myResourceProviders; private ISecurityManager mySecurityManager; + private IServerAddressStrategy myServerAddressStrategy= new IncomingRequestAddressStrategy(); private BaseMethodBinding myServerConformanceMethod; private Object myServerConformanceProvider; private String myServerName = "HAPI FHIR Server"; /** This is configurable but by default we just use HAPI version */ private String myServerVersion = VersionUtil.getVersion(); - private boolean myStarted; + private boolean myStarted; private boolean myUseBrowserFriendlyContentTypes; /** @@ -288,6 +289,14 @@ public class RestfulServer extends HttpServlet { public ISecurityManager getSecurityManager() { return mySecurityManager; } + + /** + * Get the server address strategy, which is used to determine what base URL to + * provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy} + */ + public IServerAddressStrategy getServerAddressStrategy() { + return myServerAddressStrategy; + } /** * Returns the server conformance provider, which is the provider that is used to generate the server's conformance @@ -393,20 +402,8 @@ public class RestfulServer extends HttpServlet { requestPath = requestPath.substring(1); } - int contextIndex; - if (servletPath.length() == 0) { - if (requestPath.length() == 0) { - contextIndex = requestUrl.length(); - } else { - contextIndex = requestUrl.indexOf(requestPath); - } - } else { - contextIndex = requestUrl.indexOf(servletPath); - } - String fhirServerBase; - int length = contextIndex + servletPath.length(); - fhirServerBase = requestUrl.substring(0, length); + fhirServerBase = myServerAddressStrategy.determineServerBase(theRequest); if (fhirServerBase.endsWith("/")) { fhirServerBase = fhirServerBase.substring(0, fhirServerBase.length() - 1); @@ -635,7 +632,7 @@ public class RestfulServer extends HttpServlet { * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the * server being used. */ - protected void initialize() { + protected void initialize() throws ServletException { // nothing by default } @@ -652,6 +649,13 @@ public class RestfulServer extends HttpServlet { myImplementationDescription = theImplementationDescription; } + /** + * Sets the paging provider to use, or null to use no paging (which is the default) + */ + public void setPagingProvider(IPagingProvider thePagingProvider) { + myPagingProvider = thePagingProvider; + } + // /** // * Sets the {@link INarrativeGenerator Narrative Generator} to use when // serializing responses from this server, or null (which is @@ -669,13 +673,6 @@ public class RestfulServer extends HttpServlet { // myNarrativeGenerator = theNarrativeGenerator; // } - /** - * Sets the paging provider to use, or null to use no paging (which is the default) - */ - public void setPagingProvider(IPagingProvider thePagingProvider) { - myPagingProvider = thePagingProvider; - } - /** * Sets the non-resource specific providers which implement method calls on this server. * @@ -724,6 +721,15 @@ public class RestfulServer extends HttpServlet { mySecurityManager = theSecurityManager; } + /** + * Provide a server address strategy, which is used to determine what base URL to + * provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy} + */ + public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) { + Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null"); + myServerAddressStrategy = theServerAddressStrategy; + } + /** * Returns the server conformance provider, which is the provider that is used to generate the server's conformance * (metadata) statement. @@ -923,6 +929,17 @@ public class RestfulServer extends HttpServlet { return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS); } + private static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException { + Writer writer; + if (theRespondGzip) { + theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); + writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8"); + }else { + writer = theHttpResponse.getWriter(); + } + return writer; + } + public static boolean prettyPrintResponse(Request theRequest) { Map requestParams = theRequest.getParameters(); String[] pretty = requestParams.remove(Constants.PARAM_PRETTY); @@ -1034,17 +1051,6 @@ public class RestfulServer extends HttpServlet { } } - private static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException { - Writer writer; - if (theRespondGzip) { - theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); - writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8"); - }else { - writer = theHttpResponse.getWriter(); - } - return writer; - } - public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException { int stausCode = 200; streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerFeaturesTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerFeaturesTest.java index d93c327919b..ea727d2ee52 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerFeaturesTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerFeaturesTest.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import java.util.Collections; import java.util.HashMap; @@ -21,6 +22,7 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.core.IsNot; import org.hamcrest.core.StringContains; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -32,6 +34,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.testutil.RandomServerPortProvider; @@ -43,6 +46,7 @@ public class ServerFeaturesTest { private static CloseableHttpClient ourClient; private static int ourPort; private static Server ourServer; + private static RestfulServer servlet; @Test public void testPrettyPrint() throws Exception { @@ -52,7 +56,8 @@ public class ServerFeaturesTest { HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); HttpResponse status = ourClient.execute(httpGet); - String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); assertThat(responseContent, StringContains.containsString("http://foo/bar/Patient/1")); + + } + @Test public void testAcceptHeaderWithPrettyPrint() throws Exception { HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); - httpGet.addHeader("Accept", Constants.CT_FHIR_XML+ "; pretty=true"); + httpGet.addHeader("Accept", Constants.CT_FHIR_XML + "; pretty=true"); CloseableHttpResponse status = ourClient.execute(httpGet); - String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); assertThat(responseContent, StringContains.containsString("\n ")); httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); - httpGet.addHeader("Accept", Constants.CT_FHIR_JSON+ "; pretty=true"); + httpGet.addHeader("Accept", Constants.CT_FHIR_JSON + "; pretty=true"); status = ourClient.execute(httpGet); - responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); + responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); assertThat(responseContent, StringContains.containsString("\",\n")); } - - + @Test public void testInternalErrorIfNoId() throws Exception { HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/?_query=findPatientsWithNoIdSpecified"); - httpGet.addHeader("Accept", Constants.CT_FHIR_XML+ "; pretty=true"); + httpGet.addHeader("Accept", Constants.CT_FHIR_XML + "; pretty=true"); CloseableHttpResponse status = ourClient.execute(httpGet); - String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent()); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); assertEquals(500, status.getStatusLine().getStatusCode()); assertThat(responseContent, StringContains.containsString("ID")); @@ -185,6 +216,11 @@ public class ServerFeaturesTest { ourServer.stop(); } + @Before + public void before() { + servlet.setServerAddressStrategy(new IncomingRequestAddressStrategy()); + } + @BeforeClass public static void beforeClass() throws Exception { ourPort = RandomServerPortProvider.findFreePort(); @@ -193,7 +229,7 @@ public class ServerFeaturesTest { DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); ServletHandler proxyHandler = new ServletHandler(); - RestfulServer servlet = new RestfulServer(); + servlet = new RestfulServer(); servlet.setResourceProviders(patientProvider); ServletHolder servletHolder = new ServletHolder(servlet); proxyHandler.addServletWithMapping(servletHolder, "/*"); @@ -245,8 +281,25 @@ public class ServerFeaturesTest { public Patient getResourceById(@IdParam IdDt theId) { return getIdToPatient().get(theId.getValue()); } - - @Search(queryName="findPatientsWithNoIdSpecified") + + /** + * Retrieve the resource by its identifier + * + * @param theId + * The resource identity + * @return The resource + */ + @Search() + public List getResourceById(@RequiredParam(name = "_id") String theId) { + Patient patient = getIdToPatient().get(theId); + if (patient != null) { + return Collections.singletonList(patient); + } else { + return Collections.emptyList(); + } + } + + @Search(queryName = "findPatientsWithNoIdSpecified") public List findPatientsWithNoIdSpecified() { Patient p = new Patient(); p.addIdentifier().setSystem("foo"); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java index 47a4c874045..e3740e524b1 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java @@ -1,18 +1,18 @@ package ca.uhn.fhirtest; -import java.sql.DriverManager; import java.util.Collection; +import javax.servlet.ServletException; + +import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.web.context.ContextLoaderListener; -import org.springframework.web.context.WebApplicationContext; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.JpaConformanceProvider; import ca.uhn.fhir.jpa.provider.JpaSystemProvider; import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; @@ -25,7 +25,7 @@ public class TestRestfulServer extends RestfulServer { private ApplicationContext myAppCtx; @Override - protected void initialize() { + protected void initialize() throws ServletException { super.initialize(); // try { @@ -63,6 +63,12 @@ public class TestRestfulServer extends RestfulServer { setUseBrowserFriendlyContentTypes(true); + String baseUrl = System.getProperty("fhir.baseurl"); + if (StringUtils.isBlank(baseUrl)) { + throw new ServletException("Missing system property: fhir.baseurl"); + } + + setServerAddressStrategy(new HardcodedServerAddressStrategy(baseUrl)); setPagingProvider(new FifoMemoryPagingProvider(10)); }