diff --git a/.gitignore b/.gitignore
index 67907698..81428507 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ _site/
.factorypath
*.log
.shelf
+*.swp
+*.swo
diff --git a/docs/pom.xml b/docs/pom.xml
index 201718f4..ddf45464 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -14,6 +14,7 @@
spring-cloud-netflix
${basedir}/..
+ 1.0.x,1.1.x
diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh
index e1063ce3..a5d1acd5 100755
--- a/docs/src/main/asciidoc/ghpages.sh
+++ b/docs/src/main/asciidoc/ghpages.sh
@@ -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
\ No newline at end of file
diff --git a/scripts/runAcceptanceTests.sh b/scripts/runAcceptanceTests.sh
index 0fc9429a..97a9d6bf 100755
--- a/scripts/runAcceptanceTests.sh
+++ b/scripts/runAcceptanceTests.sh
@@ -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"
diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java
index 3bdb633b..9d3278df 100644
--- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java
@@ -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.
diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java
index 39dc918b..6db078a9 100644
--- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilter.java
@@ -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);
diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTest.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java
similarity index 93%
rename from spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTest.java
rename to spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java
index 8166bfb2..c9c0285c 100644
--- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTest.java
+++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/resttemplate/RestTemplateRetryTests.java
@@ -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 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 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);
+ }
+ }
+
+}
diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java
index 9b521676..940634ff 100644
--- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java
+++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/pre/PreDecorationFilterTests.java
@@ -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");
diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java
index 3557179f..8378efc8 100644
--- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java
+++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfiguration.java
@@ -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 {
- }
-
- }
-
}
diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java
index a9240293..9363bec9 100644
--- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java
+++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaClientAutoConfigurationTests.java
@@ -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) {