Ryan Baxter
7 years ago
20 changed files with 1188 additions and 54 deletions
@ -0,0 +1,174 @@
@@ -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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,160 @@
@@ -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; |
||||
} |
||||
} |
@ -0,0 +1,163 @@
@@ -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 { |
||||
} |
||||
|
||||
} |
@ -0,0 +1,109 @@
@@ -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)); |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,178 @@
@@ -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; |
||||
} |
||||
} |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/* |
||||
* 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.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; |
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* @author Yongsung Yoon |
||||
*/ |
||||
public class RibbonCommandHystrixThreadPoolKeyTests { |
||||
|
||||
private ZuulProperties zuulProperties; |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
zuulProperties = new ZuulProperties(); |
||||
} |
||||
|
||||
@Test |
||||
public void testDefaultHystrixThreadPoolKey() throws Exception { |
||||
zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); |
||||
|
||||
TestRibbonCommand ribbonCommand1 = new TestRibbonCommand("testCommand1", zuulProperties); |
||||
TestRibbonCommand ribbonCommand2 = new TestRibbonCommand("testCommand2", zuulProperties); |
||||
|
||||
// CommandGroupKey should be used as ThreadPoolKey as default.
|
||||
assertThat(ribbonCommand1.getThreadPoolKey().name()).isEqualTo(ribbonCommand1.getCommandGroup().name()); |
||||
assertThat(ribbonCommand2.getThreadPoolKey().name()).isEqualTo(ribbonCommand2.getCommandGroup().name()); |
||||
} |
||||
|
||||
@Test |
||||
public void testUseSeparateThreadPools() throws Exception { |
||||
zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); |
||||
zuulProperties.getThreadPool().setUseSeparateThreadPools(true); |
||||
|
||||
TestRibbonCommand ribbonCommand1 = new TestRibbonCommand("testCommand1", zuulProperties); |
||||
TestRibbonCommand ribbonCommand2 = new TestRibbonCommand("testCommand2", zuulProperties); |
||||
|
||||
assertThat(ribbonCommand1.getThreadPoolKey().name()).isEqualTo("testCommand1"); |
||||
assertThat(ribbonCommand2.getThreadPoolKey().name()).isEqualTo("testCommand2"); |
||||
} |
||||
|
||||
@Test |
||||
public void testThreadPoolKeyPrefix() throws Exception { |
||||
final String prefix = "zuulgw-"; |
||||
|
||||
zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); |
||||
zuulProperties.getThreadPool().setUseSeparateThreadPools(true); |
||||
zuulProperties.getThreadPool().setThreadPoolKeyPrefix(prefix); |
||||
|
||||
TestRibbonCommand ribbonCommand1 = new TestRibbonCommand("testCommand1", zuulProperties); |
||||
TestRibbonCommand ribbonCommand2 = new TestRibbonCommand("testCommand2", zuulProperties); |
||||
|
||||
assertThat(ribbonCommand1.getThreadPoolKey().name()).isEqualTo(prefix + "testCommand1"); |
||||
assertThat(ribbonCommand2.getThreadPoolKey().name()).isEqualTo(prefix + "testCommand2"); |
||||
} |
||||
|
||||
@Test |
||||
public void testNoSideEffectOnSemaphoreIsolation() throws Exception { |
||||
final String prefix = "zuulgw-"; |
||||
|
||||
zuulProperties.setRibbonIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE); |
||||
zuulProperties.getThreadPool().setUseSeparateThreadPools(true); |
||||
zuulProperties.getThreadPool().setThreadPoolKeyPrefix(prefix); |
||||
|
||||
TestRibbonCommand ribbonCommand1 = new TestRibbonCommand("testCommand1", zuulProperties); |
||||
TestRibbonCommand ribbonCommand2 = new TestRibbonCommand("testCommand2", zuulProperties); |
||||
|
||||
// There should be no side effect on semaphore isolation
|
||||
assertThat(ribbonCommand1.getThreadPoolKey().name()).isEqualTo(ribbonCommand1.getCommandGroup().name()); |
||||
assertThat(ribbonCommand2.getThreadPoolKey().name()).isEqualTo(ribbonCommand2.getCommandGroup().name()); |
||||
} |
||||
|
||||
public static class TestRibbonCommand |
||||
extends AbstractRibbonCommand<AbstractLoadBalancerAwareClient<ClientRequest, HttpResponse>, ClientRequest, HttpResponse> { |
||||
public TestRibbonCommand(String commandKey, ZuulProperties zuulProperties) { |
||||
super(commandKey, null, null, zuulProperties); |
||||
} |
||||
|
||||
@Override |
||||
protected ClientRequest createRequest() throws Exception { |
||||
return null; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,19 @@
@@ -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 |
Loading…
Reference in new issue