Merge branch 'master' of https://github.com/spring-cloud/spring-cloud-netflix into zuul_hystrix_threadpool

This commit is contained in:
Stéphane Lagraulet
2016-08-17 18:11:52 +02:00
10 changed files with 547 additions and 350 deletions

2
.gitignore vendored
View File

@@ -16,3 +16,5 @@ _site/
.factorypath
*.log
.shelf
*.swp
*.swo

View File

@@ -14,6 +14,7 @@
<properties>
<docs.main>spring-cloud-netflix</docs.main>
<main.basedir>${basedir}/..</main.basedir>
<docs.whitelisted.branches>1.0.x,1.1.x</docs.whitelisted.branches>
</properties>
<build>
<plugins>

View File

@@ -12,43 +12,118 @@ if ! [ -d docs/target/generated-docs ]; then
exit 0
fi
# Find name of current branch
# The script should be executed from the root folder
ROOT_FOLDER=`pwd`
echo "Current folder is ${ROOT_FOLDER}"
if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then
echo "You're not in the root folder of the project!"
exit 1
fi
# Retrieve properties
###################################################################
branch=$TRAVIS_BRANCH
[ "$branch" == "" ] && branch=`git rev-parse --abbrev-ref HEAD`
target=.
if [ "$branch" != "master" ]; then target=./$branch; mkdir -p $target; fi
# Prop that will let commit the changes
COMMIT_CHANGES="no"
MAVEN_PATH=${MAVEN_PATH:-}
echo "Path to Maven is [${MAVEN_PATH}]"
# Code getting the name of the current branch. For master we want to publish as we did until now
# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch
# If there is a branch already passed will reuse it - otherwise will try to find it
CURRENT_BRANCH=${BRANCH}
if [[ -z "${CURRENT_BRANCH}" ]] ; then
CURRENT_BRANCH=$(git symbolic-ref -q HEAD)
CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/}
CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD}
fi
echo "Current branch is [${CURRENT_BRANCH}]"
git checkout ${CURRENT_BRANCH}
# Get the name of the `docs.main` property
MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \
-Dexec.executable="echo" \
-Dexec.args='${docs.main}' \
--non-recursive \
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec)
echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]"
# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile
WHITELIST_PROPERTY="docs.whitelisted.branches"
WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \
-Dexec.executable="echo" \
-Dexec.args="\${${WHITELIST_PROPERTY}}" \
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \
-P docs \
-pl docs)
echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]"
# Stash any outstanding changes
###################################################################
git diff-index --quiet HEAD
dirty=$?
git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1")
if [ "$dirty" != "0" ]; then git stash; fi
# Switch to gh-pages branch to sync it with current branch
# Switch to gh-pages branch to sync it with master
###################################################################
git checkout gh-pages
git pull origin gh-pages
for f in docs/target/generated-docs/*; do
file=${f#docs/target/generated-docs/*}
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
# Not ignored...
cp -rf $f $target
git add -A $target/$file
fi
done
git add -A README.adoc || echo "No change to README.adoc"
git commit -a -m "Sync docs from $branch to gh-pages" || echo "Nothing committed"
# Uncomment the following push if you want to auto push to
# the gh-pages branch whenever you commit to branch locally.
# This is a little extreme. Use with care!
# Add git branches
###################################################################
git push origin gh-pages || echo "Cannot push gh-pages"
mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH}
if [[ "${CURRENT_BRANCH}" == "master" ]] ; then
echo -e "Current branch is master - will copy the current docs only to the root folder"
for f in docs/target/generated-docs/*; do
file=${f#docs/target/generated-docs/*}
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
# Not ignored...
cp -rf $f ${ROOT_FOLDER}/
git add -A ${ROOT_FOLDER}/$file
fi
done
COMMIT_CHANGES="yes"
else
echo -e "Current branch is [${CURRENT_BRANCH}]"
# http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin
if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then
echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder"
for f in docs/target/generated-docs/*; do
file=${f#docs/target/generated-docs/*}
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
# Not ignored...
# We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then
# We don't want to copy the spring-cloud-sleuth.html
# we want it to be converted to index.html
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
else
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file
fi
fi
done
COMMIT_CHANGES="yes"
else
echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in
[docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch."
fi
fi
# Finally, switch back to the current branch and exit block
git checkout $branch
if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then
git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages"
# Uncomment the following push if you want to auto push to
# the gh-pages branch whenever you commit to master locally.
# This is a little extreme. Use with care!
###################################################################
git push origin gh-pages
fi
# Finally, switch back to the master branch and exit block
git checkout ${CURRENT_BRANCH}
if [ "$dirty" != "0" ]; then git stash pop; fi
exit 0
exit 0

View File

@@ -14,9 +14,9 @@ curl "${SCRIPT_URL}" --output runAcceptanceTests.sh
chmod +x runAcceptanceTests.sh
echo "Killing all running apps"
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" -n
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" --killnow
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" -k
./runAcceptanceTests.sh -t "${AT_WHAT_TO_TEST}" --killattheend
SCRIPT_URL="https://raw.githubusercontent.com/spring-cloud-samples/tests/master/scripts/runTests.sh"

View File

@@ -82,6 +82,11 @@ public class ZuulProperties {
*/
private boolean addProxyHeaders = true;
/**
* Flag to determine whether the proxy forwards the Host header.
*/
private boolean addHostHeader = false;
/**
* Set of service names not to consider for proxying automatically. By default all
* services in the discovery client will be proxied.

View File

@@ -19,6 +19,8 @@ package org.springframework.cloud.netflix.zuul.filters.pre;
import java.net.MalformedURLException;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
@@ -36,6 +38,8 @@ import lombok.extern.apachecommons.CommonsLog;
@CommonsLog
public class PreDecorationFilter extends ZuulFilter {
public static final int FILTER_ORDER = 5;
private RouteLocator routeLocator;
private String dispatcherServletPath;
@@ -58,7 +62,7 @@ public class PreDecorationFilter extends ZuulFilter {
@Override
public int filterOrder() {
return 5;
return FILTER_ORDER;
}
@Override
@@ -115,29 +119,34 @@ public class PreDecorationFilter extends ZuulFilter {
ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
}
if (this.properties.isAddProxyHeaders()) {
ctx.addZuulRequestHeader("X-Forwarded-Host",
ctx.getRequest().getServerName());
ctx.addZuulRequestHeader("X-Forwarded-Host", toHostHeader(ctx.getRequest()));
ctx.addZuulRequestHeader("X-Forwarded-Port",
String.valueOf(ctx.getRequest().getServerPort()));
ctx.addZuulRequestHeader(ZuulHeaders.X_FORWARDED_PROTO,
ctx.getRequest().getScheme());
String forwardedPrefix =
ctx.getRequest().getHeader("X-Forwarded-Prefix");
String contextPath = ctx.getRequest().getContextPath();
String prefix = StringUtils.hasLength(forwardedPrefix)
? forwardedPrefix
: (StringUtils.hasLength(contextPath) ? contextPath : null);
if (StringUtils.hasText(route.getPrefix())) {
String existingPrefix = ctx.getRequest()
.getHeader("X-Forwarded-Prefix");
StringBuilder newPrefixBuilder = new StringBuilder();
if (StringUtils.hasLength(existingPrefix)) {
if (existingPrefix.endsWith("/")
if (prefix != null) {
if (prefix.endsWith("/")
&& route.getPrefix().startsWith("/")) {
newPrefixBuilder.append(existingPrefix, 0,
existingPrefix.length() - 1);
newPrefixBuilder.append(prefix, 0,
prefix.length() - 1);
}
else {
newPrefixBuilder.append(existingPrefix);
newPrefixBuilder.append(prefix);
}
}
newPrefixBuilder.append(route.getPrefix());
ctx.addZuulRequestHeader("X-Forwarded-Prefix",
newPrefixBuilder.toString());
prefix = newPrefixBuilder.toString();
}
if (prefix != null) {
ctx.addZuulRequestHeader("X-Forwarded-Prefix", prefix);
}
String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
String remoteAddr = ctx.getRequest().getRemoteAddr();
@@ -149,6 +158,9 @@ public class PreDecorationFilter extends ZuulFilter {
}
ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
}
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader("Host", toHostHeader(ctx.getRequest()));
}
}
}
else {
@@ -182,6 +194,15 @@ public class PreDecorationFilter extends ZuulFilter {
return null;
}
private String toHostHeader(HttpServletRequest request) {
int port = request.getServerPort();
if ((port == 80 && "http".equals(request.getScheme())) || (port == 443 && "https".equals(request.getScheme()))) {
return request.getServerName();
} else {
return request.getServerName() + ":" + port;
}
}
private URL getUrl(String target) {
try {
return new URL(target);

View File

@@ -1,290 +1,289 @@
package org.springframework.cloud.netflix.resttemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
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.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
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 org.springframework.web.client.RestTemplate;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.LoadBalancerStats;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ServerStats;
import com.netflix.niws.client.http.HttpClientLoadBalancerErrorHandler;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RestTemplateRetryTest.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
"spring.application.name=resttemplatetest",
"logging.level.org.springframework.cloud.netflix.resttemplate=DEBUG",
"badClients.ribbon.MaxAutoRetries=0",
"badClients.ribbon.OkToRetryOnAllOperations=true", "ribbon.http.client.enabled" })
@DirtiesContext
public class RestTemplateRetryTest {
final private static Log logger = LogFactory.getLog(RestTemplateRetryTest.class);
@Value("${local.server.port}")
private int port = 0;
@Autowired
private RestTemplate testClient;
public RestTemplateRetryTest() {
}
@Configuration
@EnableAutoConfiguration
@RestController
@RibbonClient(name = "badClients", configuration = LocalBadClientConfiguration.class)
public static class Application {
private AtomicInteger hits = new AtomicInteger(1);
private AtomicInteger retryHits = new AtomicInteger(1);
@RequestMapping(method = RequestMethod.GET, value = "/ping")
public int ping() {
return 0;
}
@RequestMapping(method = RequestMethod.GET, value = "/good")
public int good() {
int lValue = this.hits.getAndIncrement();
return lValue;
}
@RequestMapping(method = RequestMethod.GET, value = "/timeout")
public int timeout() throws Exception {
int lValue = this.retryHits.getAndIncrement();
// Force the good server to have 2 consecutive errors a couple of times.
if (lValue == 2 || lValue == 3 || lValue == 5 || lValue == 6) {
Thread.sleep(500);
}
return lValue;
}
@RequestMapping(method = RequestMethod.GET, value = "/null")
public int isNull() throws Exception {
throw new NullPointerException("Null");
}
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Before
public void setup() throws Exception {
// Force Ribbon configuration by making one call.
this.testClient.getForObject("http://badClients/ping", Integer.class);
}
@Test
public void testNullPointer() throws Exception {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats badServer1Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer);
ServerStats badServer2Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
ServerStats goodServerStats = stats
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
long targetConnectionCount = goodServerStats.getTotalRequestsCount() + 10;
// A null pointer should NOT trigger a circuit breaker.
for (int index = 0; index < 10; index++) {
try {
this.testClient.getForObject("http://badClients/null", Integer.class);
}
catch (Exception exception) {
}
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertEquals(targetConnectionCount, goodServerStats.getTotalRequestsCount());
// Wait for any timeout thread to finish.
}
private void logServerStats(Server server) {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats serverStats = stats.getSingleServerStat(server);
logger.debug("Server : " + server.toString() + " : Total Count == "
+ serverStats.getTotalRequestsCount() + ", Failure Count == "
+ serverStats.getFailureCount() + ", Successive Connection Failure == "
+ serverStats.getSuccessiveConnectionFailureCount()
+ ", Circuit Breaker ? == " + serverStats.isCircuitBreakerTripped());
}
@Test
public void testRestRetries() {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats badServer1Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer);
ServerStats badServer2Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
ServerStats goodServerStats = stats
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
long targetConnectionCount = goodServerStats.getTotalRequestsCount() + 20;
int hits = 0;
for (int index = 0; index < 20; index++) {
hits = this.testClient.getForObject("http://badClients/good", Integer.class);
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertEquals(targetConnectionCount, goodServerStats.getTotalRequestsCount());
assertEquals(20, hits);
System.out.println("Retry Hits: " + hits);
}
@Test
public void testRestRetriesWithReadTimeout() throws Exception {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats badServer1Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer);
ServerStats badServer2Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
ServerStats goodServerStats = stats
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
assertTrue(!badServer1Stats.isCircuitBreakerTripped());
assertTrue(!badServer2Stats.isCircuitBreakerTripped());
int hits = 0;
for (int index = 0; index < 15; index++) {
hits = this.testClient.getForObject("http://badClients/timeout",
Integer.class);
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertTrue(!goodServerStats.isCircuitBreakerTripped());
// 15 + 4 timeouts. See the endpoint for timeout conditions.
assertEquals(19, hits);
// Wait for any timeout thread to finish.
Thread.sleep(600);
}
}
// Load balancer with fixed server list for "local" pointing to localhost
// and some bogus servers are thrown in to test retry
@Configuration
class LocalBadClientConfiguration {
static BaseLoadBalancer balancer;
static Server goodServer;
static Server badServer;
static Server badServer2;
public LocalBadClientConfiguration() {
}
@Value("${local.server.port}")
private int port = 0;
@Bean
public IRule loadBalancerRule() {
// This is a good place to try different load balancing rules and how those rules
// behave in failure
// states: BestAvailableRule, WeightedResponseTimeRule, etc
// This rule just uses a round robin and will skip servers that are in circuit
// breaker state.
return new AvailabilityFilteringRule();
}
@Bean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, IRule rule, IPing ping) {
goodServer = new Server("localhost", this.port);
badServer = new Server("mybadhost", 10001);
badServer2 = new Server("localhost", -1);
balancer = LoadBalancerBuilder.newBuilder().withClientConfig(config)
.withRule(rule).withPing(ping).buildFixedServerListLoadBalancer(
Arrays.asList(badServer, badServer2, goodServer));
return balancer;
}
@Bean
public RetryHandler retryHandler() {
return new OverrideRetryHandler();
}
static class OverrideRetryHandler extends HttpClientLoadBalancerErrorHandler {
public OverrideRetryHandler() {
this.circuitRelated.add(UnknownHostException.class);
this.retriable.add(UnknownHostException.class);
}
}
}
package org.springframework.cloud.netflix.resttemplate;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
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.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.SocketUtils;
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.client.RestTemplate;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.LoadBalancerBuilder;
import com.netflix.loadbalancer.LoadBalancerStats;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import com.netflix.loadbalancer.ServerStats;
import com.netflix.niws.client.http.HttpClientLoadBalancerErrorHandler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = RestTemplateRetryTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
"spring.application.name=resttemplatetest",
"logging.level.org.springframework.cloud.netflix.resttemplate=DEBUG",
"logging.level.com.netflix=DEBUG", "badClients.ribbon.MaxAutoRetries=0",
"badClients.ribbon.OkToRetryOnAllOperations=true", "ribbon.http.client.enabled" })
@DirtiesContext
public class RestTemplateRetryTests {
final private static Log logger = LogFactory.getLog(RestTemplateRetryTests.class);
@Value("${local.server.port}")
private int port = 0;
@Autowired
private RestTemplate testClient;
public RestTemplateRetryTests() {
}
@Before
public void setup() throws Exception {
// Force Ribbon configuration by making one call.
this.testClient.getForObject("http://badClients/ping", Integer.class);
}
@Test
public void testNullPointer() throws Exception {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats badServer1Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer);
ServerStats badServer2Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
ServerStats goodServerStats = stats
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
long targetConnectionCount = goodServerStats.getTotalRequestsCount() + 10;
// A null pointer should NOT trigger a circuit breaker.
for (int index = 0; index < 10; index++) {
try {
this.testClient.getForObject("http://badClients/null", Integer.class);
}
catch (Exception exception) {
}
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertEquals(targetConnectionCount, goodServerStats.getTotalRequestsCount());
// Wait for any timeout thread to finish.
}
private void logServerStats(Server server) {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats serverStats = stats.getSingleServerStat(server);
logger.debug("Server : " + server.toString() + " : Total Count == "
+ serverStats.getTotalRequestsCount() + ", Failure Count == "
+ serverStats.getFailureCount() + ", Successive Connection Failure == "
+ serverStats.getSuccessiveConnectionFailureCount()
+ ", Circuit Breaker ? == " + serverStats.isCircuitBreakerTripped());
}
@Test
public void testRestRetries() {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats badServer1Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer);
ServerStats badServer2Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
ServerStats goodServerStats = stats
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
long targetConnectionCount = goodServerStats.getTotalRequestsCount() + 20;
int hits = 0;
for (int index = 0; index < 20; index++) {
hits = this.testClient.getForObject("http://badClients/good", Integer.class);
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertEquals(targetConnectionCount, goodServerStats.getTotalRequestsCount());
assertEquals(20, hits);
logger.debug("Retry Hits: " + hits);
}
@Test
public void testRestRetriesWithReadTimeout() throws Exception {
LoadBalancerStats stats = LocalBadClientConfiguration.balancer
.getLoadBalancerStats();
ServerStats badServer1Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer);
ServerStats badServer2Stats = stats
.getSingleServerStat(LocalBadClientConfiguration.badServer2);
ServerStats goodServerStats = stats
.getSingleServerStat(LocalBadClientConfiguration.goodServer);
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
assertTrue(!badServer1Stats.isCircuitBreakerTripped());
assertTrue(!badServer2Stats.isCircuitBreakerTripped());
int hits = 0;
for (int index = 0; index < 15; index++) {
hits = this.testClient.getForObject("http://badClients/timeout",
Integer.class);
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertTrue(!goodServerStats.isCircuitBreakerTripped());
// 15 + 4 timeouts. See the endpoint for timeout conditions.
assertEquals(19, hits);
// Wait for any timeout thread to finish.
Thread.sleep(600);
}
@Configuration
@EnableAutoConfiguration
@RestController
@RibbonClient(name = "badClients", configuration = LocalBadClientConfiguration.class)
public static class Application {
private AtomicInteger hits = new AtomicInteger(1);
private AtomicInteger retryHits = new AtomicInteger(1);
@RequestMapping(method = RequestMethod.GET, value = "/ping")
public int ping() {
return 0;
}
@RequestMapping(method = RequestMethod.GET, value = "/good")
public int good() {
int lValue = this.hits.getAndIncrement();
return lValue;
}
@RequestMapping(method = RequestMethod.GET, value = "/timeout")
public int timeout() throws Exception {
int lValue = this.retryHits.getAndIncrement();
// Force the good server to have 2 consecutive errors a couple of times.
if (lValue == 2 || lValue == 3 || lValue == 5 || lValue == 6) {
Thread.sleep(500);
}
return lValue;
}
@RequestMapping(method = RequestMethod.GET, value = "/null")
public int isNull() throws Exception {
throw new NullPointerException("Null");
}
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
}
// Load balancer with fixed server list for "local" pointing to localhost
// and some bogus servers are thrown in to test retry
@Configuration
class LocalBadClientConfiguration {
static BaseLoadBalancer balancer;
static Server goodServer;
static Server badServer;
static Server badServer2;
public LocalBadClientConfiguration() {
}
@Value("${local.server.port}")
private int port = 0;
@Bean
public IRule loadBalancerRule() {
// This is a good place to try different load balancing rules and how those rules
// behave in failure states: BestAvailableRule, WeightedResponseTimeRule, etc
// This rule just uses a round robin and will skip servers that are in circuit
// breaker state.
return new AvailabilityFilteringRule();
}
@Bean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, IRule rule, IPing ping) {
goodServer = new Server("localhost", this.port);
badServer = new Server("mybadhost", 10001);
badServer2 = new Server("localhost", SocketUtils.findAvailableTcpPort());
balancer = LoadBalancerBuilder.newBuilder().withClientConfig(config)
.withRule(rule).withPing(ping).buildFixedServerListLoadBalancer(
Arrays.asList(badServer, badServer2, goodServer));
return balancer;
}
@Bean
public RetryHandler retryHandler() {
return new OverrideRetryHandler();
}
static class OverrideRetryHandler extends HttpClientLoadBalancerErrorHandler {
public OverrideRetryHandler() {
this.circuitRelated.add(UnknownHostException.class);
this.retriable.add(UnknownHostException.class);
}
}
}

View File

@@ -90,6 +90,34 @@ public class PreDecorationFilterTests {
assertEquals(false, this.filter.shouldFilter());
}
@Test
public void xForwardedHostHasPort() throws Exception {
this.properties.setPrefix("/api");
this.request.setRequestURI("/api/foo/1");
this.request.setRemoteAddr("5.6.7.8");
this.request.setServerPort(8080);
this.routeLocator.addRoute(
new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null));
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("localhost:8080", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
}
@Test
public void hostHeaderSet() throws Exception {
this.properties.setPrefix("/api");
this.properties.setAddHostHeader(true);
this.request.setRequestURI("/api/foo/1");
this.request.setRemoteAddr("5.6.7.8");
this.request.setServerPort(8080);
this.routeLocator.addRoute(
new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null));
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("localhost:8080", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
assertEquals("localhost:8080", ctx.getZuulRequestHeaders().get("host"));
}
@Test
public void prefixRouteAddsHeader() throws Exception {
this.properties.setPrefix("/api");
@@ -136,6 +164,86 @@ public class PreDecorationFilterTests {
getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid"));
}
@Test
public void routeWithContextPath() {
this.properties.setStripPrefix(false);
this.request.setRequestURI("/api/foo/1");
this.request.setContextPath("/context-path");
this.routeLocator.addRoute(
new ZuulRoute("foo", "/api/foo/**", "foo", null, false, null, null));
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/api/foo/1", ctx.get("requestURI"));
assertEquals("localhost", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
assertEquals("80", ctx.getZuulRequestHeaders().get("x-forwarded-port"));
assertEquals("http", ctx.getZuulRequestHeaders().get("x-forwarded-proto"));
assertEquals("/context-path",
ctx.getZuulRequestHeaders().get("x-forwarded-prefix"));
assertEquals("foo",
getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid"));
}
@Test
public void prefixRouteWithContextPath() {
this.properties.setPrefix("/api");
this.properties.setStripPrefix(true);
this.request.setRequestURI("/api/foo/1");
this.request.setContextPath("/context-path");
this.routeLocator.addRoute(
new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null));
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/foo/1", ctx.get("requestURI"));
assertEquals("localhost", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
assertEquals("80", ctx.getZuulRequestHeaders().get("x-forwarded-port"));
assertEquals("http", ctx.getZuulRequestHeaders().get("x-forwarded-proto"));
assertEquals("/context-path/api",
ctx.getZuulRequestHeaders().get("x-forwarded-prefix"));
assertEquals("foo",
getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid"));
}
@Test
public void routeIgnoreContextPathIfPrefixHeader() {
this.properties.setStripPrefix(false);
this.request.setRequestURI("/api/foo/1");
this.request.setContextPath("/context-path");
this.request.addHeader("X-Forwarded-Prefix", "/prefix");
this.routeLocator.addRoute(
new ZuulRoute("foo", "/api/foo/**", "foo", null, false, null, null));
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/api/foo/1", ctx.get("requestURI"));
assertEquals("localhost", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
assertEquals("80", ctx.getZuulRequestHeaders().get("x-forwarded-port"));
assertEquals("http", ctx.getZuulRequestHeaders().get("x-forwarded-proto"));
assertEquals("/prefix",
ctx.getZuulRequestHeaders().get("x-forwarded-prefix"));
assertEquals("foo",
getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid"));
}
@Test
public void prefixRouteIgnoreContextPathIfPrefixHeader() {
this.properties.setPrefix("/api");
this.properties.setStripPrefix(true);
this.request.setRequestURI("/api/foo/1");
this.request.setContextPath("/context-path");
this.request.addHeader("X-Forwarded-Prefix", "/prefix");
this.routeLocator.addRoute(
new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null));
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/foo/1", ctx.get("requestURI"));
assertEquals("localhost", ctx.getZuulRequestHeaders().get("x-forwarded-host"));
assertEquals("80", ctx.getZuulRequestHeaders().get("x-forwarded-port"));
assertEquals("http", ctx.getZuulRequestHeaders().get("x-forwarded-proto"));
assertEquals("/prefix/api",
ctx.getZuulRequestHeaders().get("x-forwarded-prefix"));
assertEquals("foo",
getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid"));
}
@Test
public void forwardRouteAddsLocation() throws Exception {
this.properties.setPrefix("/api");
@@ -377,7 +485,7 @@ public class PreDecorationFilterTests {
assertTrue("sensitiveHeaders is wrong: " + sensitiveHeaders,
sensitiveHeaders.containsAll(Arrays.asList("x-bar", "x-foo")));
}
@Test
public void urlProperlyDecodedWhenCharacterEncodingIsSet() throws Exception {
this.request.setCharacterEncoding("UTF-8");

View File

@@ -26,7 +26,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -204,7 +203,8 @@ public class EurekaClientAutoConfiguration {
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnRefreshScopeCondition.class)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnBean(RefreshAutoConfiguration.class)
@interface ConditionalOnRefreshScope {
}
@@ -219,24 +219,10 @@ public class EurekaClientAutoConfiguration {
static class MissingClass {
}
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnMissingBean(RefreshAutoConfiguration.class)
static class MissingScope {
}
}
private static class OnRefreshScopeCondition extends AllNestedConditions {
public OnRefreshScopeCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnBean(RefreshAutoConfiguration.class)
static class FoundScope {
}
}
}

View File

@@ -112,8 +112,8 @@ public class EurekaClientAutoConfigurationTests {
EnvironmentTestUtils.addEnvironment(this.context, "server.port=8989",
"eureka.client.serviceUrl.defaultZone=http://user:foo@example.com:80/eureka");
setupContext(MockClientConfiguration.class);
//ApacheHttpClient4 http = this.context.getBean(ApacheHttpClient4.class);
//Mockito.verify(http).addFilter(Matchers.any(HTTPBasicAuthFilter.class));
// ApacheHttpClient4 http = this.context.getBean(ApacheHttpClient4.class);
// Mockito.verify(http).addFilter(Matchers.any(HTTPBasicAuthFilter.class));
}
private void testNonSecurePort(String propName) {