Compare commits

...
Author SHA1 Message Date
Ryan Baxter 766f8bbab7 Versions changes for 1.1.5.RELEASE 2016-07-22 09:36:13 -04:00
Dave Syer 75dcd309e9 Actually fix the feign client validation issue
We need to still assert that the FeignClient is valid (no name and
value specified together for instance). Added missing tests.
2016-07-21 10:35:22 +01:00
Dave Syer c7eb5f570d Fix Feign validation for Spring 4.2 and 4.3
There is a utility method in Spring that we can use to merge all
the aliases. That way 4.2 and 4.3 behave the same way.

Fixes gh-1192
2016-07-21 08:26:43 +01:00
Dave Syer 2179675027 Use JUnit assertions 2016-07-21 08:26:43 +01:00
Dave Syer fd159673c4 Fix generic type signature (otherwise breaks with Spring 4.3) 2016-07-21 08:26:43 +01:00
Ryan Baxter 5d52c61c7c Merge pull request #1190 from denyska/sticky_cookie
Disable cookie management for RibbonLoadBalancingHttpClient Fixes gh-301
2016-07-15 09:48:05 -04:00
Denys Kurylenko 76baf2b914 Merge remote-tracking branch 'spring/1.1.x' into sticky_cookie 2016-07-14 17:59:28 -07:00
Denys Kurylenko 4f3b89d798 Disable cookie management for RibbonLoadBalancingHttpClient Fixes gh-301 2016-07-14 14:19:36 -07:00
Ryan Baxter d35b660eed Merge pull request #1187 from ryanjbaxter/1.1.x-upgrade-eureka-to-1.4.10
Upgrade Eureka to 1.4.10
2016-07-13 14:23:06 -04:00
Ryan Baxter fa783ccedd Upgrade Eureka to 1.4.10. Fixes #1186 2016-07-13 14:03:25 -04:00
Spencer Gibb 0cb8bf671b Remove phantom spring-cloud-netflix-zuul-server from dependencies pom
fixes gh-1175
2016-07-11 21:48:39 -06:00
Ryan Baxter 10eb85b7f0 Updated snapshot versions 2016-07-08 09:46:40 -04:00
Ryan Baxter b42ccc5d44 Revert "Updated dependencies and versions for 1.1.4 release"
This reverts commit 931f058068.
2016-07-08 09:31:26 -04:00
Ryan Baxter 931f058068 Updated dependencies and versions for 1.1.4 release 2016-07-07 16:48:59 -04:00
Ryan Baxter c1dd0a6b6d Merge pull request #1170 from ryanjbaxter/upgrade-eureka-to-1.4.9-1167
Upgrade Eureka to 1.4.9
2016-07-07 13:19:31 -04:00
Ryan Baxter c57a0e323c Upgrade Eureka to 1.4.9. Fixes #1167 2016-07-07 12:15:06 -04:00
Dave Syer e1dca45c10 Revert to snapshots 2016-06-30 08:09:22 -07:00
Dave Syer a8282dd0bd Update to 1.1.3.RELEASE 2016-06-30 08:07:45 -07:00
Spencer Gibb 9e3e749438 fix typo 2016-06-29 11:38:33 -06:00
Patrick Cornelißen 434305de19 Adds ability to disable hostname validation.
Adds property zuul.sslHostnameValidationEnabled to be able to disable the hostname validation in the simpleHostRoutingFilter

fixes gh-1057
2016-06-28 10:34:57 -06:00
Spencer Gibb a9c0fc1502 Split ProxyRequestHelper to function without Actuator.
Previously ProxyRequestHelper contained a TraceRepository field. This caused zuul apps to fail with a class not found exception if they excluded actuator. This splits TraceRepository functionality into a new TraceProxyRequestHelper that extends ProxyRequestHelper. Auto configuration creates the appropriate ProxyRequestHelper based on the existence or not of actuator classes.

fixes gh-1135
2016-06-24 16:03:59 -06:00
Spencer Gibb 9dafae6c01 add .shelf to .gitignore 2016-06-24 09:53:10 -06:00
Spencer Gibb f538444dee Add hystrix.stream.queue.size to customize queue size.
Also added descriptions to properties.

fixes gh-1131
2016-06-24 09:52:36 -06:00
Spencer Gibb 99beb9d045 Merge pull request #1127 from william-tran/schemeless-uri
* schemeless-uri:
  Without explicit client config of IsSecure, lookup from registration
2016-06-23 15:09:36 -06:00
Will Tran 4cada7c8cc Without explicit client config of IsSecure, lookup from registration
instead of defaulting to false.

Fixes gh-1126
2016-06-23 15:09:16 -06:00
Spencer Gibb 34dff916cf Change parent poms to snapshot. 2016-06-22 12:36:22 -06:00
Spencer Gibb 7e9faef2e7 Set entity contentLength to 0 for GET requests.
Zuul sets an empty entity for GET requests. The apache http client either sets transfer encoding to chunked or a content length header. This change sets the apache entity contentLength to 0 (not the header), to that in RequestContent the "content-length" header is set to 0 rathern than transfer encoding.

See gh-1042
2016-06-22 12:32:57 -06:00
Jérôme Mirc 476a5cb84c Fix hystrix dashboard TPS.
fixes gh-1113

See https://github.com/Netflix/Hystrix/issues/1251
2016-06-21 15:24:31 -06:00
Spencer Gibb cfa4302965 Set entity contentLength for HttpClientRibbonCommand.
If the content length isn't set on the entity, when http client determines which header to set, it sets transfer-encoding to chunked rather than content-length.

This fixes a missing content-length issue when using `HttpClientRibbonCommandFactory`.

See gh-1042
2016-06-20 12:42:33 -06:00
Spencer Gibb 5d6590da44 Angel eureka clients can use brixton eureka servers.
Updates so CloudJacksonJson extension is properly recognized by Eureka
Server as LegacyJacksonJson.

see gh-978
fixes gh-1111
2016-06-17 16:46:31 -06:00
52 changed files with 644 additions and 212 deletions
+1
View File
@@ -14,3 +14,4 @@ _site/
*.ipr
*.iws
.factorypath
.shelf
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
</parent>
<artifactId>spring-cloud-netflix-docs</artifactId>
<packaging>pom</packaging>
+1 -1
View File
@@ -9,7 +9,7 @@
<relativePath />
</parent>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<packaging>pom</packaging>
<name>Spring Cloud Netflix</name>
<description>Spring Cloud Netflix</description>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-core</artifactId>
@@ -16,6 +16,7 @@
package org.springframework.cloud.netflix.endpoint;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -60,8 +61,8 @@ public abstract class ServletWrappingEndpoint implements InitializingBean,
this.controller.setApplicationContext(applicationContext);
}
protected ServletWrappingEndpoint(Class<?> servletClass, String servletName,
String path, boolean sensitive, boolean enabled) {
protected ServletWrappingEndpoint(Class<? extends Servlet> servletClass,
String servletName, String path, boolean sensitive, boolean enabled) {
this.controller.setServletClass(servletClass);
this.controller.setServletName(servletName);
this.path = path;
@@ -76,12 +77,13 @@ public abstract class ServletWrappingEndpoint implements InitializingBean,
}
public boolean isEnabled() {
return enabled;
return this.enabled;
}
public ServletWrappingController getController() {
return controller;
return this.controller;
}
@Override
public String getPath() {
return this.path;
@@ -40,6 +40,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
@@ -189,16 +190,9 @@ public class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
}
private void validate(Map<String, Object> attributes) {
if (StringUtils.hasText((String) attributes.get("value"))) {
Assert.isTrue(!StringUtils.hasText((String) attributes.get("name")),
"Either name or value can be specified, but not both");
Assert.isTrue(!StringUtils.hasText((String) attributes.get("serviceId")),
"Either name (serviceId) or value can be specified, but not both");
}
if (StringUtils.hasText((String) attributes.get("name"))) {
Assert.isTrue(!StringUtils.hasText((String) attributes.get("serviceId")),
"Either name or serviceId can be specified, but not both");
}
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
annotation.getAliasedString("name", FeignClient.class, null);
}
private String getName(Map<String, Object> attributes) {
@@ -114,7 +114,10 @@ public class RibbonLoadBalancerClient implements LoadBalancerClient {
private boolean isSecure(Server server, String serviceId) {
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
if (config != null) {
return config.get(CommonClientConfigKey.IsSecure, false);
Boolean isSecure = config.get(CommonClientConfigKey.IsSecure);
if (isSecure != null) {
return isSecure;
}
}
return serverIntrospector(serviceId).isSecure(server);
@@ -37,6 +37,7 @@ import lombok.Getter;
public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable {
private final String method;
private Long contentLength;
private final MultiValueMap<String, String> headers;
@@ -44,11 +45,18 @@ public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable
private final InputStream requestEntity;
public RibbonApacheHttpRequest(final String method, final URI uri,
final Boolean retryable, final MultiValueMap<String, String> headers,
final MultiValueMap<String, String> params, final InputStream requestEntity) {
this(method, uri, retryable, headers, params, requestEntity, null);
}
public RibbonApacheHttpRequest(final String method, final URI uri,
final Boolean retryable, final MultiValueMap<String, String> headers,
final MultiValueMap<String, String> params, final InputStream requestEntity) {
final MultiValueMap<String, String> params, final InputStream requestEntity, Long contentLength) {
this.method = method;
this.contentLength = contentLength;
this.uri = uri;
this.isRetriable = retryable;
this.headers = headers;
@@ -77,6 +85,13 @@ public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable
final BasicHttpEntity entity;
entity = new BasicHttpEntity();
entity.setContent(this.requestEntity);
// if the entity contentLength isn't set, transfer-encoding will be set
// to chunked in org.apache.http.protocol.RequestContent. See gh-1042
if (contentLength != null) {
entity.setContentLength(this.contentLength);
} else if ("GET".equals(this.method)) {
entity.setContentLength(0);
}
builder.setEntity(entity);
}
@@ -39,7 +39,7 @@ import com.netflix.loadbalancer.ILoadBalancer;
public class RibbonLoadBalancingHttpClient
extends
AbstractLoadBalancerAwareClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
private final HttpClient delegate = HttpClientBuilder.create().build();
private final HttpClient delegate = HttpClientBuilder.create().disableCookieManagement().build();
private int connectTimeout;
@@ -21,6 +21,7 @@ import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
@@ -30,6 +31,7 @@ import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.filters.TraceProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.discovery.ServiceRouteMapper;
@@ -52,9 +54,6 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class ZuulProxyConfiguration extends ZuulConfiguration {
@Autowired(required = false)
private TraceRepository traces;
@Autowired
private SpringClientFactory clientFactory;
@@ -108,17 +107,6 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
return new SimpleHostRoutingFilter(helper, zuulProperties);
}
@Bean
public ProxyRequestHelper proxyRequestHelper() {
ProxyRequestHelper helper = new ProxyRequestHelper();
if (this.traces != null) {
helper.setTraces(this.traces);
}
helper.setIgnoredHeaders(this.zuulProperties.getIgnoredHeaders());
helper.setTraceRequestBody(this.zuulProperties.isTraceRequestBody());
return helper;
}
@Bean
public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {
return new ZuulDiscoveryRefreshListener();
@@ -130,15 +118,42 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
return new SimpleServiceRouteMapper();
}
@Configuration
@ConditionalOnMissingClass("org.springframework.boot.actuate.endpoint.Endpoint")
protected static class NoActuatorConfiguration {
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
ProxyRequestHelper helper = new ProxyRequestHelper();
helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders());
helper.setTraceRequestBody(zuulProperties.isTraceRequestBody());
return helper;
}
}
@Configuration
@ConditionalOnClass(Endpoint.class)
protected static class RoutesEndpointConfiguration {
@Autowired(required = false)
private TraceRepository traces;
@Bean
public RoutesEndpoint zuulEndpoint(RouteLocator routeLocator) {
return new RoutesEndpoint(routeLocator);
}
@Bean
public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
TraceProxyRequestHelper helper = new TraceProxyRequestHelper();
if (this.traces != null) {
helper.setTraces(this.traces);
}
helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders());
helper.setTraceRequestBody(zuulProperties.isTraceRequestBody());
return helper;
}
}
private static class ZuulDiscoveryRefreshListener
@@ -16,10 +16,11 @@
package org.springframework.cloud.netflix.zuul.filters;
import static org.springframework.http.HttpHeaders.CONTENT_ENCODING;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
@@ -33,7 +34,6 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.cloud.netflix.zuul.util.RequestUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
@@ -45,14 +45,12 @@ import org.springframework.web.util.WebUtils;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.util.HTTPRequestUtils;
import static org.springframework.http.HttpHeaders.CONTENT_ENCODING;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
import lombok.extern.apachecommons.CommonsLog;
/**
* @author Dave Syer
* @author Marcos Barbero
* @author Spencer Gibb
*/
@CommonsLog
public class ProxyRequestHelper {
@@ -63,8 +61,6 @@ public class ProxyRequestHelper {
*/
public static final String IGNORED_HEADERS = "ignoredHeaders";
private TraceRepository traces;
private Set<String> ignoredHeaders = new LinkedHashSet<>();
private Set<String> sensitiveHeaders = new LinkedHashSet<>();
@@ -85,10 +81,6 @@ public class ProxyRequestHelper {
this.ignoredHeaders.addAll(ignoredHeaders);
}
public void setTraces(TraceRepository traces) {
this.traces = traces;
}
public void setTraceRequestBody(boolean traceRequestBody) {
this.traceRequestBody = traceRequestBody;
}
@@ -238,39 +230,10 @@ public class ProxyRequestHelper {
MultiValueMap<String, String> headers, MultiValueMap<String, String> params,
InputStream requestEntity) throws IOException {
Map<String, Object> info = new LinkedHashMap<>();
if (this.traces != null) {
RequestContext context = RequestContext.getCurrentContext();
info.put("method", verb);
info.put("path", uri);
info.put("query", getQueryString(params));
info.put("remote", true);
info.put("proxy", context.get("proxy"));
Map<String, Object> trace = new LinkedHashMap<>();
Map<String, Object> input = new LinkedHashMap<>();
trace.put("request", input);
info.put("headers", trace);
for (Entry<String, List<String>> entry : headers.entrySet()) {
Collection<String> collection = entry.getValue();
Object value = collection;
if (collection.size() < 2) {
value = collection.isEmpty() ? "" : collection.iterator().next();
}
input.put(entry.getKey(), value);
}
RequestContext ctx = RequestContext.getCurrentContext();
if (shouldDebugBody(ctx)) {
// Prevent input stream from being read if it needs to go downstream
if (requestEntity != null) {
debugRequestEntity(info, ctx.getRequest().getInputStream());
}
}
this.traces.add(info);
return info;
}
return info;
}
/* for tests */ boolean shouldDebugBody(RequestContext ctx) {
protected boolean shouldDebugBody(RequestContext ctx) {
HttpServletRequest request = ctx.getRequest();
if (!this.traceRequestBody || ctx.isChunkedRequestBody()
|| RequestUtils.isZuulServletRequest()) {
@@ -284,36 +247,6 @@ public class ProxyRequestHelper {
public void appendDebug(Map<String, Object> info, int status,
MultiValueMap<String, String> headers) {
if (this.traces != null) {
@SuppressWarnings("unchecked")
Map<String, Object> trace = (Map<String, Object>) info.get("headers");
Map<String, Object> output = new LinkedHashMap<String, Object>();
trace.put("response", output);
for (Entry<String, List<String>> key : headers.entrySet()) {
Collection<String> collection = key.getValue();
Object value = collection;
if (collection.size() < 2) {
value = collection.isEmpty() ? "" : collection.iterator().next();
}
output.put(key.getKey(), value);
}
output.put("status", "" + status);
}
}
private void debugRequestEntity(Map<String, Object> info, InputStream inputStream)
throws IOException {
if (RequestContext.getCurrentContext().isChunkedRequestBody()) {
info.put("body", "<chunked>");
return;
}
char[] buffer = new char[4096];
int count = new InputStreamReader(inputStream, Charset.forName("UTF-8"))
.read(buffer, 0, buffer.length);
if (count > 0) {
String entity = new String(buffer).substring(0, count);
info.put("body", entity.length() < 4096 ? entity : entity + "<truncated>");
}
}
public String getQueryString(MultiValueMap<String, String> params) {
@@ -0,0 +1,117 @@
/*
* Copyright 2013-2016 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.util.MultiValueMap;
import com.netflix.zuul.context.RequestContext;
import lombok.extern.apachecommons.CommonsLog;
/**
* @author Spencer Gibb
*/
@CommonsLog
public class TraceProxyRequestHelper extends ProxyRequestHelper {
private TraceRepository traces;
public void setTraces(TraceRepository traces) {
this.traces = traces;
}
@Override
public Map<String, Object> debug(String verb, String uri,
MultiValueMap<String, String> headers, MultiValueMap<String, String> params,
InputStream requestEntity) throws IOException {
Map<String, Object> info = new LinkedHashMap<>();
if (this.traces != null) {
RequestContext context = RequestContext.getCurrentContext();
info.put("method", verb);
info.put("path", uri);
info.put("query", getQueryString(params));
info.put("remote", true);
info.put("proxy", context.get("proxy"));
Map<String, Object> trace = new LinkedHashMap<>();
Map<String, Object> input = new LinkedHashMap<>();
trace.put("request", input);
info.put("headers", trace);
debugHeaders(headers, input);
RequestContext ctx = RequestContext.getCurrentContext();
if (shouldDebugBody(ctx)) {
// Prevent input stream from being read if it needs to go downstream
if (requestEntity != null) {
debugRequestEntity(info, ctx.getRequest().getInputStream());
}
}
this.traces.add(info);
return info;
}
return info;
}
void debugHeaders(MultiValueMap<String, String> headers, Map<String, Object> map) {
for (Entry<String, List<String>> entry : headers.entrySet()) {
Collection<String> collection = entry.getValue();
Object value = collection;
if (collection.size() < 2) {
value = collection.isEmpty() ? "" : collection.iterator().next();
}
map.put(entry.getKey(), value);
}
}
public void appendDebug(Map<String, Object> info, int status,
MultiValueMap<String, String> headers) {
if (this.traces != null) {
@SuppressWarnings("unchecked")
Map<String, Object> trace = (Map<String, Object>) info.get("headers");
Map<String, Object> output = new LinkedHashMap<String, Object>();
trace.put("response", output);
debugHeaders(headers, output);
output.put("status", "" + status);
}
}
private void debugRequestEntity(Map<String, Object> info, InputStream inputStream)
throws IOException {
if (RequestContext.getCurrentContext().isChunkedRequestBody()) {
info.put("body", "<chunked>");
return;
}
char[] buffer = new char[4096];
int count = new InputStreamReader(inputStream, Charset.forName("UTF-8"))
.read(buffer, 0, buffer.length);
if (count > 0) {
String entity = new String(buffer).substring(0, count);
info.put("body", entity.length() < 4096 ? entity : entity + "<truncated>");
}
}
}
@@ -111,7 +111,7 @@ public class ZuulProperties {
private boolean traceRequestBody = true;
/**
* Flag to say that path elelents past the first semicolon can be dropped.
* Flag to say that path elements past the first semicolon can be dropped.
*/
private boolean removeSemicolonContent = true;
@@ -125,6 +125,12 @@ public class ZuulProperties {
private Set<String> sensitiveHeaders = new LinkedHashSet<>(
Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
/**
* Flag to say whether the hostname for ssl connections should be verified or not. Default is true.
* This should only be used in test setups!
*/
private boolean sslHostnameValidationEnabled =true;
public Set<String> getIgnoredHeaders() {
Set<String> ignoredHeaders = new LinkedHashSet<>(this.ignoredHeaders);
if (ClassUtils.isPresent(
@@ -53,11 +53,13 @@ import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
@@ -66,7 +68,6 @@ import org.apache.http.protocol.HttpContext;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.Host;
import org.springframework.http.HttpStatus;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@@ -94,6 +95,8 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
private final Timer connectionManagerTimer = new Timer(
"SimpleHostRoutingFilter.connectionManagerTimer", true);
private boolean sslHostnameValidationEnabled;
private ProxyRequestHelper helper;
private Host hostProperties;
private PoolingHttpClientConnectionManager connectionManager;
@@ -115,6 +118,7 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
public SimpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties) {
this.helper = helper;
this.hostProperties = properties.getHost();
this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled();
}
@PostConstruct
@@ -204,11 +208,18 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
}
} }, new SecureRandom());
final Registry<ConnectionSocketFactory> registry = RegistryBuilder
RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder
.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", new SSLConnectionSocketFactory(sslContext))
.build();
.register("http", PlainConnectionSocketFactory.INSTANCE);
if (sslHostnameValidationEnabled) {
registryBuilder.register("https",
new SSLConnectionSocketFactory(sslContext));
}
else {
registryBuilder.register("https", new SSLConnectionSocketFactory(
sslContext, NoopHostnameVerifier.INSTANCE));
}
final Registry<ConnectionSocketFactory> registry = registryBuilder.build();
this.connectionManager = new PoolingHttpClientConnectionManager(registry);
this.connectionManager
@@ -228,7 +239,11 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
.setConnectTimeout(CONNECTION_TIMEOUT.get())
.setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
return HttpClients.custom().setConnectionManager(newConnectionManager())
HttpClientBuilder httpClientBuilder = HttpClients.custom();
if (!sslHostnameValidationEnabled) {
httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
}
return httpClientBuilder.setConnectionManager(newConnectionManager())
.setDefaultRequestConfig(requestConfig)
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
.setRedirectStrategy(new RedirectStrategy() {
@@ -363,4 +378,11 @@ public class SimpleHostRoutingFilter extends ZuulFilter {
this.helper.addIgnoredHeaders(names);
}
/**
* Determines whether the filter enables the validation for ssl hostnames.
* @return
*/
boolean isSslHostnameValidationEnabled() {
return sslHostnameValidationEnabled;
}
}
@@ -35,6 +35,7 @@ import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.zuul.constants.ZuulConstants;
import com.netflix.zuul.context.RequestContext;
import org.springframework.util.StringUtils;
/**
* @author Christian Lohmann
@@ -100,10 +101,15 @@ public class HttpClientRibbonCommand extends HystrixCommand<ClientHttpResponse>
protected ClientHttpResponse forward() throws Exception {
final RequestContext context = RequestContext.getCurrentContext();
Long contentLength = null;
String contentLengthHeader = context.getRequest().getHeader("Content-Length");
if (StringUtils.hasText(contentLengthHeader)) {
contentLength = new Long(contentLengthHeader);
}
URI uriInstance = new URI(this.uri);
RibbonApacheHttpRequest request = new RibbonApacheHttpRequest(this.method,
uriInstance, this.retryable, this.headers, this.params,
this.requestEntity);
this.requestEntity, contentLength);
final RibbonApacheHttpResponse response = this.client
.executeWithLoadBalancer(request);
context.set("ribbonResponse", response);
@@ -39,6 +39,50 @@ public class FeignClientValidationTests {
@Rule
public ExpectedException expected = ExpectedException.none();
@Test
public void testNameAndValue() {
this.expected.expectMessage("only one is permitted");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NameAndValueConfiguration.class);
assertNotNull(context.getBean(NameAndValueConfiguration.Client.class));
context.close();
}
@Configuration
@Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = NameAndValueConfiguration.Client.class)
protected static class NameAndValueConfiguration {
@FeignClient(value = "foo", name = "bar")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
}
@Test
public void testServiceIdAndValue() {
this.expected.expectMessage("only one is permitted");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NameAndValueConfiguration.class);
assertNotNull(context.getBean(NameAndServiceIdConfiguration.Client.class));
context.close();
}
@Configuration
@Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = NameAndServiceIdConfiguration.Client.class)
protected static class NameAndServiceIdConfiguration {
@FeignClient(serviceId = "foo", name = "bar")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
}
@Test
public void testNotLegalHostname() {
this.expected.expectMessage("not legal hostname (foo_bar)");
@@ -115,7 +159,8 @@ public class FeignClientValidationTests {
return new Dummy();
}
class Dummy { }
class Dummy {
}
}
}
@@ -20,7 +20,6 @@ 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.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@@ -40,6 +39,9 @@ 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)
@SpringApplicationConfiguration(classes = RestTemplateRetryTest.Application.class)
@WebIntegrationTest(randomPort = true, value = {
@@ -77,13 +79,13 @@ public class RestTemplateRetryTest {
@RequestMapping(method = RequestMethod.GET, value = "/good")
public int good() {
int lValue = hits.getAndIncrement();
int lValue = this.hits.getAndIncrement();
return lValue;
}
@RequestMapping(method = RequestMethod.GET, value = "/timeout")
public int timeout() throws Exception {
int lValue = retryHits.getAndIncrement();
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) {
@@ -107,7 +109,7 @@ public class RestTemplateRetryTest {
@Before
public void setup() throws Exception {
// Force Ribbon configuration by making one call.
testClient.getForObject("http://badClients/ping", Integer.class);
this.testClient.getForObject("http://badClients/ping", Integer.class);
}
@Test
@@ -129,7 +131,7 @@ public class RestTemplateRetryTest {
// A null pointer should NOT trigger a circuit breaker.
for (int index = 0; index < 10; index++) {
try {
testClient.getForObject("http://badClients/null", Integer.class);
this.testClient.getForObject("http://badClients/null", Integer.class);
}
catch (Exception exception) {
}
@@ -138,9 +140,9 @@ public class RestTemplateRetryTest {
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
Assert.isTrue(badServer1Stats.isCircuitBreakerTripped());
Assert.isTrue(badServer2Stats.isCircuitBreakerTripped());
Assert.isTrue(goodServerStats.getTotalRequestsCount() == targetConnectionCount);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertEquals(targetConnectionCount, goodServerStats.getTotalRequestsCount());
// Wait for any timeout thread to finish.
@@ -176,17 +178,17 @@ public class RestTemplateRetryTest {
int hits = 0;
for (int index = 0; index < 20; index++) {
hits = testClient.getForObject("http://badClients/good", Integer.class);
hits = this.testClient.getForObject("http://badClients/good", Integer.class);
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
Assert.isTrue(badServer1Stats.isCircuitBreakerTripped());
Assert.isTrue(badServer2Stats.isCircuitBreakerTripped());
Assert.isTrue(goodServerStats.getTotalRequestsCount() == targetConnectionCount);
Assert.isTrue(hits == 20);
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertEquals(targetConnectionCount, goodServerStats.getTotalRequestsCount());
assertEquals(20, hits);
System.out.println("Retry Hits: " + hits);
}
@@ -204,24 +206,25 @@ public class RestTemplateRetryTest {
badServer1Stats.clearSuccessiveConnectionFailureCount();
badServer2Stats.clearSuccessiveConnectionFailureCount();
Assert.isTrue(!badServer1Stats.isCircuitBreakerTripped());
Assert.isTrue(!badServer2Stats.isCircuitBreakerTripped());
assertTrue(!badServer1Stats.isCircuitBreakerTripped());
assertTrue(!badServer2Stats.isCircuitBreakerTripped());
int hits = 0;
for (int index = 0; index < 15; index++) {
hits = testClient.getForObject("http://badClients/timeout", Integer.class);
hits = this.testClient.getForObject("http://badClients/timeout",
Integer.class);
}
logServerStats(LocalBadClientConfiguration.badServer);
logServerStats(LocalBadClientConfiguration.badServer2);
logServerStats(LocalBadClientConfiguration.goodServer);
Assert.isTrue(badServer1Stats.isCircuitBreakerTripped());
Assert.isTrue(badServer2Stats.isCircuitBreakerTripped());
Assert.isTrue(!goodServerStats.isCircuitBreakerTripped());
assertTrue(badServer1Stats.isCircuitBreakerTripped());
assertTrue(badServer2Stats.isCircuitBreakerTripped());
assertTrue(!goodServerStats.isCircuitBreakerTripped());
// 15 + 4 timeouts. See the endpoint for timeout conditions.
Assert.isTrue(hits == 19);
assertEquals(19, hits);
// Wait for any timeout thread to finish.
Thread.sleep(600);
@@ -266,12 +269,8 @@ class LocalBadClientConfiguration {
badServer = new Server("mybadhost", 10001);
badServer2 = new Server("localhost", -1);
balancer = LoadBalancerBuilder
.newBuilder()
.withClientConfig(config)
.withRule(rule)
.withPing(ping)
.buildFixedServerListLoadBalancer(
balancer = LoadBalancerBuilder.newBuilder().withClientConfig(config)
.withRule(rule).withPing(ping).buildFixedServerListLoadBalancer(
Arrays.asList(badServer, badServer2, goodServer));
return balancer;
}
@@ -283,8 +282,8 @@ class LocalBadClientConfiguration {
static class OverrideRetryHandler extends HttpClientLoadBalancerErrorHandler {
public OverrideRetryHandler() {
circuitRelated.add(UnknownHostException.class);
retriable.add(UnknownHostException.class);
this.circuitRelated.add(UnknownHostException.class);
this.retriable.add(UnknownHostException.class);
}
}
@@ -112,7 +112,7 @@ public class RibbonLoadBalancerClientTests {
public void testReconstructUriWithSecureClientConfig() {
RibbonServer server = getRibbonServer();
IClientConfig config = mock(IClientConfig.class);
when(config.get(CommonClientConfigKey.IsSecure, false)).thenReturn(true);
when(config.get(CommonClientConfigKey.IsSecure)).thenReturn(true);
when(clientFactory.getClientConfig(server.getServiceId())).thenReturn(config);
RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server);
@@ -124,6 +124,33 @@ public class RibbonLoadBalancerClientTests {
assertEquals("https", uri.getScheme());
}
@Test
@SneakyThrows
public void testReconstructSecureUriWithoutScheme() {
testReconstructSchemelessUriWithoutClientConfig(getSecureRibbonServer(), "https");
}
@Test
@SneakyThrows
public void testReconstructUnsecureSchemelessUri() {
testReconstructSchemelessUriWithoutClientConfig(getRibbonServer(), "http");
}
@SneakyThrows
public void testReconstructSchemelessUriWithoutClientConfig(RibbonServer server, String expectedScheme) {
IClientConfig config = mock(IClientConfig.class);
when(config.get(CommonClientConfigKey.IsSecure)).thenReturn(null);
when(clientFactory.getClientConfig(server.getServiceId())).thenReturn(config);
RibbonLoadBalancerClient client = getRibbonLoadBalancerClient(server);
ServiceInstance serviceInstance = client.choose(server.getServiceId());
URI uri = client.reconstructURI(serviceInstance,
new URI("//" + server.getServiceId()));
assertEquals(server.getHost(), uri.getHost());
assertEquals(server.getPort(), uri.getPort());
assertEquals(expectedScheme, uri.getScheme());
}
@Test
public void testChoose() {
RibbonServer server = getRibbonServer();
@@ -206,6 +233,11 @@ public class RibbonLoadBalancerClientTests {
Collections.singletonMap("mykey", "myvalue"));
}
protected RibbonServer getSecureRibbonServer() {
return new RibbonServer("testService", new Server("myhost", 8443), false,
Collections.singletonMap("mykey", "myvalue"));
}
protected void verifyServerStats() {
verify(this.serverStats).incrementActiveRequestsCount();
verify(this.serverStats).decrementActiveRequestsCount();
@@ -0,0 +1,109 @@
/*
* Copyright 2013-2016 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.ribbon.apache;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpUriRequest;
import org.junit.Test;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.StreamUtils;
/**
* @author Spencer Gibb
*/
public class RibbonApacheHttpRequestTests {
@Test
public void testNullEntity() throws Exception {
URI uri = URI.create("http://example.com");
LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("my-header", "my-value");
LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("myparam", "myparamval");
RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest("GET", uri, false,
headers, params, null);
HttpUriRequest request = httpRequest.toRequest(RequestConfig.custom().build());
assertThat("request is wrong type", request, is(not(instanceOf(HttpEntityEnclosingRequest.class))));
assertThat("uri is wrong", request.getURI().toString(), startsWith(uri.toString()));
assertThat("my-header is missing", request.getFirstHeader("my-header"), is(notNullValue()));
assertThat("my-header is wrong", request.getFirstHeader("my-header").getValue(), is(equalTo("my-value")));
assertThat("myparam is missing", request.getURI().getQuery(), is(equalTo("myparam=myparamval")));
}
@Test
// this situation happens, see https://github.com/spring-cloud/spring-cloud-netflix/issues/1042#issuecomment-227723877
public void testEmptyEntityGet() throws Exception {
String entityValue = "";
testEntity(entityValue, new ByteArrayInputStream(entityValue.getBytes()), false, "GET");
}
@Test
public void testNonEmptyEntityPost() throws Exception {
String entityValue = "abcd";
testEntity(entityValue, new ByteArrayInputStream(entityValue.getBytes()), true, "POST");
}
void testEntity(String entityValue, ByteArrayInputStream requestEntity, boolean addContentLengthHeader, String method) throws IOException {
String lengthString = String.valueOf(entityValue.length());
Long length = null;
URI uri = URI.create("http://example.com");
LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
if (addContentLengthHeader) {
headers.add("Content-Length", lengthString);
length = (long) entityValue.length();
}
RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest(method, uri, false,
headers, new LinkedMultiValueMap<String, String>(), requestEntity,
length);
HttpUriRequest request = httpRequest.toRequest(RequestConfig.custom().build());
assertThat("request is wrong type", request, is(instanceOf(HttpEntityEnclosingRequest.class)));
assertThat("uri is wrong", request.getURI().toString(), startsWith(uri.toString()));
if (addContentLengthHeader) {
assertThat("Content-Length is missing", request.getFirstHeader("Content-Length"), is(notNullValue()));
assertThat("Content-Length is wrong", request.getFirstHeader("Content-Length").getValue(),
is(equalTo(lengthString)));
}
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
assertThat("entity is missing", entityRequest.getEntity(), is(notNullValue()));
HttpEntity entity = entityRequest.getEntity();
assertThat("contentLength is wrong", entity.getContentLength(), is(equalTo((long)entityValue.length())));
assertThat("content is missing", entity.getContent(), is(notNullValue()));
String string = StreamUtils.copyToString(entity.getContent(), Charset.forName("UTF-8"));
assertThat("content is wrong", string, is(equalTo(entityValue)));
}
}
@@ -16,9 +16,15 @@
package org.springframework.cloud.netflix.zuul;
import com.netflix.client.RetryHandler;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
@@ -28,6 +34,8 @@ import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.RibbonClients;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory;
import org.springframework.context.annotation.Bean;
@@ -43,15 +51,23 @@ import org.springframework.web.bind.annotation.RequestBody;
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.util.WebUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.http.HttpHeaders.SET_COOKIE;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleHttpClientZuulProxyApplication.class)
@WebIntegrationTest(randomPort = true, value = {
"zuul.routes.other: /test/**=http://localhost:7777/local",
"zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" })
"zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**",
"zuul.routes.singleton: /singleton/**", "zuul.routes.singleton.sensitiveHeaders: "})
@DirtiesContext
public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase {
@@ -99,6 +115,33 @@ public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase {
assertEquals("Patched 1!", result.getBody());
}
@Test
public void ribbonLoadBalancingHttpClientCookiePolicy() {
ResponseEntity<String> result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/simple/downstream_cookie", HttpMethod.POST,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("Cookie 434354454!", result.getBody());
assertNull(result.getHeaders().getFirst(SET_COOKIE));
// if new instance of RibbonLoadBalancingHttpClient is getting created every time
// and HttpClient is not reused then there are no concerns for the shared cookie storage
// but since https://github.com/spring-cloud/spring-cloud-netflix/issues/1150 is on the way a
result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/singleton/downstream_cookie", HttpMethod.POST,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("Cookie 434354454!", result.getBody());
assertEquals("jsessionid=434354454", result.getHeaders().getFirst(SET_COOKIE));
result = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/singleton/downstream_cookie", HttpMethod.GET,
new HttpEntity<>((Void) null), String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("Cookie null!", result.getBody());
}
@Test
public void ribbonCommandFactoryOverridden() {
assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory",
@@ -107,6 +150,30 @@ public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase {
}
// Load balancer with fixed server list and defined ribbon rest client
@Configuration
class SingletonRibbonClientConfiguration {
@Value("${local.server.port}")
private int port;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
@Bean
public RibbonLoadBalancingHttpClient ribbonClient(IClientConfig config, ILoadBalancer loadBalancer, RetryHandler retryHandler)
{
final RibbonLoadBalancingHttpClient client = new RibbonLoadBalancingHttpClient();
client.setLoadBalancer(loadBalancer);
client.setRetryHandler(retryHandler);
client.initWithNiwsConfig(config);
return client;
}
}
// Don't use @SpringBootApplication because we don't want to component scan
@Configuration
@EnableAutoConfiguration
@@ -114,7 +181,8 @@ public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase {
@EnableZuulProxy
@RibbonClients({
@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class),
@RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) })
@RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class),
@RibbonClient(name = "singleton", configuration = SingletonRibbonClientConfiguration.class)})
class SampleHttpClientZuulProxyApplication extends ZuulProxyTestBase.AbstractZuulProxyApplication {
public static void main(final String[] args) {
@@ -126,6 +194,20 @@ class SampleHttpClientZuulProxyApplication extends ZuulProxyTestBase.AbstractZuu
return "Patched " + id + "!";
}
@RequestMapping(value = "/downstream_cookie", method = RequestMethod.POST)
public String setDownstreamCookie(HttpServletResponse response)
{
response.addCookie(new Cookie("jsessionid", "434354454"));
return "Cookie 434354454!";
}
@RequestMapping(value = "/downstream_cookie", method = RequestMethod.GET)
public String readDownstreamCookie(HttpServletRequest request)
{
final Cookie cookie = WebUtils.getCookie(request, "jsessionid");
return "Cookie " + cookie + "!";
}
@Bean
public RibbonCommandFactory<?> ribbonCommandFactory(
final SpringClientFactory clientFactory) {
@@ -72,7 +72,7 @@ public class ProxyRequestHelperTests {
request.addHeader("multiName", "multiValue2");
RequestContext.getCurrentContext().setRequest(request);
ProxyRequestHelper helper = new ProxyRequestHelper();
TraceProxyRequestHelper helper = new TraceProxyRequestHelper();
this.traceRepository = new InMemoryTraceRepository();
helper.setTraces(this.traceRepository);
@@ -172,7 +172,7 @@ public class ProxyRequestHelperTests {
request.addHeader("multiName", "multiValue1");
request.addHeader("multiName", "multiValue2");
ProxyRequestHelper helper = new ProxyRequestHelper();
TraceProxyRequestHelper helper = new TraceProxyRequestHelper();
helper.setTraces(this.traceRepository);
MultiValueMap<String, String> headers = helper.buildZuulRequestHeaders(request);
@@ -28,6 +28,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.boot.test.EnvironmentTestUtils.addEnvironment;
/**
@@ -54,6 +56,21 @@ public class SimpleHostRoutingFilterTests {
assertEquals(10, connMgr.getDefaultMaxPerRoute());
}
@Test
public void validateSslHostnamesByDefault() {
setupContext();
assertTrue("Hostname verification should be enabled by default",
getFilter().isSslHostnameValidationEnabled());
}
@Test
public void validationOfSslHostnamesCanBeDisabledViaProperty() {
addEnvironment(this.context, "zuul.sslHostnameValidationEnabled=false");
setupContext();
assertFalse("Hostname verification should be disabled via property",
getFilter().isSslHostnameValidationEnabled());
}
@Test
public void defaultPropertiesAreApplied() {
setupContext();
+5 -10
View File
@@ -9,16 +9,16 @@
<relativePath/>
</parent>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<packaging>pom</packaging>
<name>spring-cloud-netflix-dependencies</name>
<description>Spring Cloud Netflix Dependencies</description>
<properties>
<spring-cloud-commons.version>1.1.2.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-config.version>1.1.2.BUILD-SNAPSHOT</spring-cloud-config.version>
<spring-cloud-stream.version>1.0.3.BUILD-SNAPSHOT</spring-cloud-stream.version>
<spring-cloud-commons.version>1.1.1.RELEASE</spring-cloud-commons.version>
<spring-cloud-config.version>1.1.2.RELEASE</spring-cloud-config.version>
<spring-cloud-stream.version>1.0.2.RELEASE</spring-cloud-stream.version>
<archaius.version>0.7.4</archaius.version>
<eureka.version>1.4.8</eureka.version>
<eureka.version>1.4.10</eureka.version>
<feign.version>8.16.2</feign.version>
<hystrix.version>1.5.3</hystrix.version>
<ribbon.version>2.2.0</ribbon.version>
@@ -162,11 +162,6 @@
<artifactId>spring-cloud-netflix-turbine-stream</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-zuul-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-dependencies</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
@@ -11,7 +11,9 @@ import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
@@ -36,9 +38,13 @@ public class CloudJacksonJson extends LegacyJacksonJson {
protected final CloudJacksonCodec codec = new CloudJacksonCodec();
public CloudJacksonCodec getCodec() {
return codec;
}
@Override
public String codecName() {
return getCodecName(this.getClass());
return getCodecName(LegacyJacksonJson.class);
}
@Override
@@ -120,25 +126,26 @@ public class CloudJacksonJson extends LegacyJacksonJson {
static class CloudInstanceInfoSerializer extends InstanceInfoSerializer {
@Override
public void serialize(InstanceInfo info, JsonGenerator jgen,
public void serialize(final InstanceInfo info, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
if (info.getInstanceId() == null && info.getMetadata() != null) {
String instanceId = calculateInstanceId(info);
info = new InstanceInfo.Builder(info).setInstanceId(instanceId).build();
}
super.serialize(info, jgen, provider);
InstanceInfo updated = updateIfNeeded(info);
super.serialize(updated, jgen, provider);
}
}
private String calculateInstanceId(InstanceInfo info) {
static InstanceInfo updateIfNeeded(final InstanceInfo info) {
if (info.getInstanceId() == null && info.getMetadata() != null) {
String instanceId = info.getMetadata().get("instanceId");
String hostName = info.getHostName();
if (instanceId != null && StringUtils.hasText(hostName) && !instanceId.startsWith(hostName)) {
instanceId = hostName + ":" + instanceId;
if (StringUtils.hasText(instanceId)) {
// backwards compatibility for Angel
if (StringUtils.hasText(info.getHostName()) && !instanceId.startsWith(info.getHostName())) {
instanceId = info.getHostName()+":"+instanceId;
}
return new InstanceInfo.Builder(info).setInstanceId(instanceId).build();
}
return instanceId == null ? hostName : instanceId;
}
return info;
}
static class CloudInstanceInfoDeserializer extends InstanceInfoDeserializer {
@@ -146,5 +153,12 @@ public class CloudJacksonJson extends LegacyJacksonJson {
protected CloudInstanceInfoDeserializer(ObjectMapper mapper) {
super(mapper);
}
@Override
public InstanceInfo deserialize(JsonParser jp, DeserializationContext context) throws IOException {
InstanceInfo info = super.deserialize(jp, context);
InstanceInfo updated = updateIfNeeded(info);
return updated;
}
}
}
@@ -52,6 +52,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.converters.EurekaJacksonCodec;
import com.netflix.discovery.converters.wrappers.CodecWrapper;
import com.netflix.discovery.converters.wrappers.CodecWrappers;
import com.netflix.eureka.DefaultEurekaServerContext;
@@ -102,6 +103,7 @@ public class EurekaServerConfiguration extends WebMvcConfigurerAdapter {
@Value("${eureka.server.defaultOpenForTrafficCount:1}")
private int defaultOpenForTrafficCount;
public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();
@Bean
public HasFeatures eurekaServerFeature() {
@@ -128,15 +130,19 @@ public class EurekaServerConfiguration extends WebMvcConfigurerAdapter {
return new EurekaController(this.applicationInfoManager);
}
static {
CodecWrappers.registerWrapper(JACKSON_JSON);
EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
}
@Bean
public ServerCodecs serverCodecs() {
CodecWrappers.registerWrapper(new CloudJacksonJson());
return new CloudServerCodecs(this.eurekaServerConfig);
}
private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
return codec == null ? CodecWrappers.getCodec(CloudJacksonJson.class) : codec;
return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
}
private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-hystrix-amqp</artifactId>
@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<properties>
@@ -114,9 +114,6 @@
convertAvg(data, "errorPercentage", true);
convertAvg(data, "latencyExecute_mean", false);
convertAvg(data, "latencyTotal_mean", false);
// the following will break when it becomes a compound string if the property is dynamically changed
convertAvg(data, "propertyValue_metricsRollingStatisticalWindowInMilliseconds", false);
}
function convertAvg(data, key, decimal) {
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
@@ -23,11 +23,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.MessageChannel;
import org.springframework.scheduling.annotation.EnableScheduling;
import com.netflix.hystrix.HystrixCircuitBreaker;
@@ -57,6 +60,10 @@ public class HystrixStreamAutoConfiguration {
@Autowired
private HystrixStreamProperties properties;
@Autowired
@Output(HystrixStreamClient.OUTPUT)
private MessageChannel outboundChannel;
@Bean
public HasFeatures hystrixStreamQueueFeature() {
return HasFeatures.namedFeature("Hystrix Stream (Queue)",
@@ -87,8 +94,8 @@ public class HystrixStreamAutoConfiguration {
}
@Bean
public HystrixStreamTask hystrixStreamTask() {
return new HystrixStreamTask();
public HystrixStreamTask hystrixStreamTask(DiscoveryClient discoveryClient) {
return new HystrixStreamTask(this.outboundChannel, discoveryClient, this.properties);
}
}
@@ -28,14 +28,28 @@ import lombok.Data;
@Data
public class HystrixStreamProperties {
/** Flag to indicate that Hystrix Stream is enabled. Default is true. */
private boolean enabled = true;
/** Flag to indicate to prefix metric names with serviceId. Default is true. */
private boolean prefixMetricName = true;
/** Flag to indicate to send the id field in the metrics. Default is true */
private boolean sendId = true;
/** The destination of the stream. Destination as defined by Spring Cloud Stream. Defaults to springCloudHystrixStream */
private String destination = HystrixConstants.HYSTRIX_STREAM_DESTINATION;
/** The content type of the messages. Defaults to application/json */
private String contentType = "application/json";
/** How often (in ms) to send messages to the stream. Defaults to 500. */
private long sendRate = 500;
/** How often to put messages in the queue. This queue drains to the stream. Defaults to 500. */
private long gatherRate = 500;
/** The size of the metrics queue. This queue drains to the stream. Defaults to 1000. */
private int size = 1000;
}
@@ -23,10 +23,8 @@ import java.util.Collection;
import java.util.concurrent.LinkedBlockingQueue;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.messaging.MessageChannel;
@@ -55,24 +53,26 @@ import lombok.extern.apachecommons.CommonsLog;
@CommonsLog
public class HystrixStreamTask implements ApplicationContextAware {
@Autowired
@Output(HystrixStreamClient.OUTPUT)
private MessageChannel outboundChannel;
@Autowired
private DiscoveryClient discoveryClient;
private HystrixStreamProperties properties;
private ApplicationContext context;
@Autowired
private HystrixStreamProperties properties;
// Visible for testing
final LinkedBlockingQueue<String> jsonMetrics = new LinkedBlockingQueue<>(
1000);
final LinkedBlockingQueue<String> jsonMetrics;
private final JsonFactory jsonFactory = new JsonFactory();
public HystrixStreamTask(MessageChannel outboundChannel, DiscoveryClient discoveryClient, HystrixStreamProperties properties) {
this.outboundChannel = outboundChannel;
this.discoveryClient = discoveryClient;
this.properties = properties;
this.jsonMetrics = new LinkedBlockingQueue<>(properties.getSize());
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
@@ -95,7 +95,7 @@ public class HystrixStreamTask implements ApplicationContextAware {
// TODO: remove the explicit content type when s-c-stream can handle that for us
this.outboundChannel.send(MessageBuilder.withPayload(json)
.setHeader(MessageHeaders.CONTENT_TYPE,
"application/json")
this.properties.getContentType())
.build());
}
catch (Exception ex) {
@@ -61,8 +61,9 @@ public class HystrixStreamTaskTests {
new HystrixPropertiesCommandDefault(hystrixCommandKey, HystrixCommandProperties.defaultSetter()));
given(this.discoveryClient.getLocalServiceInstance()).willReturn(this.serviceInstance);
this.hystrixStreamTask.setApplicationContext(this.context);
this.hystrixStreamTask.gatherMetrics();
assertThat(this.hystrixStreamTask.jsonMetrics.isEmpty(), is(false));
}
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-spectator</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-turbine-stream</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-turbine</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-archaius</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-atlas</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-eureka</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-feign</artifactId>
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-hystrix</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-ribbon</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-spectator</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-turbine-stream</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-turbine</artifactId>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>1.1.3.BUILD-SNAPSHOT</version>
<version>1.1.5.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-starter-zuul</artifactId>