mirror of
https://github.com/spring-cloud/spring-cloud-netflix.git
synced 2026-09-19 16:49:01 +00:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
766f8bbab7 | ||
|
|
75dcd309e9 | ||
|
|
c7eb5f570d | ||
|
|
2179675027 | ||
|
|
fd159673c4 | ||
|
|
5d52c61c7c | ||
|
|
76baf2b914 | ||
|
|
4f3b89d798 | ||
|
|
d35b660eed | ||
|
|
fa783ccedd | ||
|
|
0cb8bf671b | ||
|
|
10eb85b7f0 | ||
|
|
b42ccc5d44 | ||
|
|
931f058068 | ||
|
|
c1dd0a6b6d | ||
|
|
c57a0e323c | ||
|
|
e1dca45c10 | ||
|
|
a8282dd0bd | ||
|
|
9e3e749438 | ||
|
|
434305de19 | ||
|
|
a9c0fc1502 | ||
|
|
9dafae6c01 | ||
|
|
f538444dee | ||
|
|
99beb9d045 | ||
|
|
4cada7c8cc | ||
|
|
34dff916cf | ||
|
|
7e9faef2e7 | ||
|
|
476a5cb84c | ||
|
|
cfa4302965 | ||
|
|
5d6590da44 |
@@ -14,3 +14,4 @@ _site/
|
||||
*.ipr
|
||||
*.iws
|
||||
.factorypath
|
||||
.shelf
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+6
-4
@@ -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;
|
||||
|
||||
+4
-10
@@ -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) {
|
||||
|
||||
+4
-1
@@ -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);
|
||||
|
||||
+16
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
+29
-14
@@ -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
|
||||
|
||||
+5
-72
@@ -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) {
|
||||
|
||||
+117
@@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
-1
@@ -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(
|
||||
|
||||
+28
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -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);
|
||||
|
||||
+46
-1
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+27
-28
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+33
-1
@@ -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();
|
||||
|
||||
+109
@@ -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)));
|
||||
}
|
||||
}
|
||||
+84
-2
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+17
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+27
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
-3
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
+9
-2
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
+11
-11
@@ -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) {
|
||||
|
||||
+2
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user