Merge remote-tracking branch 'origin/2.0.x'

This commit is contained in:
Ryan Baxter
2018-11-19 16:16:23 -05:00
12 changed files with 246 additions and 39 deletions

View File

@@ -1661,6 +1661,26 @@ public class ZuulConfig {
CAUTION: Use this filter carefully. The filter acts on the `Location` header of ALL `3XX` response codes, which may not be appropriate in all scenarios, such as when redirecting the user to an external URL.
=== Enabling Cross Origin Requests
By default Zuul routes all Cross Origin requests (CORS) to the services. If you want instead Zuul to handle these requests it can be done by providing custom `WebMvcConfigurer` bean:
[source,java]
----
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/path-1/**")
.allowedOrigins("http://allowed-origin.com")
.allowedMethods("GET", "POST");
}
};
}
----
In the example above, we allow `GET` and `POST` methods from `http://allowed-origin.com` to send cross-origin requests to the endpoints starting with `path-1`.
You can apply CORS configuration to a specific path pattern or globally for the whole application, using `/**` mapping.
You can customize properties: `allowedOrigins`,`allowedMethods`,`allowedHeaders`,`exposedHeaders`,`allowCredentials` and `maxAge` via this configuration.
=== Metrics
Zuul will provide metrics under the Actuator metrics endpoint for any failures that might occur when routing requests.
@@ -2021,6 +2041,8 @@ info:
url: https://github.com/spring-cloud-samples
----
To enable the health check request to accept all certificates when using HTTPs set `sidecar.accept-all-ssl-certificates` to `true.
[[retrying-failed-requests]]
== Retrying Failed Requests
@@ -2033,15 +2055,15 @@ When Spring Retry is present, load-balanced `RestTemplates`, Feign, and Zuul aut
=== BackOff Policies
By default, no backoff policy is used when retrying requests.
If you would like to configure a backoff policy, you need to create a bean of type `LoadBalancedBackOffPolicyFactory`, which is used to create a `BackOffPolicy` for a given service, as shown in the following example:
If you would like to configure a backoff policy, you need to create a bean of type `LoadBalancedRetryFactory` and override the `createBackOffPolicy` method for a given service, as shown in the following example:
[source,java,indent=0]
----
@Configuration
public class MyConfiguration {
@Bean
LoadBalancedBackOffPolicyFactory backOffPolicyFactory() {
return new LoadBalancedBackOffPolicyFactory() {
LoadBalancedRetryFactory retryFactory() {
return new LoadBalancedRetryFactory() {
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return new ExponentialBackOffPolicy();

View File

@@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
@@ -53,6 +54,7 @@ import org.springframework.cloud.netflix.eureka.metadata.ManagementMetadataProvi
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaAutoServiceRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry;
import org.springframework.cloud.util.ProxyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
@@ -285,10 +287,22 @@ public class EurekaClientAutoConfiguration {
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance) {
manager.getInfo(); // force initialization
return new CloudEurekaClient(manager, config, this.optionalArgs,
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance,
@Autowired(required = false) HealthCheckHandler healthCheckHandler) {
//If we use the proxy of the ApplicationInfoManager we could run into a problem
//when shutdown is called on the CloudEurekaClient where the ApplicationInfoManager bean is
//requested but wont be allowed because we are shutting down. To avoid this we use the
//object directly.
ApplicationInfoManager appManager;
if(AopUtils.isAopProxy(manager)) {
appManager = ProxyUtils.getTargetObject(manager);
} else {
appManager = manager;
}
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, this.optionalArgs,
this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
@Bean

View File

@@ -39,7 +39,7 @@ public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration
maybeInitializeClient(reg);
if (log.isInfoEnabled()) {
log.info("Registering application " + reg.getInstanceConfig().getAppname()
log.info("Registering application " + reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status "
+ reg.getInstanceConfig().getInitialStatus());
}
@@ -62,7 +62,7 @@ public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration
if (reg.getApplicationInfoManager().getInfo() != null) {
if (log.isInfoEnabled()) {
log.info("Unregistering application " + reg.getInstanceConfig().getAppname()
log.info("Unregistering application " + reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status DOWN");
}
@@ -91,8 +91,8 @@ public class EurekaServiceRegistry implements ServiceRegistry<EurekaRegistration
@Override
public Object getStatus(EurekaRegistration registration) {
String appname = registration.getInstanceConfig().getAppname();
String instanceId = registration.getInstanceConfig().getInstanceId();
String appname = registration.getApplicationInfoManager().getInfo().getAppName();
String instanceId = registration.getApplicationInfoManager().getInfo().getId();
InstanceInfo info = registration.getEurekaClient().getInstanceInfo(appname, instanceId);
HashMap<String, Object> status = new HashMap<>();

View File

@@ -31,6 +31,7 @@ import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import static com.netflix.appinfo.InstanceInfo.InstanceStatus.DOWN;
import static com.netflix.appinfo.InstanceInfo.InstanceStatus.OUT_OF_SERVICE;
import static com.netflix.appinfo.InstanceInfo.InstanceStatus.UNKNOWN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -70,20 +71,29 @@ public class EurekaServiceRegistryTests {
config.setAppname("myapp");
config.setInstanceId("1234");
CloudEurekaClient eurekaClient = mock(CloudEurekaClient.class);
InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()
InstanceInfo local = InstanceInfo.Builder.newBuilder()
.setAppName("myapp")
.setInstanceId("1234")
.setStatus(DOWN)
.setOverriddenStatus(UNKNOWN)
.build();
when(eurekaClient.getInstanceInfo("myapp", "1234"))
.thenReturn(instanceInfo);
InstanceInfo remote = InstanceInfo.Builder.newBuilder()
.setAppName("myapp")
.setInstanceId("1234")
.setStatus(DOWN)
.setOverriddenStatus(OUT_OF_SERVICE)
.build();
CloudEurekaClient eurekaClient = mock(CloudEurekaClient.class);
when(eurekaClient.getInstanceInfo(local.getAppName(), local.getId()))
.thenReturn(remote);
ApplicationInfoManager applicationInfoManager = mock(ApplicationInfoManager.class);
when(applicationInfoManager.getInfo()).thenReturn(local);
EurekaRegistration registration = EurekaRegistration.builder(config)
.with(eurekaClient)
.with(mock(ApplicationInfoManager.class))
.with(applicationInfoManager)
.with(new EurekaClientConfigBean(), mock(ApplicationEventPublisher.class))
.build();
@@ -95,7 +105,7 @@ public class EurekaServiceRegistryTests {
assertThat(map).hasSize(2)
.containsEntry("status", DOWN.toString())
.containsEntry("overriddenStatus", UNKNOWN.toString());
.containsEntry("overriddenStatus", OUT_OF_SERVICE.toString());
}
@@ -112,9 +122,12 @@ public class EurekaServiceRegistryTests {
when(eurekaClient.getInstanceInfo("myapp", "1234"))
.thenReturn(null);
ApplicationInfoManager applicationInfoManager = mock(ApplicationInfoManager.class);
when(applicationInfoManager.getInfo()).thenReturn(mock(InstanceInfo.class));
EurekaRegistration registration = EurekaRegistration.builder(config)
.with(eurekaClient)
.with(mock(ApplicationInfoManager.class))
.with(applicationInfoManager)
.with(new EurekaClientConfigBean(), mock(ApplicationEventPublisher.class))
.build();

View File

@@ -140,6 +140,12 @@ public class RibbonCommandContext {
if (requestEntity == null) {
return null;
}
//If the route is not retryable there is no point in copying the RequestEntity. This
//has memory implications in all cases but especially when uploading large files through
//Zuul
if(!retryable) {
return requestEntity;
}
try {
if (!(requestEntity instanceof ResettableServletInputStreamWrapper)) {

View File

@@ -16,22 +16,26 @@
package org.springframework.cloud.netflix.sidecar;
import java.net.URI;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.util.Map;
/**
* @author Spencer Gibb
* @author Fabrizio Di Napoli
*/
public class LocalApplicationHealthIndicator extends AbstractHealthIndicator {
@Autowired
private SidecarProperties properties;
@Autowired
private RestTemplate restTemplate;
@SuppressWarnings("unchecked")
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
@@ -40,12 +44,13 @@ public class LocalApplicationHealthIndicator extends AbstractHealthIndicator {
builder.up();
return;
}
Map<String, Object> map = new RestTemplate().getForObject(uri, Map.class);
Map<String, Object> map = restTemplate.getForObject(uri, Map.class);
Object status = map.get("status");
if (status != null && status instanceof String) {
if (status instanceof String) {
builder.status(status.toString());
}
else if (status != null && status instanceof Map) {
else if (status instanceof Map) {
Map<String, Object> statusMap = (Map<String, Object>) status;
Object code = statusMap.get("code");
if (code != null) {
@@ -63,5 +68,4 @@ public class LocalApplicationHealthIndicator extends AbstractHealthIndicator {
private Health.Builder getWarning(Health.Builder builder) {
return builder.unknown().withDetail("warning", "no status field in response");
}
}

View File

@@ -16,6 +16,11 @@
package org.springframework.cloud.netflix.sidecar;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import static org.springframework.cloud.commons.util.IdUtils.getDefaultInstanceId;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
@@ -33,10 +39,12 @@ import org.springframework.cloud.netflix.eureka.metadata.ManagementMetadataProvi
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.discovery.EurekaClientConfig;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@@ -54,6 +62,7 @@ import java.util.Map;
*
* @author Spencer Gibb
* @author Ryan Baxter
* @author Fabrizio Di Napoli
*
* @see EurekaInstanceConfigBeanConfiguration
*/
@@ -153,7 +162,27 @@ public class SidecarConfiguration {
final LocalApplicationHealthIndicator healthIndicator) {
return new LocalApplicationHealthCheckHandler(healthIndicator);
}
}
@Bean
@ConditionalOnMissingClass("org.apache.http.client.HttpClient")
public RestTemplate restTemplate() {
return new RestTemplateBuilder().build();
}
@Bean
@ConditionalOnClass(HttpClient.class)
public RestTemplate sslRestTemplate(SidecarProperties properties) {
RestTemplateBuilder builder = new RestTemplateBuilder();
if(properties.acceptAllSslCertificates()) {
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
builder = builder.requestFactory(() -> requestFactory);
}
return builder.build();
}
@Bean

View File

@@ -27,6 +27,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Spencer Gibb
* @author Gregor Zurowski
* @author Fabrizio Di Napoli
*/
@ConfigurationProperties("sidecar")
public class SidecarProperties {
@@ -43,6 +44,8 @@ public class SidecarProperties {
private String ipAddress;
private boolean acceptAllSslCertificates;
public URI getHealthUri() {
return healthUri;
}
@@ -83,6 +86,14 @@ public class SidecarProperties {
this.ipAddress = ipAddress;
}
public boolean acceptAllSslCertificates() {
return acceptAllSslCertificates;
}
public void setAcceptAllSslCertificates(boolean acceptAllSslCertificates) {
this.acceptAllSslCertificates = acceptAllSslCertificates;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -92,12 +103,13 @@ public class SidecarProperties {
Objects.equals(homePageUri, that.homePageUri) &&
port == that.port &&
Objects.equals(hostname, that.hostname) &&
Objects.equals(ipAddress, that.ipAddress);
Objects.equals(ipAddress, that.ipAddress) &&
Objects.equals(acceptAllSslCertificates, that.acceptAllSslCertificates);
}
@Override
public int hashCode() {
return Objects.hash(healthUri, homePageUri, port, hostname, ipAddress);
return Objects.hash(healthUri, homePageUri, port, hostname, ipAddress, acceptAllSslCertificates);
}
@Override
@@ -107,7 +119,8 @@ public class SidecarProperties {
.append("homePageUri=").append(homePageUri).append(", ")
.append("port=").append(port).append(", ")
.append("hostname='").append(hostname).append("', ")
.append("ipAddress='").append(ipAddress).append("'}")
.append("ipAddress='").append(ipAddress).append("', ")
.append("acceptAllSslCertificates='").append(acceptAllSslCertificates).append("'}")
.toString();
}

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.netflix.sidecar;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,6 +27,7 @@ import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import org.springframework.web.client.RestTemplate;
public class SidecarApplicationTests {
@@ -128,4 +130,16 @@ public class SidecarApplicationTests {
assertThat(this.config.getHealthCheckUrl(), equalTo("http://mhhost2:0/foo/health"));
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SidecarApplication.class, webEnvironment = RANDOM_PORT, value = {"sidecar.accept-all-ssl-certificates=false"})
public static class AcceptAllSslCertificatesContext {
@Autowired
RestTemplate restTemplate;
@Test
public void testUseRestTemplateWhenHttpClientIsNotAvailable() {
assertNull(restTemplate.getRequestFactory());
}
}
}

View File

@@ -7,6 +7,7 @@ spring:
sidecar:
port: 8000
health-uri: http://localhost:8000/src/test/resources/health.json
accept-all-ssl-certificates: true
eureka:
instance:

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.netflix.zuul;
import java.util.Collection;
import java.util.Map;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -59,6 +60,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.netflix.zuul.FilterLoader;
import com.netflix.zuul.ZuulFilter;
@@ -69,6 +73,8 @@ import com.netflix.zuul.monitoring.TracerFactory;
import io.micrometer.core.instrument.MeterRegistry;
import static java.util.Collections.emptyList;
/**
* @author Spencer Gibb
* @author Dave Syer
@@ -91,6 +97,11 @@ public class ZuulServerAutoConfiguration {
@Autowired(required = false)
private ErrorController errorController;
private Map<String, CorsConfiguration> corsConfigurations;
@Autowired(required = false)
private List<WebMvcConfigurer> configurers = emptyList();
@Bean
public HasFeatures zuulFeature() {
return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
@@ -119,9 +130,20 @@ public class ZuulServerAutoConfiguration {
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
if (this.corsConfigurations == null) {
ZuulCorsRegistry registry = new ZuulCorsRegistry();
this.configurers
.forEach(configurer -> configurer.addCorsMappings(registry));
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
@@ -267,4 +289,12 @@ public class ZuulServerAutoConfiguration {
this.zuulHandlerMapping.setDirty(true);
}
}
private static class ZuulCorsRegistry extends CorsRegistry {
@Override
protected Map<String, CorsConfiguration> getCorsConfigurations() {
return super.getCorsConfigurations();
}
}
}

View File

@@ -20,12 +20,11 @@ package org.springframework.cloud.netflix.zuul;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.zuul.context.RequestContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@@ -38,27 +37,41 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ZuulProxyApplicationTests.ZuulProxyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"zuul.routes.simplezpat:/simplezpat/**", "logging.level.org.apache.http: DEBUG" })
@SpringBootTest(
classes = ZuulProxyApplicationTests.ZuulProxyApplication.class,
webEnvironment = WebEnvironment.RANDOM_PORT,
properties = {
"zuul.routes.simplezpat:/simplezpat/**",
"logging.level.org.apache.http: DEBUG"
})
@DirtiesContext
public class ZuulProxyApplicationTests {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate testRestTemplate;
@Before
public void setTestRequestcontext() {
RequestContext context = new RequestContext();
@@ -72,22 +85,58 @@ public class ZuulProxyApplicationTests {
@Test
public void getHasCorrectTransferEncoding() {
ResponseEntity<String> result = new TestRestTemplate().getForEntity(
"http://localhost:" + this.port + "/simplezpat/transferencoding",
String.class);
ResponseEntity<String> result = testRestTemplate.getForEntity(url(), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("missing", result.getBody());
}
@Test
public void postHasCorrectTransferEncoding() {
ResponseEntity<String> result = new TestRestTemplate().postForEntity(
"http://localhost:" + this.port + "/simplezpat/transferencoding",
new HttpEntity<>("hello"), String.class);
ResponseEntity<String> result = testRestTemplate.postForEntity(url(), new HttpEntity<>("hello"), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("missing", result.getBody());
}
@Test
public void preflightRequestSucceedsForGetRequest() {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.put("Origin", singletonList("http://hello.com"));
headers.put("Access-Control-Request-Method", singletonList("GET"));
ResponseEntity<Void> result = testRestTemplate.exchange(url(), HttpMethod.OPTIONS,
new HttpEntity<>(headers), Void.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}
@Test
public void preflightRequestIsForbiddenForUnsupportedMethod() {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.put("Origin", singletonList("http://hello.com"));
headers.put("Access-Control-Request-Method", singletonList("PUT"));
ResponseEntity<Void> result = testRestTemplate.exchange(url(), HttpMethod.OPTIONS,
new HttpEntity<>(headers), Void.class);
assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
}
@Test
public void preflightRequestIsForbiddenForUnsupportedorigin() {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.put("Origin", singletonList("http://unknown-origin.com"));
headers.put("Access-Control-Request-Method", singletonList("GET"));
ResponseEntity<Void> result = testRestTemplate.exchange(url(), HttpMethod.OPTIONS,
new HttpEntity<>(headers), Void.class);
assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
}
private String url() {
return "http://localhost:" + this.port + "/simplezpat/transferencoding";
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@@ -116,6 +165,18 @@ public class ZuulProxyApplicationTests {
return transferEncoding;
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/simplezpat/**")
.allowedOrigins("http://hello.com")
.allowedMethods("GET", "POST")
.allowedHeaders("Authorization");
}
};
}
}
// Load balancer with fixed server list for "simplezpat" pointing to localhost