Merge remote-tracking branch 'origin/2.0.x'
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user