Introduce new CORS interceptor and switch examples to use it

This commit is contained in:
James Agnew 2016-11-21 18:30:51 +01:00
parent 2d52affd8d
commit 888f42a032
28 changed files with 392 additions and 1427 deletions

View File

@ -73,6 +73,10 @@
<version>3.0</version> <version>3.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies> </dependencies>
<reporting> <reporting>

View File

@ -1,17 +1,16 @@
package example; package example;
import java.util.Arrays;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet; import javax.servlet.annotation.WebServlet;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; import ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@ -119,4 +118,38 @@ public class ServletExamples {
} }
// END SNIPPET: responseHighlighterInterceptor // END SNIPPET: responseHighlighterInterceptor
// START SNIPPET: corsInterceptor
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithCors extends RestfulServer {
@Override
protected void initialize() throws ServletException {
// ... define your resource providers here ...
// Define your CORS configuration. This is an example
// showing a typical setup. You should customize this
// to your specific needs
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
// Create the interceptor and register it
CorsInterceptor interceptor = new CorsInterceptor(config);
registerInterceptor(interceptor);
}
}
// END SNIPPET: corsInterceptor
} }

View File

@ -43,15 +43,6 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- Only required for CORS support -->
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<optional>true</optional>
<!-- <exclusions> <exclusion> <artifactId>servlet-api</artifactId> <groupId>javax.servlet</groupId>
</exclusion> </exclusions> -->
</dependency>
<!-- Only required for Schematron Validator Support --> <!-- Only required for Schematron Validator Support -->
<dependency> <dependency>
<groupId>com.phloc</groupId> <groupId>com.phloc</groupId>
@ -123,6 +114,17 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Server --> <!-- Server -->
<dependency> <dependency>

View File

@ -0,0 +1,71 @@
package ca.uhn.fhir.rest.server.interceptor;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsProcessor;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.DefaultCorsProcessor;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class CorsInterceptor extends InterceptorAdapter {
private CorsProcessor myCorsProcessor;
private CorsConfiguration myConfig;
/**
* Constructor
*/
public CorsInterceptor() {
this(new CorsConfiguration());
}
/**
* Constructor
*
* @param theConfiguration
* The CORS configuration
*/
public CorsInterceptor(CorsConfiguration theConfiguration) {
Validate.notNull(theConfiguration, "theConfiguration must not be null");
myCorsProcessor = new DefaultCorsProcessor();
setConfig(theConfiguration);
}
/**
* Sets the CORS configuration
*/
public void setConfig(CorsConfiguration theConfiguration) {
myConfig = theConfiguration;
}
/**
* Gets the CORS configuration
*/
public CorsConfiguration getConfig() {
return myConfig;
}
@Override
public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
if (CorsUtils.isCorsRequest(theRequest)) {
boolean isValid;
try {
isValid = myCorsProcessor.processRequest(myConfig, theRequest, theResponse);
} catch (IOException e) {
throw new InternalErrorException(e);
}
if (!isValid || CorsUtils.isPreFlightRequest(theRequest)) {
return false;
}
}
return super.incomingRequestPreProcessed(theRequest, theResponse);
}
}

View File

@ -171,10 +171,6 @@
<artifactId>thymeleaf-spring4</artifactId> <artifactId>thymeleaf-spring4</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.phloc</groupId> <groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId> <artifactId>phloc-schematron</artifactId>

View File

@ -147,11 +147,6 @@
<groupId>com.phloc</groupId> <groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId> <artifactId>phloc-schematron</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.demo; package ca.uhn.fhir.jpa.demo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -9,6 +10,7 @@ import javax.servlet.ServletException;
import org.hl7.fhir.dstu3.model.Meta; import org.hl7.fhir.dstu3.model.Meta;
import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
@ -30,6 +32,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class JpaServerDemo extends RestfulServer { public class JpaServerDemo extends RestfulServer {
@ -139,6 +142,22 @@ public class JpaServerDemo extends RestfulServer {
*/ */
setPagingProvider(new FifoMemoryPagingProvider(10)); setPagingProvider(new FifoMemoryPagingProvider(10));
// Register a CORS filter
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
registerInterceptor(corsInterceptor);
/* /*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
*/ */

View File

@ -61,52 +61,4 @@
<url-pattern>/</url-pattern> <url-pattern>/</url-pattern>
</servlet-mapping> </servlet-mapping>
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
<filter>
<filter-name>CORS Filter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
<param-name>cors.allowed.headers</param-name>
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
<param-name>cors.exposed.headers</param-name>
<param-value>Location,Content-Location</param-value>
</init-param>
<init-param>
<description>A flag that suggests if CORS is supported with cookies</description>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>A flag to control logging</description>
<param-name>cors.logging.enabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
<param-name>cors.preflight.maxage</param-name>
<param-value>300</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app> </web-app>

View File

@ -130,11 +130,13 @@
<artifactId>commons-dbcp2</artifactId> <artifactId>commons-dbcp2</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!--
<dependency> <dependency>
<groupId>org.apache.tomcat</groupId> <groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId> <artifactId>tomcat-catalina</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
-->
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>

View File

@ -1,8 +1,12 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import javax.el.ExpressionFactory;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class DispatcherServletConfig { public class DispatcherServletConfig {
//nothing //nothing
ExpressionFactory myFacg;
} }

View File

@ -3,18 +3,14 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import org.apache.catalina.filters.CorsFilter;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle;
@ -28,6 +24,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config; import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config;
@ -40,6 +37,7 @@ import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test { public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
@ -116,15 +114,21 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
subsServletHolder.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, WebsocketDstu3Config.class.getName()); subsServletHolder.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, WebsocketDstu3Config.class.getName());
proxyHandler.addServlet(subsServletHolder, "/*"); proxyHandler.addServlet(subsServletHolder, "/*");
FilterHolder corsFilterHolder = new FilterHolder(); // Register a CORS filter
corsFilterHolder.setHeldClass(CorsFilter.class); CorsConfiguration config = new CorsConfiguration();
corsFilterHolder.setInitParameter("cors.allowed.origins", "*"); CorsInterceptor corsInterceptor = new CorsInterceptor(config);
corsFilterHolder.setInitParameter("cors.allowed.methods", "GET,POST,PUT,DELETE,OPTIONS"); config.addAllowedHeader("x-fhir-starter");
corsFilterHolder.setInitParameter("cors.allowed.headers", "X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers"); config.addAllowedHeader("Origin");
corsFilterHolder.setInitParameter("cors.exposed.headers", "Location,Content-Location"); config.addAllowedHeader("Accept");
corsFilterHolder.setInitParameter("cors.support.credentials", "true"); config.addAllowedHeader("X-Requested-With");
corsFilterHolder.setInitParameter("cors.logging.enabled", "true"); config.addAllowedHeader("Content-Type");
proxyHandler.addFilter(corsFilterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
ourRestServer.registerInterceptor(corsInterceptor);
server.setHandler(proxyHandler); server.setHandler(proxyHandler);
server.start(); server.start();

View File

@ -151,17 +151,6 @@
<artifactId>phloc-schematron</artifactId> <artifactId>phloc-schematron</artifactId>
</dependency> </dependency>
<!--
Used for CORS support
If you are deploying your project to Apache Tomcat, you can
comment this dependency out since the tomcat CORS filter will
already be available on the classpath
-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.demo; package ca.uhn.fhir.jpa.demo;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -8,6 +9,7 @@ import javax.servlet.ServletException;
import org.hl7.fhir.dstu3.model.Meta; import org.hl7.fhir.dstu3.model.Meta;
import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
@ -29,6 +31,7 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class JpaServerDemo extends RestfulServer { public class JpaServerDemo extends RestfulServer {
@ -140,6 +143,23 @@ public class JpaServerDemo extends RestfulServer {
*/ */
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
* Enable CORS
*/
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
registerInterceptor(corsInterceptor);
/* /*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java) * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
*/ */

View File

@ -159,17 +159,6 @@
<artifactId>commons-dbcp2</artifactId> <artifactId>commons-dbcp2</artifactId>
</dependency> </dependency>
<!--
Only required for CORS support - Change the scope from
"provided" to "compile" if you are deploying to something
other than Tomcat
-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhirtest; package ca.uhn.fhirtest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -12,6 +13,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.WebsocketDstu2Config; import ca.uhn.fhir.jpa.config.WebsocketDstu2Config;
@ -27,19 +29,15 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.EncodingEnum;
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;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor; import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.TestDstu3Config;
import ca.uhn.fhirtest.config.TdlDstu2Config; import ca.uhn.fhirtest.config.TdlDstu2Config;
import ca.uhn.fhirtest.config.TdlDstu3Config; import ca.uhn.fhirtest.config.TdlDstu3Config;
import ca.uhn.fhirtest.config.TestDstu2Config; import ca.uhn.fhirtest.config.TestDstu2Config;
import ca.uhn.fhirtest.config.TestDstu3Config;
public class TestRestfulServer extends RestfulServer { public class TestRestfulServer extends RestfulServer {
@ -172,6 +170,23 @@ public class TestRestfulServer extends RestfulServer {
setPlainProviders(plainProviders); setPlainProviders(plainProviders);
/*
* Enable CORS
*/
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
registerInterceptor(corsInterceptor);
/* /*
* We want to format the response using nice HTML if it's a browser, since this * We want to format the response using nice HTML if it's a browser, since this
* makes things a little easier for testers. * makes things a little easier for testers.

View File

@ -76,6 +76,7 @@
<load-on-startup>1</load-on-startup> <load-on-startup>1</load-on-startup>
</servlet> </servlet>
<!--
<servlet> <servlet>
<servlet-name>fhirServletTdl2</servlet-name> <servlet-name>fhirServletTdl2</servlet-name>
<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class> <servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
@ -89,7 +90,6 @@
</init-param> </init-param>
<load-on-startup>1</load-on-startup> <load-on-startup>1</load-on-startup>
</servlet> </servlet>
<servlet> <servlet>
<servlet-name>fhirServletTdl3</servlet-name> <servlet-name>fhirServletTdl3</servlet-name>
<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class> <servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
@ -103,6 +103,7 @@
</init-param> </init-param>
<load-on-startup>1</load-on-startup> <load-on-startup>1</load-on-startup>
</servlet> </servlet>
-->
<servlet-mapping> <servlet-mapping>
<servlet-name>fhirServletDstu1</servlet-name> <servlet-name>fhirServletDstu1</servlet-name>
@ -132,11 +133,11 @@
<url-pattern>/baseStu3/*</url-pattern> <url-pattern>/baseStu3/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<!--
<servlet-mapping> <servlet-mapping>
<servlet-name>fhirServletTdl2</servlet-name> <servlet-name>fhirServletTdl2</servlet-name>
<url-pattern>/testDataLibraryDstu2/*</url-pattern> <url-pattern>/testDataLibraryDstu2/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet-mapping> <servlet-mapping>
<servlet-name>fhirServletTdl3</servlet-name> <servlet-name>fhirServletTdl3</servlet-name>
<url-pattern>/testDataLibraryDstu3/*</url-pattern> <url-pattern>/testDataLibraryDstu3/*</url-pattern>
@ -145,6 +146,7 @@
<servlet-name>fhirServletTdl3</servlet-name> <servlet-name>fhirServletTdl3</servlet-name>
<url-pattern>/testDataLibraryStu3/*</url-pattern> <url-pattern>/testDataLibraryStu3/*</url-pattern>
</servlet-mapping> </servlet-mapping>
-->
<servlet-mapping> <servlet-mapping>
<servlet-name>fhirServletDstu2</servlet-name> <servlet-name>fhirServletDstu2</servlet-name>
@ -156,52 +158,4 @@
<url-pattern>/</url-pattern> <url-pattern>/</url-pattern>
</servlet-mapping> </servlet-mapping>
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
<filter>
<filter-name>CORS Filter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
<param-name>cors.allowed.headers</param-name>
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
<param-name>cors.exposed.headers</param-name>
<param-value>Location,Content-Location</param-value>
</init-param>
<init-param>
<description>A flag that suggests if CORS is supported with cookies</description>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>A flag to control logging</description>
<param-name>cors.logging.enabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
<param-name>cors.preflight.maxage</param-name>
<param-value>300</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app> </web-app>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId> <artifactId>hapi-deployable-pom</artifactId>
<version>2.1-SNAPSHOT</version> <version>2.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath> <relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent> </parent>
@ -19,18 +19,15 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId> <artifactId>hapi-fhir-base</artifactId>
<version>2.1-SNAPSHOT</version> <version>2.2-SNAPSHOT</version>
</dependency>
<!-- conformance profile -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>2.1-SNAPSHOT</version>
</dependency> </dependency>
<!-- Unit test dependencies -->
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId> <artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>2.1-SNAPSHOT</version> <version>2.2-SNAPSHOT</version>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -73,11 +73,6 @@
<optional>true</optional> <optional>true</optional>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>
@ -94,13 +89,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency> <dependency>
<groupId>org.apache.tomcat</groupId> <!-- This is used for cors -->
<artifactId>tomcat-catalina</artifactId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency> <dependency>
<groupId>net.sf.json-lib</groupId> <groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId> <artifactId>json-lib</artifactId>

View File

@ -1,15 +1,15 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import org.apache.catalina.filters.CorsFilter;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -29,11 +29,14 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.server.RestfulServerSelfReferenceTest.DummyPatientResourceProvider; import ca.uhn.fhir.rest.server.RestfulServerSelfReferenceTest.DummyPatientResourceProvider;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
@ -55,11 +58,26 @@ public class CorsTest {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent); ourLog.info("Response was:\n{}", responseContent);
assertEquals("GET", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue()); assertEquals("GET,POST,PUT,DELETE,OPTIONS", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("null", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue()); assertEquals("null", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue());
} }
} }
@Test
public void testRequestWithInvalidOrigin() throws ClientProtocolException, IOException {
{
HttpOptions httpOpt = new HttpOptions(ourBaseUri + "/Organization/b27ed191-f62d-4128-d99d-40b5e84f2bf2");
httpOpt.addHeader("Access-Control-Request-Method", "GET");
httpOpt.addHeader("Origin", "http://yahoo.com");
httpOpt.addHeader("Access-Control-Request-Headers", "accept, x-fhir-starter, content-type");
HttpResponse status = ourClient.execute(httpOpt);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(403, status.getStatusLine().getStatusCode());
}
}
@Test @Test
public void testContextWithSpace() throws Exception { public void testContextWithSpace() throws Exception {
{ {
@ -71,7 +89,7 @@ public class CorsTest {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent); ourLog.info("Response was:\n{}", responseContent);
assertEquals("POST", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue()); assertEquals("GET,POST,PUT,DELETE,OPTIONS", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("http://www.fhir-starter.com", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue()); assertEquals("http://www.fhir-starter.com", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue());
} }
{ {
@ -113,6 +131,13 @@ public class CorsTest {
ourClient.close(); ourClient.close();
} }
@Test
public void testCorsConfigMethods() {
CorsInterceptor corsInterceptor = new CorsInterceptor();
assertNotNull(corsInterceptor.getConfig());
corsInterceptor.setConfig(new CorsConfiguration());
}
@BeforeClass @BeforeClass
public static void beforeClass() throws Exception { public static void beforeClass() throws Exception {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
@ -129,18 +154,26 @@ public class CorsTest {
// ServletHandler proxyHandler = new ServletHandler(); // ServletHandler proxyHandler = new ServletHandler();
ServletHolder servletHolder = new ServletHolder(restServer); ServletHolder servletHolder = new ServletHolder(restServer);
FilterHolder fh = new FilterHolder(); CorsConfiguration config = new CorsConfiguration();
fh.setHeldClass(CorsFilter.class); CorsInterceptor interceptor = new CorsInterceptor(config);
fh.setInitParameter("cors.logging.enabled", "true"); config.addAllowedHeader("x-fhir-starter");
fh.setInitParameter("cors.allowed.origins", "*"); config.addAllowedHeader("Origin");
fh.setInitParameter("cors.allowed.headers", "x-fhir-starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers"); config.addAllowedHeader("Accept");
fh.setInitParameter("cors.exposed.headers", "Location,Content-Location"); config.addAllowedHeader("X-Requested-With");
fh.setInitParameter("cors.allowed.methods", "GET,POST,PUT,DELETE,OPTIONS"); config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("http://www.fhir-starter.com");
config.addAllowedOrigin("null");
config.addAllowedOrigin("file://");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
restServer.registerInterceptor(interceptor);
ServletContextHandler ch = new ServletContextHandler(); ServletContextHandler ch = new ServletContextHandler();
ch.setContextPath("/rootctx/rcp2"); ch.setContextPath("/rootctx/rcp2");
ch.addServlet(servletHolder, "/fhirctx/fcp2/*"); ch.addServlet(servletHolder, "/fhirctx/fcp2/*");
ch.addFilter(fh, "/*", EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
ContextHandlerCollection contexts = new ContextHandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection();
ourServer.setHandler(contexts); ourServer.setHandler(contexts);

View File

@ -81,11 +81,6 @@
<optional>true</optional> <optional>true</optional>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>
@ -142,6 +137,11 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -16,11 +16,7 @@ import static org.mockito.Mockito.when;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -33,7 +29,6 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.ebaysf.web.cors.CORSFilter;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
@ -42,6 +37,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.web.cors.CorsConfiguration;
import com.phloc.commons.collections.iterate.ArrayEnumeration; import com.phloc.commons.collections.iterate.ArrayEnumeration;
@ -62,11 +58,7 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.BundleInclusionRule; import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
@ -708,14 +700,30 @@ public class ResponseHighlightingInterceptorTest {
ServletHandler proxyHandler = new ServletHandler(); ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx); ourServlet = new RestfulServer(ourCtx);
/*
* Enable CORS
*/
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
ourServlet.registerInterceptor(corsInterceptor);
ourServlet.registerInterceptor(new ResponseHighlighterInterceptor()); ourServlet.registerInterceptor(new ResponseHighlighterInterceptor());
ourServlet.setResourceProviders(patientProvider, new DummyBinaryResourceProvider()); ourServlet.setResourceProviders(patientProvider, new DummyBinaryResourceProvider());
ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE); ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(ourServlet); ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*"); proxyHandler.addServletWithMapping(servletHolder, "/*");
proxyHandler.addFilterWithMapping(CORSFilter.class, "/*", 1);
ourServer.setHandler(proxyHandler); ourServer.setHandler(proxyHandler);
ourServer.start(); ourServer.start();

View File

@ -95,11 +95,6 @@
<optional>true</optional> <optional>true</optional>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>
@ -153,6 +148,11 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<reporting> <reporting>

View File

@ -94,11 +94,6 @@
<optional>true</optional> <optional>true</optional>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>
@ -155,6 +150,11 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<reporting> <reporting>

14
pom.xml
View File

@ -309,8 +309,6 @@
<phloc_commons_version>4.4.5</phloc_commons_version> <phloc_commons_version>4.4.5</phloc_commons_version>
<spring_version>4.3.1.RELEASE</spring_version> <spring_version>4.3.1.RELEASE</spring_version>
<thymeleaf-version>3.0.1.RELEASE</thymeleaf-version> <thymeleaf-version>3.0.1.RELEASE</thymeleaf-version>
<tomcat_version>8.0.39</tomcat_version>
<ebay_cors_filter_version>1.0.1</ebay_cors_filter_version>
<xmlunit_version>1.6</xmlunit_version> <xmlunit_version>1.6</xmlunit_version>
<!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. --> <!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. -->
@ -526,12 +524,6 @@
<artifactId>wagon-scm</artifactId> <artifactId>wagon-scm</artifactId>
<version>2.10</version> <version>2.10</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<!-- Use property for version because we should refer to this from docs -->
<version>${tomcat_version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.velocity</groupId> <groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId> <artifactId>velocity</artifactId>
@ -547,11 +539,6 @@
<artifactId>woodstox-core-asl</artifactId> <artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version> <version>4.4.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<version>${ebay_cors_filter_version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId> <artifactId>jetty-http</artifactId>
@ -1592,6 +1579,7 @@
<module>hapi-fhir-base-test-mindeps-server</module> <module>hapi-fhir-base-test-mindeps-server</module>
<module>hapi-tinder-plugin</module> <module>hapi-tinder-plugin</module>
<module>hapi-tinder-test</module> <module>hapi-tinder-test</module>
<module>hapi-fhir-narrativegenerator</module>
<module>hapi-fhir-structures-dstu</module> <module>hapi-fhir-structures-dstu</module>
<module>hapi-fhir-validation-resources-dstu2</module> <module>hapi-fhir-validation-resources-dstu2</module>
<module>hapi-fhir-structures-dstu2</module> <module>hapi-fhir-structures-dstu2</module>

View File

@ -30,11 +30,13 @@
to GitHub user @vijayt27 for reporting! to GitHub user @vijayt27 for reporting!
</action> </action>
<action type="add"> <action type="add">
All server examples as well as the CLI have been As the eBay CORS interceptor has gone dormant, we have introduced a new
switched from using eBay's CORS filter to using HAPI server interceptor which can be used to implement CORS support
the Apache Tomcat CORS filter instead. The former instead of using the previously recommended Servlet Filter. All server
has become unmaintained and has unfixed bugs so examples as well as the CLI have been switched to use this new interceptor.
it is no longer recommended for use. See the
<![CDATA[<a href="./doc_cors.html">CORS Documentation</a>]]>
for more information.
</action> </action>
<action type="fix" issue="480"> <action type="fix" issue="480">
Make the parser configurable so that when Make the parser configurable so that when

View File

@ -10,13 +10,7 @@
<section name="CORS"> <section name="CORS">
<p> <p class="doc_info_bubble">
If you are intending to support JavaScript clients in your server application,
you will need to enable Cross Origin Resource Sharing (CORS). There are
a number of ways of supporting this, but the easiest is to use a servlet filter.
</p>
<p>
Note that in previous revisions of this document we recommended using the Note that in previous revisions of this document we recommended using the
<a href="https://github.com/ebay/cors-filter">eBay CORS Filter</a>, but <a href="https://github.com/ebay/cors-filter">eBay CORS Filter</a>, but
as of 2016 the eBay filter is no longer being maintained and contains known bugs. as of 2016 the eBay filter is no longer being maintained and contains known bugs.
@ -24,34 +18,79 @@
</p> </p>
<p> <p>
The following examples show how to use the Apache Tomcat CorsFilter to enable If you are intending to support JavaScript clients in your server application,
CORS support. The instructions below should work even on platforms other than you will generally need to enable Cross Origin Resource Sharing (CORS). There are
Tomcat (in other words, you can deploy the Tomcat CorsFilter to Jetty or JBoss if you like) a number of ways of supporting this, so two are shown here:
but if you run into conflicts it may be worth investigating if there is a dedicated
CORS filter for the platform you are using.
</p> </p>
<ul>
<li>An approach using a HAPI FHIR Server Interceptor (Requires SpringFramework)</li>
<li>An approach using a servlet Filter (Container Specific)</li>
</ul>
<subsection name="HAPI FHIR Server Interceptor">
<p>
The HAPI FHIR server framework includes an interceptor that can be
used to provide CORS functionality on your server. This mechanism is
nice because it relies purely on Java configuration (no messing around with
web.xml files). HAPI's interceptor is a thin wrapper around Spring Framework's
CorsProcessor class, so it requires Spring to be present on your classpath.
</p>
<p>
Spring is generally unlikely to conflict with other libraries so it is usually
safe to add it to your classpath, but it is a fairly large library so if size is
a concern you might opt to use a filter instead.
</p>
<p>
The following steps outline how to enable HAPI's CorsInterceptor:
</p>
<p>
Add the following dependency to your POM. Note the exclusion of
commons-logging, as we are using SLF4j without commons-logging in
most of our examples. If your application uses commons-logging you don't need
to exclude that dependency.
</p>
<source><![CDATA[<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring_version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>]]></source>
<p>
In your server's initialization method, create and register
a CorsInterceptor:
</p>
<macro name="snippet">
<param name="id" value="corsInterceptor" />
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
</macro>
</subsection>
<subsection name="Add the Dependency"> <subsection name="Add the Dependency">
<p> <p>
If you are deploying to a platform other than Tomcat, add the The following examples show how to use the Apache Tomcat CorsFilter to enable
following dependency to your Maven POM. If you are deploying CORS support. The filter being used
to Tomcat, the required classes are present on the classpath (<code>org.apache.catalina.filters.CorsFilter</code>) is bundled with Apache
so youdo not need to do this step. Tomcat so if you are deploying to that server you can use the filter.
</p> </p>
<p> <p>
Add the following dependency to your POM: Other containers have similar filters you can use, so consult the documentation
for the given container you are using for more information. (If you have
an example for how to configure a different CORS filter, please send it
our way! Examples are always useful!)
</p> </p>
<source><![CDATA[<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat_version}</version>
</dependency>]]></source>
</subsection>
<subsection name="Add the filter to your web.xml">
<p> <p>
In your web.xml file (within the WEB-INF directory in your WAR file), In your web.xml file (within the WEB-INF directory in your WAR file),

View File

@ -290,6 +290,17 @@
</subsection> </subsection>
<subsection name="CORS (Cross-Origin Resource Sharing)">
<p>
HAPI FHIR includes an interceptor which can be used to
implement CORS support on your server. See HAPI's
<a href="./doc_cors.html">CORS Documentation</a> for information
on how to use this interceptor.
</p>
</subsection>
<subsection name="Rejecting Unsupported HTTP Verbs"> <subsection name="Rejecting Unsupported HTTP Verbs">
<p> <p>