Merge remote-tracking branch 'Upstream/master' into http-client-centralization

This commit is contained in:
Ryan Baxter
2017-07-11 16:48:19 -04:00
22 changed files with 980 additions and 20 deletions

View File

@@ -1044,8 +1044,46 @@ public class FooConfiguration {
This replaces the `SpringMvcContract` with `feign.Contract.Default` and adds a `RequestInterceptor` to the collection of `RequestInterceptor`.
`@FeignClient` also can be configured using configuration properties.
application.yml
[source,yaml]
----
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
----
Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients.
If you prefer using configuration properties to configured all `@FeignClient`, you can create configuration properties with `default` feign name.
application.yml
[source,yaml]
----
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
----
If we create both `@Configuration` bean and configuration properties, configuration properties will win.
It will override `@Configuration` values. But if you want to change the priority to `@Configuration`,
you can change `feign.client.default-to-properties` to `false`.
NOTE: If you need to use `ThreadLocal` bound variables in your `RequestInterceptor`s you will need to either set the
thread isolation strategy for Hystrix to `SEMAPHORE` or disable Hystrix in Feign.
@@ -1868,6 +1906,32 @@ class MyFallbackProvider implements ZuulFallbackProvider {
}
----
[[zuul-redirect-location-rewrite]]
=== Rewriting `Location` header
If Zuul is fronting a web application then there may be a need to re-write the `Location` header when the web application redirects through a http status code of 3XX, otherwise the browser will end up redirecting to the web application's url instead of the Zuul url.
A `LocationRewriteFilter` Zuul filter can be configured to re-write the Location header to the Zuul's url, it also adds back the stripped global and route specific prefixes. The filter can be added the following way via a Spring Configuration file:
[source,java]
----
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
...
@Configuration
@EnableZuulProxy
public class ZuulConfig {
@Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
----
[WARNING]
====
Use this filter with caution though, the filter acts on the `Location` header of ALL 3XX response codes which may not be appropriate in all scenarios, say if the user is redirecting to an external URL.
====
[[zuul-developer-guide]]
=== Zuul Developer Guide

View File

@@ -56,7 +56,7 @@ import javax.annotation.PreDestroy;
*/
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,9 +18,11 @@ package org.springframework.cloud.netflix.feign;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -44,6 +46,7 @@ import lombok.EqualsAndHashCode;
/**
* @author Spencer Gibb
* @author Venil Noronha
* @author Eko Kurniawan Khannedy
*/
@Data
@EqualsAndHashCode(callSuper = false)
@@ -74,7 +77,6 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
Assert.hasText(this.name, "Name must be set");
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
@@ -93,7 +95,29 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
.contract(get(context, Contract.class));
// @formatter:on
// optional values
configureFeign(context, builder);
return builder;
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
if (properties != null) {
if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder);
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
@@ -119,8 +143,52 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
if (decode404) {
builder.decode404();
}
}
return builder;
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
if (config == null) {
return;
}
if (config.getLoggerLevel() != null) {
builder.logLevel(config.getLoggerLevel());
}
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
}
if (config.getRetryer() != null) {
Retryer retryer = getOrInstantiate(config.getRetryer());
builder.retryer(retryer);
}
if (config.getErrorDecoder() != null) {
ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
builder.errorDecoder(errorDecoder);
}
if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
// this will add request interceptor to builder, not replace existing
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
}
private <T> T getOrInstantiate(Class<T> tClass) {
try {
return applicationContext.getBean(tClass);
} catch (NoSuchBeanDefinitionException e) {
return BeanUtils.instantiateClass(tClass);
}
}
protected <T> T get(FeignContext context, Class<T> type) {

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2013-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.feign;
import feign.Logger;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author Eko Kurniawan Khannedy
*/
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
private String defaultConfig = "default";
private Map<String, FeignClientConfiguration> config = new HashMap<>();
public boolean isDefaultToProperties() {
return defaultToProperties;
}
public void setDefaultToProperties(boolean defaultToProperties) {
this.defaultToProperties = defaultToProperties;
}
public String getDefaultConfig() {
return defaultConfig;
}
public void setDefaultConfig(String defaultConfig) {
this.defaultConfig = defaultConfig;
}
public Map<String, FeignClientConfiguration> getConfig() {
return config;
}
public void setConfig(Map<String, FeignClientConfiguration> config) {
this.config = config;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FeignClientProperties that = (FeignClientProperties) o;
return defaultToProperties == that.defaultToProperties &&
Objects.equals(defaultConfig, that.defaultConfig) &&
Objects.equals(config, that.config);
}
@Override
public int hashCode() {
return Objects.hash(defaultToProperties, defaultConfig, config);
}
public static class FeignClientConfiguration {
private Logger.Level loggerLevel;
private Integer connectTimeout;
private Integer readTimeout;
private Class<Retryer> retryer;
private Class<ErrorDecoder> errorDecoder;
private List<Class<RequestInterceptor>> requestInterceptors;
private Boolean decode404;
public Logger.Level getLoggerLevel() {
return loggerLevel;
}
public void setLoggerLevel(Logger.Level loggerLevel) {
this.loggerLevel = loggerLevel;
}
public Integer getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Integer getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(Integer readTimeout) {
this.readTimeout = readTimeout;
}
public Class<Retryer> getRetryer() {
return retryer;
}
public void setRetryer(Class<Retryer> retryer) {
this.retryer = retryer;
}
public Class<ErrorDecoder> getErrorDecoder() {
return errorDecoder;
}
public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {
this.errorDecoder = errorDecoder;
}
public List<Class<RequestInterceptor>> getRequestInterceptors() {
return requestInterceptors;
}
public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
this.requestInterceptors = requestInterceptors;
}
public Boolean getDecode404() {
return decode404;
}
public void setDecode404(Boolean decode404) {
this.decode404 = decode404;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FeignClientConfiguration that = (FeignClientConfiguration) o;
return loggerLevel == that.loggerLevel &&
Objects.equals(connectTimeout, that.connectTimeout) &&
Objects.equals(readTimeout, that.readTimeout) &&
Objects.equals(retryer, that.retryer) &&
Objects.equals(errorDecoder, that.errorDecoder) &&
Objects.equals(requestInterceptors, that.requestInterceptors) &&
Objects.equals(decode404, that.decode404);
}
@Override
public int hashCode() {
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer,
errorDecoder, requestInterceptors, decode404);
}
}
}

View File

@@ -42,6 +42,12 @@ public class Route {
}
}
}
public Route(String id, String path, String location, String prefix,
Boolean retryable, Set<String> ignoredHeaders, boolean prefixStripped) {
this(id, path, location, prefix, retryable, ignoredHeaders);
this.prefixStripped = prefixStripped;
}
private String id;
@@ -58,6 +64,8 @@ public class Route {
private Set<String> sensitiveHeaders = new LinkedHashSet<>();
private boolean customSensitiveHeaders;
private boolean prefixStripped = true;
public boolean isCustomSensitiveHeaders() {
return this.customSensitiveHeaders;

View File

@@ -151,7 +151,8 @@ public class SimpleRouteLocator implements RouteLocator, Ordered {
}
return new Route(route.getId(), targetPath, route.getLocation(), prefix,
retryable,
route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null);
route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null,
route.isStripPrefix());
}
/**

View File

@@ -304,7 +304,8 @@ public class ZuulProperties {
public Route getRoute(String prefix) {
return new Route(this.id, this.path, getLocation(), prefix, this.retryable,
isCustomSensitiveHeaders() ? this.sensitiveHeaders : null);
isCustomSensitiveHeaders() ? this.sensitiveHeaders : null,
this.stripPrefix);
}
public void setSensitiveHeaders(Set<String> headers) {

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.netflix.zuul.filters.post;
import com.netflix.util.Pair;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UrlPathHelper;
import java.net.URI;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER;
/**
* {@link ZuulFilter} Responsible for rewriting the Location header to be the Zuul URL
*
* @author Biju Kunjummen
*/
public class LocationRewriteFilter extends ZuulFilter {
private final UrlPathHelper urlPathHelper = new UrlPathHelper();
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private RouteLocator routeLocator;
private static final String LOCATION_HEADER = "Location";
public LocationRewriteFilter() {
}
public LocationRewriteFilter(ZuulProperties zuulProperties,
RouteLocator routeLocator) {
this.routeLocator = routeLocator;
this.zuulProperties = zuulProperties;
}
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 100;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
int statusCode = ctx.getResponseStatusCode();
return HttpStatus.valueOf(statusCode).is3xxRedirection();
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Route route = routeLocator.getMatchingRoute(
urlPathHelper.getPathWithinApplication(ctx.getRequest()));
if (route != null) {
Pair<String, String> lh = locationHeader(ctx);
if (lh != null) {
String location = lh.second();
URI originalRequestUri = UriComponentsBuilder
.fromHttpRequest(new ServletServerHttpRequest(ctx.getRequest()))
.build().toUri();
UriComponentsBuilder redirectedUriBuilder = UriComponentsBuilder
.fromUriString(location);
UriComponents redirectedUriComps = redirectedUriBuilder.build();
String newPath = getRestoredPath(this.zuulProperties, route,
redirectedUriComps);
String modifiedLocation = redirectedUriBuilder
.scheme(originalRequestUri.getScheme())
.host(originalRequestUri.getHost())
.port(originalRequestUri.getPort()).replacePath(newPath).build()
.toUriString();
lh.setSecond(modifiedLocation);
}
}
return null;
}
private String getRestoredPath(ZuulProperties zuulProperties, Route route,
UriComponents redirectedUriComps) {
StringBuilder path = new StringBuilder();
String redirectedPathWithoutGlobal = downstreamHasGlobalPrefix(zuulProperties)
? redirectedUriComps.getPath()
.substring(("/" + zuulProperties.getPrefix()).length())
: redirectedUriComps.getPath();
if (downstreamHasGlobalPrefix(zuulProperties)) {
path.append("/" + zuulProperties.getPrefix());
}
else {
path.append(zuulHasGlobalPrefix(zuulProperties)
? "/" + zuulProperties.getPrefix() : "");
}
path.append(downstreamHasRoutePrefix(route) ? "" : "/" + route.getPrefix())
.append(redirectedPathWithoutGlobal);
return path.toString();
}
private boolean downstreamHasGlobalPrefix(ZuulProperties zuulProperties) {
return (!zuulProperties.isStripPrefix()
&& StringUtils.hasText(zuulProperties.getPrefix()));
}
private boolean zuulHasGlobalPrefix(ZuulProperties zuulProperties) {
return StringUtils.hasText(zuulProperties.getPrefix());
}
private boolean downstreamHasRoutePrefix(Route route) {
return (!route.isPrefixStripped() && StringUtils.hasText(route.getPrefix()));
}
private Pair<String, String> locationHeader(RequestContext ctx) {
if (ctx.getZuulResponseHeaders() != null) {
for (Pair<String, String> pair : ctx.getZuulResponseHeaders()) {
if (pair.first().equals(LOCATION_HEADER)) {
return pair;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.RetryableException;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import static org.junit.Assert.*;
/**
* @author Eko Kurniawan Khannedy
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@TestPropertySource("classpath:feign-properties.properties")
@DirtiesContext
public class FeignClientUsingPropertiesTests {
@Autowired
FeignContext context;
@Autowired
private ApplicationContext applicationContext;
@Value("${local.server.port}")
private int port = 0;
private FeignClientFactoryBean fooFactoryBean;
private FeignClientFactoryBean barFactoryBean;
public FeignClientUsingPropertiesTests() {
fooFactoryBean = new FeignClientFactoryBean();
fooFactoryBean.setName("foo");
fooFactoryBean.setType(FeignClientFactoryBean.class);
barFactoryBean = new FeignClientFactoryBean();
barFactoryBean.setName("bar");
barFactoryBean.setType(FeignClientFactoryBean.class);
}
public FooClient fooClient() {
fooFactoryBean.setApplicationContext(applicationContext);
return fooFactoryBean.feign(context).target(FooClient.class, "http://localhost:" + this.port);
}
public BarClient barClient() {
barFactoryBean.setApplicationContext(applicationContext);
return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + this.port);
}
@Test
public void testFoo() {
String response = fooClient().foo();
assertNotNull("OK", response);
}
@Test(expected = RetryableException.class)
public void testBar() {
barClient().bar();
fail("it should timeout");
}
protected interface FooClient {
@RequestMapping(method = RequestMethod.GET, value = "/foo")
String foo();
}
protected interface BarClient {
@RequestMapping(method = RequestMethod.GET, value = "/bar")
String bar();
}
@Configuration
@EnableAutoConfiguration
@RestController
protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/foo")
public String foo(HttpServletRequest request) throws IllegalAccessException {
if ("Foo".equals(request.getHeader("Foo")) &&
"Bar".equals(request.getHeader("Bar"))) {
return "OK";
} else {
throw new IllegalAccessException("It should has Foo and Bar header");
}
}
@RequestMapping(method = RequestMethod.GET, value = "/bar")
public String bar() throws InterruptedException {
Thread.sleep(2000L);
return "OK";
}
}
public static class FooRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Foo", "Foo");
}
}
public static class BarRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Bar", "Bar");
}
}
public static class NoRetryer implements Retryer {
@Override
public void continueOrPropagate(RetryableException e) {
throw e;
}
@Override
public Retryer clone() {
return this;
}
}
public static class DefaultErrorDecoder extends ErrorDecoder.Default {
}
}

View File

@@ -63,6 +63,7 @@ import org.springframework.web.multipart.MultipartFile;
import static java.nio.charset.Charset.defaultCharset;
import static org.junit.Assert.assertEquals;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.util.StreamUtils.copyToString;
import lombok.extern.slf4j.Slf4j;
@@ -271,7 +272,7 @@ class FormZuulProxyApplication {
@Override
public String filterType() {
return "pre";
return PRE_TYPE;
}
@Override

View File

@@ -61,6 +61,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import static org.junit.Assert.assertEquals;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import lombok.extern.slf4j.Slf4j;
@@ -181,7 +182,7 @@ class FormZuulServletProxyApplication {
@Override
public String filterType() {
return "pre";
return PRE_TYPE;
}
@Override

View File

@@ -37,6 +37,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RetryableZuulProxyApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
@@ -100,7 +101,7 @@ class RetryableZuulProxyApplication {
return new ZuulFilter() {
@Override
public String filterType() {
return "pre";
return PRE_TYPE;
}
@Override

View File

@@ -45,6 +45,7 @@ import org.springframework.web.bind.annotation.RestController;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SimpleZuulServerApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@@ -119,7 +120,7 @@ class SimpleZuulServerApplication {
return new ZuulFilter() {
@Override
public String filterType() {
return "pre";
return PRE_TYPE;
}
@Override

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.netflix.zuul.filters.post;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.zuul.context.RequestContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.embedded.LocalServerPort;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Biju Kunjummen
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
"zuul.routes.aservice.path:/service/**", "zuul.routes.aservice.strip-prefix:true",
"eureka.client.enabled:false" })
@DirtiesContext
public class LocationRewriteFilterIntegrationTests {
@LocalServerPort
private int port;
@Before
public void before() {
RequestContext context = new RequestContext();
RequestContext.testSetCurrentContext(context);
}
@Test
public void testWithRedirectPrefixStripped() {
String url = "http://localhost:" + port + "/service/redirectingUri";
ResponseEntity<String> response = new TestRestTemplate().getForEntity(url,
String.class);
List<String> locationHeaders = response.getHeaders().get("Location");
assertThat(locationHeaders).hasSize(1);
String locationHeader = locationHeaders.get(0);
assertThat(locationHeader).withFailMessage("Location should have prefix")
.isEqualTo(
String.format("http://localhost:%d/service/redirectedUri", port));
}
@SpringBootConfiguration
@EnableAutoConfiguration
@EnableZuulProxy
@Controller
@RibbonClient(name = "aservice", configuration = RibbonConfig.class)
protected static class Config {
@RequestMapping("/redirectingUri")
public String redirect1() {
return "redirect:/redirectedUri";
}
@Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
public static class RibbonConfig {
@LocalServerPort
private int port;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.netflix.zuul.filters.post;
import com.netflix.util.Pair;
import com.netflix.zuul.context.RequestContext;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Biju Kunjummen
*/
public class LocationRewriteFilterTests {
private final String ZUUL_HOST = "myzuul.com";
private final String ZUUL_SCHEME = "https";
private final int ZUUL_PORT = 8443;
private final String ZUUL_BASE_URL = String.format("%s://%s:%d", ZUUL_SCHEME,
ZUUL_HOST, ZUUL_PORT);
private final String SERVER_HOST = "someserver.com";
private final String SERVER_SCHEME = "http";
private final int SERVER_PORT = 8564;
private final String SERVER_BASE_URL = String.format("%s://%s:%d", SERVER_SCHEME,
SERVER_HOST, SERVER_PORT);
@Before
public void before() {
RequestContext context = new RequestContext();
RequestContext.testSetCurrentContext(context);
}
@Test
public void shouldRewriteLocationHeadersWithRoutePrefix() {
RequestContext context = RequestContext.getCurrentContext();
ZuulProperties zuulProperties = new ZuulProperties();
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
new Route("service1", "/redirectingUri", "service1", "prefix", false,
Collections.EMPTY_SET, true),
"/prefix/redirectingUri", "/redirectedUri;someparam?param1=abc");
filter.run();
assertThat(getLocationHeader(context).second()).isEqualTo(String
.format("%s/prefix/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL));
}
@Test
public void shouldBeUntouchedIfNoRoutesFound() {
RequestContext context = RequestContext.getCurrentContext();
ZuulProperties zuulProperties = new ZuulProperties();
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, null,
"/prefix/redirectingUri", "/redirectedUri;someparam?param1=abc");
filter.run();
assertThat(getLocationHeader(context).second()).isEqualTo(
String.format("%s/redirectedUri;someparam?param1=abc", SERVER_BASE_URL));
}
@Test
public void shouldRewriteLocationHeadersIfPrefixIsNotStripped() {
RequestContext context = RequestContext.getCurrentContext();
ZuulProperties zuulProperties = new ZuulProperties();
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
new Route("service1", "/something/redirectingUri", "service1", "prefix",
false, Collections.EMPTY_SET, false),
"/prefix/redirectingUri",
"/something/redirectedUri;someparam?param1=abc");
filter.run();
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
"%s/something/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL));
}
@Test
public void shouldRewriteLocationHeadersIfPrefixIsEmpty() {
RequestContext context = RequestContext.getCurrentContext();
ZuulProperties zuulProperties = new ZuulProperties();
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
new Route("service1", "/something/redirectingUri", "service1", "", false,
Collections.EMPTY_SET, true),
"/redirectingUri", "/something/redirectedUri;someparam?param1=abc");
filter.run();
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
"%s/something/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL));
}
@Test
public void shouldAddBackGlobalPrefixIfPresent() {
RequestContext context = RequestContext.getCurrentContext();
ZuulProperties zuulProperties = new ZuulProperties();
zuulProperties.setPrefix("global");
zuulProperties.setStripPrefix(true);
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
new Route("service1", "/something/redirectingUri", "service1", "prefix",
false, Collections.EMPTY_SET, true),
"/global/prefix/redirectingUri",
"/something/redirectedUri;someparam?param1=abc");
filter.run();
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
"%s/global/prefix/something/redirectedUri;someparam?param1=abc",
ZUUL_BASE_URL));
}
@Test
public void shouldNotAddBackGlobalPrefixIfNotStripped() {
RequestContext context = RequestContext.getCurrentContext();
ZuulProperties zuulProperties = new ZuulProperties();
zuulProperties.setPrefix("global");
zuulProperties.setStripPrefix(false);
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
new Route("service1", "/something/redirectingUri", "service1", "prefix",
false, Collections.EMPTY_SET, true),
"/global/prefix/redirectingUri",
"/global/something/redirectedUri;someparam?param1=abc");
filter.run();
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
"%s/global/prefix/something/redirectedUri;someparam?param1=abc",
ZUUL_BASE_URL));
}
private LocationRewriteFilter setFilterUpWith(RequestContext context,
ZuulProperties zuulProperties, Route route, String toZuulRequestUri,
String redirectedUri) {
MockHttpServletRequest httpServletRequest = new MockHttpServletRequest();
httpServletRequest.setRequestURI(toZuulRequestUri);
httpServletRequest.setServerName(ZUUL_HOST);
httpServletRequest.setScheme(ZUUL_SCHEME);
httpServletRequest.setServerPort(ZUUL_PORT);
context.setRequest(httpServletRequest);
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
context.getZuulResponseHeaders().add(new Pair<>("Location",
String.format("%s%s", SERVER_BASE_URL, redirectedUri)));
context.setResponse(httpServletResponse);
RouteLocator routeLocator = mock(RouteLocator.class);
when(routeLocator.getMatchingRoute(toZuulRequestUri)).thenReturn(route);
LocationRewriteFilter filter = new LocationRewriteFilter(zuulProperties,
routeLocator);
return filter;
}
private Pair<String, String> getLocationHeader(RequestContext ctx) {
if (ctx.getZuulResponseHeaders() != null) {
for (Pair<String, String> pair : ctx.getZuulResponseHeaders()) {
if (pair.first().equals("Location")) {
return pair;
}
}
}
return null;
}
}

View File

@@ -47,6 +47,9 @@ import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE;
/**
* @author Spencer Gibb
@@ -111,7 +114,7 @@ public class SendErrorFilterIntegrationTests {
return new FailureFilter() {
@Override
public String filterType() {
return "pre";
return PRE_TYPE;
}
};
}
@@ -121,7 +124,7 @@ public class SendErrorFilterIntegrationTests {
return new FailureFilter() {
@Override
public String filterType() {
return "route";
return ROUTE_TYPE;
}
};
}
@@ -131,7 +134,7 @@ public class SendErrorFilterIntegrationTests {
return new FailureFilter() {
@Override
public String filterType() {
return "post";
return POST_TYPE;
}
};
}

View File

@@ -42,6 +42,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORWARD_TO_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.REQUEST_URI_KEY;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVICE_ID_KEY;
@@ -85,7 +86,7 @@ public class PreDecorationFilterTests {
public void basicProperties() throws Exception {
assertEquals(5, this.filter.filterOrder());
assertEquals(true, this.filter.shouldFilter());
assertEquals("pre", this.filter.filterType());
assertEquals(PRE_TYPE, this.filter.filterType());
}
@Test

View File

@@ -16,7 +16,9 @@
package org.springframework.cloud.netflix.zuul.filters.route.support;
import com.netflix.client.AbstractLoadBalancerAwareClient;
import com.netflix.client.ClientRequest;
import com.netflix.client.http.HttpResponse;
import com.netflix.hystrix.HystrixCommandProperties;
import org.junit.Before;
import org.junit.Test;
@@ -91,7 +93,8 @@ public class RibbonCommandHystrixThreadPoolKeyTests {
assertThat(ribbonCommand2.getThreadPoolKey().name()).isEqualTo(ribbonCommand2.getCommandGroup().name());
}
public static class TestRibbonCommand extends AbstractRibbonCommand {
public static class TestRibbonCommand
extends AbstractRibbonCommand<AbstractLoadBalancerAwareClient<ClientRequest, HttpResponse>, ClientRequest, HttpResponse> {
public TestRibbonCommand(String commandKey, ZuulProperties zuulProperties) {
super(commandKey, null, null, zuulProperties);
}

View File

@@ -82,6 +82,7 @@ import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assume.assumeThat;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @author Spencer Gibb
@@ -406,7 +407,7 @@ public abstract class ZuulProxyTestBase {
return new ZuulFilter() {
@Override
public String filterType() {
return "pre";
return PRE_TYPE;
}
@Override

View File

@@ -0,0 +1,19 @@
# This configuration used by test class org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests
logging.level.org.springframework.cloud.netflix.feign=debug
feign.client.default-to-properties=true
feign.client.default-config=default
feign.client.config.default.connectTimeout=5000
feign.client.config.default.readTimeout=5000
feign.client.config.default.loggerLevel=full
feign.client.config.default.errorDecoder=org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests.DefaultErrorDecoder
feign.client.config.default.retryer=org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests.NoRetryer
feign.client.config.default.decode404=true
feign.client.config.foo.requestInterceptors[0]=org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests.FooRequestInterceptor
feign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.netflix.feign.FeignClientUsingPropertiesTests.BarRequestInterceptor
feign.client.config.bar.connectTimeout=1000
feign.client.config.bar.readTimeout=1000

View File

@@ -33,6 +33,8 @@ import com.netflix.discovery.shared.transport.EurekaTransportConfig;
import lombok.Data;
import static org.springframework.cloud.netflix.eureka.EurekaConstants.DEFAULT_PREFIX;
/**
* @author Dave Syer
*/
@@ -45,7 +47,7 @@ public class EurekaClientConfigBean implements EurekaClientConfig {
@Autowired(required = false)
PropertyResolver propertyResolver;
public static final String DEFAULT_URL = "http://localhost:8761" + EurekaConstants.DEFAULT_PREFIX
public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX
+ "/";
public static final String DEFAULT_ZONE = "defaultZone";

View File

@@ -122,8 +122,9 @@ public class EurekaAutoServiceRegistration implements AutoServiceRegistration, S
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
// register in case meta data changed
stop();
if( event.getApplicationContext() == context ) {
stop();
}
}
}