Adds zuul okhttp support & zuul refactor.
Adds a OkHttpRibbonCommand and accomanying classes. They should be considered beta. Refactors zuul ribbon command classes and tests. fixes gh-1125
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.apache;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
@@ -24,9 +23,8 @@ import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.entity.BasicHttpEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.netflix.client.ClientRequest;
|
||||
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -34,62 +32,38 @@ import lombok.Getter;
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
@Getter
|
||||
public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable {
|
||||
public class RibbonApacheHttpRequest extends ContextAwareRequest implements Cloneable {
|
||||
|
||||
private final String method;
|
||||
private Long contentLength;
|
||||
|
||||
private final MultiValueMap<String, String> headers;
|
||||
|
||||
private final MultiValueMap<String, String> params;
|
||||
|
||||
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, Long contentLength) {
|
||||
|
||||
this.method = method;
|
||||
this.contentLength = contentLength;
|
||||
this.uri = uri;
|
||||
this.isRetriable = retryable;
|
||||
this.headers = headers;
|
||||
this.params = params;
|
||||
this.requestEntity = requestEntity;
|
||||
public RibbonApacheHttpRequest(RibbonCommandContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public HttpUriRequest toRequest(final RequestConfig requestConfig) {
|
||||
final RequestBuilder builder = RequestBuilder.create(this.method);
|
||||
final RequestBuilder builder = RequestBuilder.create(this.context.getMethod());
|
||||
builder.setUri(this.uri);
|
||||
for (final String name : this.headers.keySet()) {
|
||||
final List<String> values = this.headers.get(name);
|
||||
for (final String name : this.context.getHeaders().keySet()) {
|
||||
final List<String> values = this.context.getHeaders().get(name);
|
||||
for (final String value : values) {
|
||||
builder.addHeader(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
for (final String name : this.params.keySet()) {
|
||||
final List<String> values = this.params.get(name);
|
||||
for (final String name : this.context.getParams().keySet()) {
|
||||
final List<String> values = this.context.getParams().get(name);
|
||||
for (final String value : values) {
|
||||
builder.addParameter(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.requestEntity != null) {
|
||||
if (this.context.getRequestEntity() != null) {
|
||||
final BasicHttpEntity entity;
|
||||
entity = new BasicHttpEntity();
|
||||
entity.setContent(this.requestEntity);
|
||||
entity.setContent(this.context.getRequestEntity());
|
||||
// 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)) {
|
||||
if (this.context.getContentLength() != null) {
|
||||
entity.setContentLength(this.context.getContentLength());
|
||||
} else if ("GET".equals(this.context.getMethod())) {
|
||||
entity.setContentLength(0);
|
||||
}
|
||||
builder.setEntity(entity);
|
||||
@@ -99,9 +73,8 @@ public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public RibbonApacheHttpRequest withNewUri(final URI uri) {
|
||||
return new RibbonApacheHttpRequest(this.method, uri, this.isRetriable,
|
||||
this.headers, this.params, this.requestEntity);
|
||||
public RibbonApacheHttpRequest withNewUri(URI uri) {
|
||||
return new RibbonApacheHttpRequest(newContext(uri));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,13 +23,10 @@ import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
@@ -38,64 +35,15 @@ import com.netflix.loadbalancer.ILoadBalancer;
|
||||
*/
|
||||
public class RibbonLoadBalancingHttpClient
|
||||
extends
|
||||
AbstractLoadBalancerAwareClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
AbstractLoadBalancingClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
private final HttpClient delegate = HttpClientBuilder.create().build();
|
||||
|
||||
private int connectTimeout;
|
||||
|
||||
private int readTimeout;
|
||||
|
||||
private boolean secure;
|
||||
|
||||
private boolean followRedirects;
|
||||
|
||||
private boolean okToRetryOnAllOperations;
|
||||
|
||||
public RibbonLoadBalancingHttpClient() {
|
||||
super(null);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
super();
|
||||
}
|
||||
|
||||
public RibbonLoadBalancingHttpClient(final ILoadBalancer lb) {
|
||||
super(lb);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initWithNiwsConfig(IClientConfig clientConfig) {
|
||||
super.initWithNiwsConfig(clientConfig);
|
||||
this.connectTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT);
|
||||
this.readTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ReadTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_READ_TIMEOUT);
|
||||
this.secure = clientConfig.getPropertyAsBoolean(CommonClientConfigKey.IsSecure,
|
||||
false);
|
||||
this.followRedirects = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.FollowRedirects,
|
||||
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
|
||||
this.okToRetryOnAllOperations = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.OkToRetryOnAllOperations,
|
||||
DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
final RibbonApacheHttpRequest request, final IClientConfig requestConfig) {
|
||||
if (this.okToRetryOnAllOperations) {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
|
||||
if (!request.getMethod().equals("GET")) {
|
||||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
else {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,8 +77,4 @@ public class RibbonLoadBalancingHttpClient
|
||||
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
|
||||
}
|
||||
|
||||
private boolean isSecure(final IClientConfig config) {
|
||||
return (config != null) ? config.get(CommonClientConfigKey.IsSecure)
|
||||
: this.secure;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpLoadBalancingClient
|
||||
extends AbstractLoadBalancingClient<OkHttpRibbonRequest, OkHttpRibbonResponse> {
|
||||
private final OkHttpClient delegate = new OkHttpClient();
|
||||
|
||||
public OkHttpLoadBalancingClient() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OkHttpLoadBalancingClient(final ILoadBalancer lb) {
|
||||
super(lb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpRibbonResponse execute(OkHttpRibbonRequest ribbonRequest,
|
||||
final IClientConfig configOverride) throws Exception {
|
||||
boolean secure = isSecure(configOverride);
|
||||
|
||||
if (secure) {
|
||||
final URI secureUri = UriComponentsBuilder.fromUri(ribbonRequest.getUri())
|
||||
.scheme("https").build().toUri();
|
||||
ribbonRequest = ribbonRequest.withNewUri(secureUri);
|
||||
}
|
||||
|
||||
OkHttpClient httpClient = getOkHttpClient(configOverride, secure);
|
||||
|
||||
final Request request = ribbonRequest.toRequest();
|
||||
Response response = httpClient.newCall(request).execute();
|
||||
return new OkHttpRibbonResponse(response, ribbonRequest.getUri());
|
||||
}
|
||||
|
||||
OkHttpClient getOkHttpClient(IClientConfig configOverride, boolean secure) {
|
||||
OkHttpClient.Builder builder = this.delegate.newBuilder();
|
||||
if (configOverride != null) {
|
||||
builder.connectTimeout(configOverride.get(
|
||||
CommonClientConfigKey.ConnectTimeout, this.connectTimeout), TimeUnit.MILLISECONDS);
|
||||
builder.readTimeout(configOverride.get(
|
||||
CommonClientConfigKey.ReadTimeout, this.readTimeout), TimeUnit.MILLISECONDS);
|
||||
builder.followRedirects(configOverride.get(
|
||||
CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
if (secure) {
|
||||
builder.followSslRedirects(configOverride.get(
|
||||
CommonClientConfigKey.FollowRedirects, this.followRedirects));
|
||||
}
|
||||
}
|
||||
else {
|
||||
builder.connectTimeout(this.connectTimeout, TimeUnit.MILLISECONDS);
|
||||
builder.readTimeout(this.readTimeout, TimeUnit.MILLISECONDS);
|
||||
builder.followRedirects(this.followRedirects);
|
||||
if (secure) {
|
||||
builder.followSslRedirects(this.followRedirects);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
|
||||
import lombok.Getter;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.internal.http.HttpMethod;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Getter
|
||||
public class OkHttpRibbonRequest extends ContextAwareRequest implements Cloneable {
|
||||
|
||||
public OkHttpRibbonRequest(RibbonCommandContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public Request toRequest() {
|
||||
Headers.Builder headers = new Headers.Builder();
|
||||
for (String name : this.context.getHeaders().keySet()) {
|
||||
List<String> values = this.context.getHeaders().get(name);
|
||||
for (String value : values) {
|
||||
headers.add(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
HttpUrl.Builder url = HttpUrl.get(this.uri).newBuilder();
|
||||
for (String name : this.context.getParams().keySet()) {
|
||||
List<String> values = this.context.getParams().get(name);
|
||||
for (String value : values) {
|
||||
url.addQueryParameter(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
RequestBody requestBody = null;
|
||||
|
||||
if (this.context.getRequestEntity() != null && HttpMethod.permitsRequestBody(this.context.getMethod())) {
|
||||
MediaType mediaType = null;
|
||||
if (headers.get("Content-Type") != null) {
|
||||
mediaType = MediaType.parse(headers.get("Content-Type"));
|
||||
}
|
||||
requestBody = new InputStreamRequestBody(this.context.getRequestEntity(), mediaType, this.context.getContentLength());
|
||||
}
|
||||
|
||||
return new Request.Builder()
|
||||
.url(url.build())
|
||||
.headers(headers.build())
|
||||
.method(this.context.getMethod(), requestBody)
|
||||
.build();
|
||||
}
|
||||
|
||||
public OkHttpRibbonRequest withNewUri(final URI uri) {
|
||||
return new OkHttpRibbonRequest(newContext(uri));
|
||||
}
|
||||
|
||||
static class InputStreamRequestBody extends RequestBody {
|
||||
|
||||
private InputStream inputStream;
|
||||
private MediaType mediaType;
|
||||
private Long contentLength;
|
||||
|
||||
InputStreamRequestBody(InputStream inputStream, MediaType mediaType, Long contentLength) {
|
||||
this.inputStream = inputStream;
|
||||
this.mediaType = mediaType;
|
||||
this.contentLength = contentLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return mediaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
if (contentLength != null) {
|
||||
return contentLength;
|
||||
}
|
||||
try {
|
||||
return inputStream.available();
|
||||
} catch (IOException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
Source source = null;
|
||||
try {
|
||||
source = Okio.source(inputStream);
|
||||
sink.writeAll(source);
|
||||
} finally {
|
||||
if (source != null) {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.http.CaseInsensitiveMultiMap;
|
||||
import com.netflix.client.http.HttpHeaders;
|
||||
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpRibbonResponse implements com.netflix.client.http.HttpResponse {
|
||||
|
||||
private final ResponseBody body;
|
||||
private URI uri;
|
||||
private Response response;
|
||||
|
||||
public OkHttpRibbonResponse(Response response, URI uri) {
|
||||
Assert.notNull(response, "response can not be null");
|
||||
this.response = response;
|
||||
this.body = response.body();
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.response.code();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusLine() {
|
||||
return this.response.message();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPayload() throws ClientException {
|
||||
if (!hasPayload()) {
|
||||
return null;
|
||||
}
|
||||
return this.body.byteStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload() {
|
||||
return this.body != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return this.response.isSuccessful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestedURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Collection<String>> getHeaders() {
|
||||
final Map<String, Collection<String>> headers = new HashMap<>();
|
||||
for (Map.Entry<String,List<String>> entry : this.response.headers().toMultimap().entrySet()) {
|
||||
String name = entry.getKey();
|
||||
for (String value : entry.getValue()) {
|
||||
if (headers.containsKey(name)) {
|
||||
headers.get(name).add(value);
|
||||
} else {
|
||||
final List<String> values = new ArrayList<>();
|
||||
values.add(value);
|
||||
headers.put(name, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHttpHeaders() {
|
||||
final CaseInsensitiveMultiMap headers = new CaseInsensitiveMultiMap();
|
||||
for (Map.Entry<String,List<String>> entry : this.response.headers().toMultimap().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
headers.addHeader(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.response.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
if (this.body == null) {
|
||||
return null;
|
||||
}
|
||||
return this.body.byteStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEntity() {
|
||||
return hasPayload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEntity(Class<T> type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEntity(Type type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getEntity(TypeToken<T> type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.IResponse;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public abstract class AbstractLoadBalancingClient<S extends ContextAwareRequest, T extends IResponse> extends
|
||||
AbstractLoadBalancerAwareClient<S, T> {
|
||||
|
||||
protected int connectTimeout;
|
||||
|
||||
protected int readTimeout;
|
||||
|
||||
protected boolean secure;
|
||||
|
||||
protected boolean followRedirects;
|
||||
|
||||
protected boolean okToRetryOnAllOperations;
|
||||
|
||||
public AbstractLoadBalancingClient() {
|
||||
super(null);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
}
|
||||
|
||||
public AbstractLoadBalancingClient(final ILoadBalancer lb) {
|
||||
super(lb);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initWithNiwsConfig(IClientConfig clientConfig) {
|
||||
super.initWithNiwsConfig(clientConfig);
|
||||
this.connectTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ConnectTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_CONNECT_TIMEOUT);
|
||||
this.readTimeout = clientConfig.getPropertyAsInteger(
|
||||
CommonClientConfigKey.ReadTimeout,
|
||||
DefaultClientConfigImpl.DEFAULT_READ_TIMEOUT);
|
||||
this.secure = clientConfig.getPropertyAsBoolean(CommonClientConfigKey.IsSecure,
|
||||
false);
|
||||
this.followRedirects = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.FollowRedirects,
|
||||
DefaultClientConfigImpl.DEFAULT_FOLLOW_REDIRECTS);
|
||||
this.okToRetryOnAllOperations = clientConfig.getPropertyAsBoolean(
|
||||
CommonClientConfigKey.OkToRetryOnAllOperations,
|
||||
DefaultClientConfigImpl.DEFAULT_OK_TO_RETRY_ON_ALL_OPERATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
final S request, final IClientConfig requestConfig) {
|
||||
if (this.okToRetryOnAllOperations) {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
|
||||
if (!request.getContext().getMethod().equals("GET")) {
|
||||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
else {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isSecure(final IClientConfig config) {
|
||||
return (config != null) ? config.get(CommonClientConfigKey.IsSecure)
|
||||
: this.secure;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import com.netflix.client.ClientRequest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public abstract class ContextAwareRequest extends ClientRequest {
|
||||
protected final RibbonCommandContext context;
|
||||
|
||||
public ContextAwareRequest(RibbonCommandContext context) {
|
||||
this.context = context;
|
||||
this.uri = context.uri();
|
||||
this.isRetriable = context.getRetryable();
|
||||
}
|
||||
|
||||
public RibbonCommandContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
protected RibbonCommandContext newContext(URI uri) {
|
||||
RibbonCommandContext commandContext = new RibbonCommandContext(this.context.getServiceId(),
|
||||
this.context.getMethod(), uri.toString(), this.context.getRetryable(),
|
||||
this.context.getHeaders(), this.context.getParams(), this.context.getRequestEntity());
|
||||
commandContext.setContentLength(this.context.getContentLength());
|
||||
return commandContext;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientR
|
||||
import org.springframework.cloud.netflix.zuul.filters.discovery.ServiceRouteMapper;
|
||||
import org.springframework.cloud.netflix.zuul.filters.discovery.SimpleServiceRouteMapper;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.okhttp.OkHttpRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.restclient.RestClientRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
|
||||
@@ -95,6 +96,17 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty("zuul.ribbon.okhttp.enabled")
|
||||
@ConditionalOnClass(name = "okhttp3.OkHttpClient")
|
||||
protected static class OkHttpRibbonConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
return new OkHttpRibbonCommandFactory(clientFactory);
|
||||
}
|
||||
}
|
||||
|
||||
// pre filters
|
||||
@Bean
|
||||
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
|
||||
|
||||
@@ -249,14 +249,7 @@ public class ProxyRequestHelper {
|
||||
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);
|
||||
}
|
||||
transformHeaders(headers, input);
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
if (shouldDebugBody(ctx)) {
|
||||
// Prevent input stream from being read if it needs to go downstream
|
||||
@@ -287,20 +280,24 @@ public class ProxyRequestHelper {
|
||||
if (this.traces != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> trace = (Map<String, Object>) info.get("headers");
|
||||
Map<String, Object> output = new LinkedHashMap<String, Object>();
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
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);
|
||||
}
|
||||
transformHeaders(headers, output);
|
||||
output.put("status", "" + status);
|
||||
}
|
||||
}
|
||||
|
||||
void transformHeaders(MultiValueMap<String, String> headers, Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void debugRequestEntity(Map<String, Object> info, InputStream inputStream)
|
||||
throws IOException {
|
||||
if (RequestContext.getCurrentContext().isChunkedRequestBody()) {
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.client.http.HttpRequest.Builder;
|
||||
import com.netflix.client.http.HttpRequest.Verb;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import com.netflix.config.DynamicIntProperty;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import com.netflix.hystrix.HystrixCommandGroupKey;
|
||||
import com.netflix.hystrix.HystrixCommandKey;
|
||||
import com.netflix.hystrix.HystrixCommandProperties;
|
||||
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
import com.netflix.zuul.constants.ZuulConstants;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* Hystrix wrapper around Eureka Ribbon command
|
||||
*
|
||||
* see original
|
||||
* https://github.com/Netflix/zuul/blob/master/zuul-netflix/src/main/java/com/
|
||||
* netflix/zuul/dependency/ribbon/hystrix/RibbonCommand.java
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class RestClientRibbonCommand extends HystrixCommand<ClientHttpResponse> implements RibbonCommand {
|
||||
|
||||
private RestClient restClient;
|
||||
|
||||
private Verb verb;
|
||||
|
||||
private URI uri;
|
||||
|
||||
private Boolean retryable;
|
||||
|
||||
private MultiValueMap<String, String> headers;
|
||||
|
||||
private MultiValueMap<String, String> params;
|
||||
|
||||
private InputStream requestEntity;
|
||||
|
||||
public RestClientRibbonCommand(RestClient restClient, Verb verb, String uri,
|
||||
Boolean retryable,
|
||||
MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity)
|
||||
throws URISyntaxException {
|
||||
this("default", restClient, verb, uri, retryable , headers, params, requestEntity);
|
||||
}
|
||||
|
||||
public RestClientRibbonCommand(String commandKey, RestClient restClient, Verb verb, String uri,
|
||||
Boolean retryable,
|
||||
MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity)
|
||||
throws URISyntaxException {
|
||||
super(getSetter(commandKey));
|
||||
this.restClient = restClient;
|
||||
this.verb = verb;
|
||||
this.uri = new URI(uri);
|
||||
this.retryable = retryable;
|
||||
this.headers = headers;
|
||||
this.params = params;
|
||||
this.requestEntity = requestEntity;
|
||||
}
|
||||
|
||||
protected static HystrixCommand.Setter getSetter(String commandKey) {
|
||||
// we want to default to semaphore-isolation since this wraps
|
||||
// 2 others commands that are already thread isolated
|
||||
String name = ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores";
|
||||
DynamicIntProperty value = DynamicPropertyFactory.getInstance().getIntProperty(
|
||||
name, 100);
|
||||
HystrixCommandProperties.Setter setter = HystrixCommandProperties.Setter()
|
||||
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
|
||||
.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get());
|
||||
return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand"))
|
||||
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey))
|
||||
.andCommandPropertiesDefaults(setter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse run() throws Exception {
|
||||
return forward();
|
||||
}
|
||||
|
||||
protected ClientHttpResponse forward() throws Exception {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
Builder builder = HttpRequest.newBuilder().verb(this.verb).uri(this.uri)
|
||||
.entity(this.requestEntity);
|
||||
|
||||
if(this.retryable != null) {
|
||||
builder.setRetriable(this.retryable);
|
||||
}
|
||||
|
||||
for (String name : this.headers.keySet()) {
|
||||
List<String> values = this.headers.get(name);
|
||||
for (String value : values) {
|
||||
builder.header(name, value);
|
||||
}
|
||||
}
|
||||
for (String name : this.params.keySet()) {
|
||||
List<String> values = this.params.get(name);
|
||||
for (String value : values) {
|
||||
builder.queryParams(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
customizeRequest(builder);
|
||||
|
||||
HttpRequest httpClientRequest = builder.build();
|
||||
HttpResponse response = this.restClient
|
||||
.executeWithLoadBalancer(httpClientRequest);
|
||||
context.set("ribbonResponse", response);
|
||||
|
||||
// Explicitly close the HttpResponse if the Hystrix command timed out to
|
||||
// release the underlying HTTP connection held by the response.
|
||||
//
|
||||
if( this.isResponseTimedOut() ) {
|
||||
if( response!= null ) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
|
||||
RibbonHttpResponse ribbonHttpResponse = new RibbonHttpResponse(response);
|
||||
|
||||
return ribbonHttpResponse;
|
||||
}
|
||||
|
||||
protected void customizeRequest(Builder requestBuilder) {
|
||||
|
||||
}
|
||||
|
||||
protected MultiValueMap<String, String> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
protected MultiValueMap<String, String> getParams() {
|
||||
return this.params;
|
||||
}
|
||||
|
||||
protected InputStream getRequestEntity() {
|
||||
return this.requestEntity;
|
||||
}
|
||||
|
||||
protected RestClient getRestClient() {
|
||||
return this.restClient;
|
||||
}
|
||||
|
||||
protected Boolean getRetryable() {
|
||||
return this.retryable;
|
||||
}
|
||||
|
||||
protected URI getUri() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
protected Verb getVerb() {
|
||||
return this.verb;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RestClientRibbonCommandFactory implements RibbonCommandFactory<RestClientRibbonCommand> {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
public RestClientRibbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
@SneakyThrows
|
||||
public RestClientRibbonCommand create(RibbonCommandContext context) {
|
||||
RestClient restClient = this.clientFactory.getClient(context.getServiceId(),
|
||||
RestClient.class);
|
||||
return new RestClientRibbonCommand(
|
||||
context.getServiceId(), restClient, getVerb(context.getVerb()),
|
||||
context.getUri(), context.getRetryable(), context.getHeaders(),
|
||||
context.getParams(), context.getRequestEntity());
|
||||
}
|
||||
|
||||
protected SpringClientFactory getClientFactory() {
|
||||
return this.clientFactory;
|
||||
}
|
||||
|
||||
protected static HttpRequest.Verb getVerb(String sMethod) {
|
||||
if (sMethod == null)
|
||||
return HttpRequest.Verb.GET;
|
||||
try {
|
||||
return HttpRequest.Verb.valueOf(sMethod.toUpperCase());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return HttpRequest.Verb.GET;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,21 +16,35 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import lombok.Value;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Value
|
||||
@Data
|
||||
public class RibbonCommandContext {
|
||||
private final String serviceId;
|
||||
private final String verb;
|
||||
private final String method;
|
||||
private final String uri;
|
||||
private final Boolean retryable;
|
||||
private final MultiValueMap<String, String> headers;
|
||||
private final MultiValueMap<String, String> params;
|
||||
private final InputStream requestEntity;
|
||||
private Long contentLength;
|
||||
|
||||
public URI uri() {
|
||||
try {
|
||||
return new URI(this.uri);
|
||||
} catch (URISyntaxException e) {
|
||||
ReflectionUtils.rethrowRuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public class RibbonRoutingFilter extends ZuulFilter {
|
||||
}
|
||||
|
||||
protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
|
||||
Map<String, Object> info = this.helper.debug(context.getVerb(), context.getUri(),
|
||||
Map<String, Object> info = this.helper.debug(context.getMethod(), context.getUri(),
|
||||
context.getHeaders(), context.getParams(), context.getRequestEntity());
|
||||
|
||||
RibbonCommand command = this.ribbonCommandFactory.create(context);
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.apache;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpRequest;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpResponse;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.netflix.config.DynamicIntProperty;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import com.netflix.hystrix.HystrixCommandGroupKey;
|
||||
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
|
||||
*/
|
||||
public class HttpClientRibbonCommand extends HystrixCommand<ClientHttpResponse> implements
|
||||
RibbonCommand {
|
||||
|
||||
private final RibbonLoadBalancingHttpClient client;
|
||||
private final String method;
|
||||
private final String uri;
|
||||
private final MultiValueMap<String, String> headers;
|
||||
private final MultiValueMap<String, String> params;
|
||||
private final InputStream requestEntity;
|
||||
private final Boolean retryable;
|
||||
|
||||
public HttpClientRibbonCommand(final RibbonLoadBalancingHttpClient client,
|
||||
final String method, final String uri,
|
||||
final MultiValueMap<String, String> headers,
|
||||
final MultiValueMap<String, String> params, final InputStream requestEntity,
|
||||
final Boolean retryable) {
|
||||
this("default", client, method, uri, headers, params, requestEntity, retryable);
|
||||
}
|
||||
|
||||
public HttpClientRibbonCommand(final String commandKey,
|
||||
final RibbonLoadBalancingHttpClient client, final String method,
|
||||
final String uri, final MultiValueMap<String, String> headers,
|
||||
final MultiValueMap<String, String> params, final InputStream requestEntity,
|
||||
final Boolean retryable) {
|
||||
super(getSetter(commandKey));
|
||||
this.client = client;
|
||||
this.method = method;
|
||||
this.uri = uri;
|
||||
this.headers = headers;
|
||||
this.params = params;
|
||||
this.requestEntity = requestEntity;
|
||||
this.retryable = retryable;
|
||||
}
|
||||
|
||||
protected static Setter getSetter(final String commandKey) {
|
||||
|
||||
// we want to default to semaphore-isolation since this wraps
|
||||
// 2 others commands that are already thread isolated
|
||||
final String name = ZuulConstants.ZUUL_EUREKA + commandKey
|
||||
+ ".semaphore.maxSemaphores";
|
||||
final DynamicIntProperty value = DynamicPropertyFactory.getInstance()
|
||||
.getIntProperty(name, 100);
|
||||
final HystrixCommandProperties.Setter setter = HystrixCommandProperties
|
||||
.Setter()
|
||||
.withExecutionIsolationStrategy(
|
||||
HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
|
||||
.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get());
|
||||
return Setter
|
||||
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand"))
|
||||
.andCommandKey(
|
||||
HystrixCommandKey.Factory.asKey(commandKey + "RibbonCommand"))
|
||||
.andCommandPropertiesDefaults(setter);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse run() throws Exception {
|
||||
return forward();
|
||||
}
|
||||
|
||||
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, contentLength);
|
||||
final RibbonApacheHttpResponse response = this.client
|
||||
.executeWithLoadBalancer(request);
|
||||
context.set("ribbonResponse", response);
|
||||
|
||||
// Explicitly close the HttpResponse if the Hystrix command timed out to
|
||||
// release the underlying HTTP connection held by the response.
|
||||
//
|
||||
if (this.isResponseTimedOut()) {
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
|
||||
return new RibbonHttpResponse(response);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,19 +16,22 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.apache;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpRequest;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpResponse;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class HttpClientRibbonCommandFactory implements
|
||||
RibbonCommandFactory<HttpClientRibbonCommand> {
|
||||
RibbonCommandFactory<HttpClientRibbonCommandFactory.HttpClientRibbonCommand> {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
@@ -39,10 +42,20 @@ public class HttpClientRibbonCommandFactory implements
|
||||
serviceId, RibbonLoadBalancingHttpClient.class);
|
||||
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
|
||||
|
||||
final HttpClientRibbonCommand httpClientRibbonCommand = new HttpClientRibbonCommand(
|
||||
serviceId, client, context.getVerb(), context.getUri(),
|
||||
context.getHeaders(), context.getParams(), context.getRequestEntity(),
|
||||
context.getRetryable());
|
||||
return httpClientRibbonCommand;
|
||||
return new HttpClientRibbonCommand(serviceId, client, context);
|
||||
}
|
||||
|
||||
class HttpClientRibbonCommand extends AbstractRibbonCommand<RibbonLoadBalancingHttpClient, RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
|
||||
public HttpClientRibbonCommand(final String commandKey,
|
||||
final RibbonLoadBalancingHttpClient client, RibbonCommandContext context) {
|
||||
super(commandKey, client, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RibbonApacheHttpRequest createRequest() throws Exception {
|
||||
return new RibbonApacheHttpRequest(this.context);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.okhttp;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient;
|
||||
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonRequest;
|
||||
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonResponse;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class OkHttpRibbonCommandFactory implements
|
||||
RibbonCommandFactory<OkHttpRibbonCommandFactory.OkHttpRibbonCommand> {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
@Override
|
||||
public OkHttpRibbonCommand create(final RibbonCommandContext context) {
|
||||
final String serviceId = context.getServiceId();
|
||||
final OkHttpLoadBalancingClient client = this.clientFactory.getClient(
|
||||
serviceId, OkHttpLoadBalancingClient.class);
|
||||
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
|
||||
|
||||
return new OkHttpRibbonCommand(serviceId, client, context);
|
||||
}
|
||||
|
||||
class OkHttpRibbonCommand extends AbstractRibbonCommand<OkHttpLoadBalancingClient, OkHttpRibbonRequest, OkHttpRibbonResponse> {
|
||||
|
||||
public OkHttpRibbonCommand(final String commandKey,
|
||||
final OkHttpLoadBalancingClient client, RibbonCommandContext context) {
|
||||
super(commandKey, client, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OkHttpRibbonRequest createRequest() throws Exception {
|
||||
return new OkHttpRibbonRequest(this.context);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.route.restclient;
|
||||
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RestClientRibbonCommandFactory implements RibbonCommandFactory<RestClientRibbonCommandFactory.RestClientRibbonCommand> {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
public RestClientRibbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public RestClientRibbonCommand create(RibbonCommandContext context) {
|
||||
RestClient restClient = this.clientFactory.getClient(context.getServiceId(),
|
||||
RestClient.class);
|
||||
return new RestClientRibbonCommand(context.getServiceId(), restClient, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hystrix wrapper around Eureka Ribbon command
|
||||
*
|
||||
* see original
|
||||
* https://github.com/Netflix/zuul/blob/master/zuul-netflix/src/main/java/com/
|
||||
* netflix/zuul/dependency/ribbon/hystrix/RibbonCommand.java
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
static class RestClientRibbonCommand extends AbstractRibbonCommand<RestClient, HttpRequest, HttpResponse> {
|
||||
|
||||
public RestClientRibbonCommand(String commandKey, RestClient client, RibbonCommandContext context) {
|
||||
super(commandKey, client, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpRequest createRequest() throws Exception {
|
||||
HttpRequest.Builder builder = HttpRequest.newBuilder()
|
||||
.verb(getVerb(this.context.getMethod()))
|
||||
.uri(this.context.uri())
|
||||
.entity(this.context.getRequestEntity());
|
||||
|
||||
if(this.context.getRetryable() != null) {
|
||||
builder.setRetriable(this.context.getRetryable());
|
||||
}
|
||||
|
||||
for (String name : this.context.getHeaders().keySet()) {
|
||||
List<String> values = this.context.getHeaders().get(name);
|
||||
for (String value : values) {
|
||||
builder.header(name, value);
|
||||
}
|
||||
}
|
||||
for (String name : this.context.getParams().keySet()) {
|
||||
List<String> values = this.context.getParams().get(name);
|
||||
for (String value : values) {
|
||||
builder.queryParams(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
static HttpRequest.Verb getVerb(String method) {
|
||||
if (method == null)
|
||||
return HttpRequest.Verb.GET;
|
||||
try {
|
||||
return HttpRequest.Verb.valueOf(method.toUpperCase());
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return HttpRequest.Verb.GET;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.route.support;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.ClientRequest;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import com.netflix.config.DynamicIntProperty;
|
||||
import com.netflix.config.DynamicPropertyFactory;
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import com.netflix.hystrix.HystrixCommandGroupKey;
|
||||
import com.netflix.hystrix.HystrixCommandKey;
|
||||
import com.netflix.hystrix.HystrixCommandProperties;
|
||||
import com.netflix.zuul.constants.ZuulConstants;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwareClient<RQ, RS>, RQ extends ClientRequest, RS extends HttpResponse> extends HystrixCommand<ClientHttpResponse> implements
|
||||
RibbonCommand {
|
||||
|
||||
protected final LBC client;
|
||||
protected RibbonCommandContext context;
|
||||
|
||||
public AbstractRibbonCommand(LBC client, RibbonCommandContext context) {
|
||||
this("default", client, context);
|
||||
}
|
||||
|
||||
public AbstractRibbonCommand(String commandKey, LBC client, RibbonCommandContext context) {
|
||||
super(getSetter(commandKey));
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected static Setter getSetter(final String commandKey) {
|
||||
|
||||
// we want to default to semaphore-isolation since this wraps
|
||||
// 2 others commands that are already thread isolated
|
||||
// @formatter:off
|
||||
final String name = ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores";
|
||||
final DynamicIntProperty value = DynamicPropertyFactory.getInstance()
|
||||
.getIntProperty(name, 100);
|
||||
final HystrixCommandProperties.Setter setter = HystrixCommandProperties .Setter()
|
||||
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
|
||||
.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get());
|
||||
return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand"))
|
||||
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey + "RibbonCommand"))
|
||||
.andCommandPropertiesDefaults(setter);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse run() throws Exception {
|
||||
final RequestContext context = RequestContext.getCurrentContext();
|
||||
String contentLengthHeader = context.getRequest().getHeader("Content-Length");
|
||||
if (StringUtils.hasText(contentLengthHeader)) {
|
||||
this.context.setContentLength(new Long(contentLengthHeader));
|
||||
}
|
||||
|
||||
RQ request = createRequest();
|
||||
RS response = this.client.executeWithLoadBalancer(request);
|
||||
|
||||
context.set("ribbonResponse", response);
|
||||
|
||||
// Explicitly close the HttpResponse if the Hystrix command timed out to
|
||||
// release the underlying HTTP connection held by the response.
|
||||
//
|
||||
if (this.isResponseTimedOut()) {
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
|
||||
return new RibbonHttpResponse(response);
|
||||
}
|
||||
|
||||
protected abstract RQ createRequest() throws Exception;
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import org.junit.Ignore;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.restclient.RestClientRibbonCommandIntegrationTests;
|
||||
|
||||
/**
|
||||
* A test suite for probing weird ordering problems in the tests.
|
||||
@@ -29,7 +30,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses({
|
||||
org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelperTests.class,
|
||||
org.springframework.cloud.netflix.zuul.SampleZuulProxyApplicationTests.class,
|
||||
RestClientRibbonCommandIntegrationTests.class,
|
||||
org.springframework.cloud.netflix.zuul.FormZuulProxyApplicationTests.class })
|
||||
@Ignore
|
||||
public class AdhocTestSuite {
|
||||
|
||||
@@ -35,6 +35,7 @@ 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.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
@@ -45,18 +46,18 @@ public class RibbonApacheHttpRequestTests {
|
||||
|
||||
@Test
|
||||
public void testNullEntity() throws Exception {
|
||||
URI uri = URI.create("http://example.com");
|
||||
String uri = "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);
|
||||
RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest(new RibbonCommandContext("example", "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("uri is wrong", request.getURI().toString(), startsWith(uri));
|
||||
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")));
|
||||
@@ -84,9 +85,9 @@ public class RibbonApacheHttpRequestTests {
|
||||
headers.add("Content-Length", lengthString);
|
||||
length = (long) entityValue.length();
|
||||
}
|
||||
RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest(method, uri, false,
|
||||
headers, new LinkedMultiValueMap<String, String>(), requestEntity,
|
||||
length);
|
||||
RibbonCommandContext context = new RibbonCommandContext("example", method, uri.toString(), false, headers, new LinkedMultiValueMap<String, String>(), requestEntity);
|
||||
context.setContentLength(length);
|
||||
RibbonApacheHttpRequest httpRequest = new RibbonApacheHttpRequest(context);
|
||||
|
||||
HttpUriRequest request = httpRequest.toRequest(RequestConfig.custom().build());
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpLoadBalancingClientTests {
|
||||
|
||||
@Test
|
||||
public void testOkHttpClientUseDefaultsNoOverride() throws Exception {
|
||||
OkHttpClient result = getHttpClient(UseDefaults.class, null);
|
||||
|
||||
assertThat(result.followRedirects(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOkHttpClientDoNotFollowRedirectsNoOverride() throws Exception {
|
||||
OkHttpClient result = getHttpClient(DoNotFollowRedirects.class, null);
|
||||
|
||||
assertThat(result.followRedirects(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOkHttpClientFollowRedirectsNoOverride() throws Exception {
|
||||
OkHttpClient result = getHttpClient(FollowRedirects.class, null);
|
||||
|
||||
assertThat(result.followRedirects(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOkHttpClientDoNotFollowRedirectsOverrideWithFollowRedirects()
|
||||
throws Exception {
|
||||
|
||||
DefaultClientConfigImpl override = new DefaultClientConfigImpl();
|
||||
override.set(CommonClientConfigKey.FollowRedirects, true);
|
||||
override.set(CommonClientConfigKey.IsSecure, false);
|
||||
|
||||
OkHttpClient result = getHttpClient(DoNotFollowRedirects.class, override);
|
||||
|
||||
assertThat(result.followRedirects(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOkHttpClientFollowRedirectsOverrideWithDoNotFollowRedirects()
|
||||
throws Exception {
|
||||
|
||||
DefaultClientConfigImpl override = new DefaultClientConfigImpl();
|
||||
override.set(CommonClientConfigKey.FollowRedirects, false);
|
||||
override.set(CommonClientConfigKey.IsSecure, false);
|
||||
|
||||
OkHttpClient result = getHttpClient(FollowRedirects.class, override);
|
||||
|
||||
assertThat(result.followRedirects(), is(false));
|
||||
}
|
||||
|
||||
private OkHttpClient getHttpClient(Class<?> defaultConfigurationClass,
|
||||
IClientConfig configOverride) throws Exception {
|
||||
SpringClientFactory factory = new SpringClientFactory();
|
||||
factory.setApplicationContext(new AnnotationConfigApplicationContext(
|
||||
defaultConfigurationClass));
|
||||
|
||||
OkHttpLoadBalancingClient client = factory.getClient("service",
|
||||
OkHttpLoadBalancingClient.class);
|
||||
|
||||
return client.getOkHttpClient(configOverride, false);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class UseDefaults {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class FollowRedirects {
|
||||
@Bean
|
||||
public IClientConfig clientConfig() {
|
||||
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
|
||||
config.set(CommonClientConfigKey.FollowRedirects, true);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected static class DoNotFollowRedirects {
|
||||
@Bean
|
||||
public IClientConfig clientConfig() {
|
||||
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
|
||||
config.set(CommonClientConfigKey.FollowRedirects, false);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.okhttp;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okio.Buffer;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpRibbonRequestTests {
|
||||
|
||||
@Test
|
||||
public void testNullEntity() throws Exception {
|
||||
String uri = "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");
|
||||
RibbonCommandContext context = new RibbonCommandContext("example", "GET", uri, false, headers, params, null);
|
||||
OkHttpRibbonRequest httpRequest = new OkHttpRibbonRequest(context);
|
||||
|
||||
Request request = httpRequest.toRequest();
|
||||
|
||||
assertThat("body is not null", request.body(), is(nullValue()));
|
||||
assertThat("uri is wrong", request.url().toString(), startsWith(uri));
|
||||
assertThat("my-header is wrong", request.header("my-header"), is(equalTo("my-value")));
|
||||
assertThat("myparam is missing", request.url().queryParameter("myparam"), is(equalTo("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;
|
||||
String uri = "http://example.com";
|
||||
LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
|
||||
if (addContentLengthHeader) {
|
||||
headers.add("Content-Length", lengthString);
|
||||
length = (long) entityValue.length();
|
||||
}
|
||||
RibbonCommandContext context = new RibbonCommandContext("example", method, uri, false,
|
||||
headers, new LinkedMultiValueMap<String, String>(), requestEntity);
|
||||
context.setContentLength(length);
|
||||
OkHttpRibbonRequest httpRequest = new OkHttpRibbonRequest(context);
|
||||
|
||||
Request request = httpRequest.toRequest();
|
||||
|
||||
assertThat("uri is wrong", request.url().toString(), startsWith(uri));
|
||||
if (addContentLengthHeader) {
|
||||
assertThat("Content-Length is wrong", request.header("Content-Length"),
|
||||
is(equalTo(lengthString)));
|
||||
}
|
||||
|
||||
if (!method.equalsIgnoreCase("get")) {
|
||||
assertThat("body is null", request.body(), is(notNullValue()));
|
||||
RequestBody body = request.body();
|
||||
assertThat("contentLength is wrong", body.contentLength(), is(equalTo((long) entityValue.length())));
|
||||
Buffer content = new Buffer();
|
||||
body.writeTo(content);
|
||||
String string = content.readByteString().utf8();
|
||||
assertThat("content is wrong", string, is(equalTo(entityValue)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon.okhttp;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class OkHttpRibbonResponseTests {
|
||||
|
||||
@Test
|
||||
public void testNullEntity() throws Exception {
|
||||
URI uri = URI.create("http://example.com");
|
||||
Response response = response(uri).build();
|
||||
|
||||
OkHttpRibbonResponse httpResponse = new OkHttpRibbonResponse(response, uri);
|
||||
|
||||
assertThat(httpResponse.isSuccess(), is(true));
|
||||
assertThat(httpResponse.hasPayload(), is(false));
|
||||
assertThat(httpResponse.getPayload(), is(nullValue()));
|
||||
assertThat(httpResponse.getInputStream(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotNullEntity() throws Exception {
|
||||
URI uri = URI.create("http://example.com");
|
||||
Response response = response(uri)
|
||||
.body(ResponseBody.create(MediaType.parse("text/plain"), "abcd"))
|
||||
.build();
|
||||
|
||||
OkHttpRibbonResponse httpResponse = new OkHttpRibbonResponse(response, uri);
|
||||
|
||||
assertThat(httpResponse.isSuccess(), is(true));
|
||||
assertThat(httpResponse.hasPayload(), is(true));
|
||||
assertThat(httpResponse.getPayload(), is(notNullValue()));
|
||||
assertThat(httpResponse.getInputStream(), is(notNullValue()));
|
||||
}
|
||||
|
||||
Response.Builder response(URI uri) {
|
||||
return new Response.Builder()
|
||||
.request(new Request.Builder().url(HttpUrl.get(uri)).build())
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.code(HttpStatus.OK.value());
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
@@ -43,8 +44,6 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = ContextPathZuulProxyApplication.class)
|
||||
@WebAppConfiguration
|
||||
@@ -105,8 +104,4 @@ class ContextPathZuulProxyApplication {
|
||||
return "Gotten " + id + "!";
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,10 +115,6 @@ class RetryableZuulProxyApplication {
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "simple" pointing to localhost
|
||||
@@ -133,4 +129,4 @@ class RetryableRibbonClientConfiguration {
|
||||
return new StaticServerList<>(new Server("localhost", this.port),
|
||||
new Server("failed-localhost", this.port));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
@@ -43,10 +44,8 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = ServletPathZuulProxyApplication.class)
|
||||
@SpringApplicationConfiguration(classes = ServletPathZuulProxyApplicationTests.ServletPathZuulProxyApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port: 0", "server.servletPath: /app" })
|
||||
@DirtiesContext
|
||||
@@ -91,22 +90,18 @@ public class ServletPathZuulProxyApplicationTests {
|
||||
assertEquals("Gotten strip!", result.getBody());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
class ServletPathZuulProxyApplication {
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
static class ServletPathZuulProxyApplication {
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.GET)
|
||||
public String get(@PathVariable String id) {
|
||||
return "Gotten " + id + "!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.GET)
|
||||
public String get(@PathVariable String id) {
|
||||
return "Gotten " + id + "!";
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -12,14 +12,14 @@
|
||||
* 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;
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.apache;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
@@ -28,8 +28,9 @@ 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.zuul.EnableZuulProxy;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -47,13 +48,21 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleHttpClientZuulProxyApplication.class)
|
||||
@SpringApplicationConfiguration(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class)
|
||||
@WebIntegrationTest(randomPort = true, value = {
|
||||
"zuul.routes.other: /test/**=http://localhost:7777/local",
|
||||
"zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" })
|
||||
@DirtiesContext
|
||||
public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase {
|
||||
public class HttpClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
|
||||
@Override
|
||||
protected boolean supportsPatch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
@@ -87,47 +96,35 @@ public class SampleZuulProxyWithHttpClientTests extends ZuulProxyTestBase {
|
||||
assertEquals("Deleted 1!", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patchOnSelfViaSimpleHostRoutingFilter() {
|
||||
this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local");
|
||||
this.endpoint.reset();
|
||||
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/self/1", HttpMethod.PATCH,
|
||||
new HttpEntity<>("TestPatch"), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Patched 1!", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ribbonCommandFactoryOverridden() {
|
||||
assertTrue("ribbonCommandFactory not a HttpClientRibbonCommandFactory",
|
||||
this.ribbonCommandFactory instanceof HttpClientRibbonCommandFactory);
|
||||
}
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "simple", configuration = ZuulProxyTestBase.SimpleRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "another", configuration = ZuulProxyTestBase.AnotherRibbonClientConfiguration.class) })
|
||||
static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
}
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH)
|
||||
public String patch(@PathVariable final String id, @RequestBody final String body) {
|
||||
return "Patched " + id + "!";
|
||||
}
|
||||
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) })
|
||||
class SampleHttpClientZuulProxyApplication extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
final SpringClientFactory clientFactory) {
|
||||
return new HttpClientRibbonCommandFactory(clientFactory);
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH)
|
||||
public String patch(@PathVariable final String id, @RequestBody final String body) {
|
||||
return "Patched " + id + "!";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyErrorController myErrorController(ErrorAttributes errorAttributes) {
|
||||
return new MyErrorController(errorAttributes);
|
||||
@Bean
|
||||
public ZuulProxyTestBase.MyErrorController myErrorController(ErrorAttributes errorAttributes) {
|
||||
return new ZuulProxyTestBase.MyErrorController(errorAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.route.okhttp;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
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.zuul.EnableZuulProxy;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = OkHttpRibbonCommandIntegrationTests.TestConfig.class)
|
||||
@WebIntegrationTest(randomPort = true, value = {
|
||||
"zuul.routes.other: /test/**=http://localhost:7777/local",
|
||||
"zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" })
|
||||
@DirtiesContext
|
||||
public class OkHttpRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
|
||||
@Override
|
||||
protected boolean supportsPatch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
super.setTestRequestcontext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void patchOnSelfViaRibbonRoutingFilter() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple/local/1", HttpMethod.PATCH,
|
||||
new HttpEntity<>("TestPatch"), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Patched 1!", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postOnSelfViaRibbonRoutingFilter() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple/local/1", HttpMethod.POST,
|
||||
new HttpEntity<>("TestPost"), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Posted 1!", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteOnSelfViaRibbonRoutingFilter() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple/local/1", HttpMethod.DELETE,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Deleted 1!", result.getBody());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void ribbonCommandFactoryOverridden() {
|
||||
assertTrue("ribbonCommandFactory not a OkHttpRibbonCommandFactory",
|
||||
this.ribbonCommandFactory instanceof OkHttpRibbonCommandFactory);
|
||||
}
|
||||
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) })
|
||||
static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
final SpringClientFactory clientFactory) {
|
||||
return new OkHttpRibbonCommandFactory(clientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyErrorController myErrorController(ErrorAttributes errorAttributes) {
|
||||
return new MyErrorController(errorAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2015 the original author or authors.
|
||||
* 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.
|
||||
@@ -12,12 +12,18 @@
|
||||
* 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;
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.restclient;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -36,13 +42,13 @@ 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.zuul.EnableZuulProxy;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommand;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -55,7 +61,6 @@ import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.MatrixVariable;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -63,22 +68,14 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleZuulProxyApplication.class)
|
||||
@SpringApplicationConfiguration(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port: 0",
|
||||
"zuul.routes.other: /test/**=http://localhost:7777/local",
|
||||
@@ -87,11 +84,16 @@ import static org.junit.Assert.assertTrue;
|
||||
"zuul.routes.rnd: /rnd/**", "rnd.ribbon.listOfServers: ${random.value}",
|
||||
"zuul.removeSemicolonContent: false" })
|
||||
@DirtiesContext
|
||||
public class SampleZuulProxyApplicationTests extends ZuulProxyTestBase {
|
||||
public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
|
||||
@Autowired
|
||||
RouteLocator routeLocator;
|
||||
|
||||
@Override
|
||||
protected boolean supportsPatch() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleHostRouteWithTrailingSlash() {
|
||||
this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/");
|
||||
@@ -223,144 +225,141 @@ public class SampleZuulProxyApplicationTests extends ZuulProxyTestBase {
|
||||
@Test
|
||||
public void ribbonCommandFactoryOverridden() {
|
||||
assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory",
|
||||
this.ribbonCommandFactory instanceof SampleZuulProxyApplication.MyRibbonCommandFactory);
|
||||
this.ribbonCommandFactory instanceof TestConfig.MyRibbonCommandFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void routeLocatorOverridden() {
|
||||
assertTrue("routeLocator not a MyRouteLocator",
|
||||
this.routeLocator instanceof SampleZuulProxyApplication.MyRouteLocator);
|
||||
this.routeLocator instanceof TestConfig.MyRouteLocator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "badhost", configuration = SampleZuulProxyApplication.BadHostRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "simple", configuration = SimpleRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) })
|
||||
class SampleZuulProxyApplication extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
@RequestMapping("/trailing-slash")
|
||||
public String trailingSlash(HttpServletRequest request) {
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
@RequestMapping("/content-type")
|
||||
public String contentType(HttpServletRequest request) {
|
||||
String header = request.getHeader("Content-Type");
|
||||
return header == null ? "<NONE>" : header;
|
||||
}
|
||||
|
||||
@RequestMapping("/add-header")
|
||||
public ResponseEntity<String> addHeader(HttpServletRequest request) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-Header", "FOO");
|
||||
ResponseEntity<String> result = new ResponseEntity<String>(
|
||||
request.getRequestURI(), headers, HttpStatus.OK);
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping("/query")
|
||||
public String addQuery(HttpServletRequest request, @RequestParam String foo) {
|
||||
return request.getRequestURI() + "?foo=" + foo;
|
||||
}
|
||||
|
||||
@RequestMapping("/matrix/{name}/{another}")
|
||||
public String matrix(@PathVariable("name") String name,
|
||||
@MatrixVariable(value = "p", pathVar = "name") int p,
|
||||
@MatrixVariable(value = "q", pathVar = "name") int q,
|
||||
@PathVariable("another") String another,
|
||||
@MatrixVariable(value = "x", pathVar = "another") int x) {
|
||||
return name + "=" + p + "-" + q + ";" + another + "=" + x;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
SpringClientFactory clientFactory) {
|
||||
return new MyRibbonCommandFactory(clientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator routeLocator(DiscoveryClient discoveryClient, ZuulProperties zuulProperties) {
|
||||
return new MyRouteLocator("/", discoveryClient, zuulProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyErrorController myErrorController(ErrorAttributes errorAttributes) {
|
||||
return new MyErrorController(errorAttributes);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
public static class MyRibbonCommandFactory extends RestClientRibbonCommandFactory {
|
||||
|
||||
public MyRibbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
super(clientFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
@SneakyThrows
|
||||
public RestClientRibbonCommand create(RibbonCommandContext context) {
|
||||
String uri = context.getUri();
|
||||
if (uri.startsWith("/throwexception/")) {
|
||||
String code = uri.replace("/throwexception/", "");
|
||||
RestClient restClient = getClientFactory()
|
||||
.getClient(context.getServiceId(), RestClient.class);
|
||||
return new MyCommand(Integer.parseInt(code), context.getServiceId(),
|
||||
restClient, getVerb(context.getVerb()), context.getUri(),
|
||||
context.getRetryable(), context.getHeaders(), context.getParams(),
|
||||
context.getRequestEntity());
|
||||
}
|
||||
return super.create(context);
|
||||
}
|
||||
}
|
||||
|
||||
static class MyCommand extends RestClientRibbonCommand {
|
||||
|
||||
private int errorCode;
|
||||
|
||||
public MyCommand(int errorCode, String commandKey, RestClient restClient,
|
||||
HttpRequest.Verb verb, String uri, Boolean retryable,
|
||||
MultiValueMap<String, String> headers,
|
||||
MultiValueMap<String, String> params, InputStream requestEntity)
|
||||
throws URISyntaxException {
|
||||
super(commandKey, restClient, verb, uri, retryable, headers, params,
|
||||
requestEntity);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse forward() throws Exception {
|
||||
if (this.errorCode == 503) {
|
||||
throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED);
|
||||
}
|
||||
return new MockClientHttpResponse((byte[]) null,
|
||||
HttpStatus.valueOf(this.errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "simple" pointing to bad host
|
||||
// Don't use @SpringBootApplication because we don't want to component scan
|
||||
@Configuration
|
||||
static class BadHostRibbonClientConfiguration {
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server(UUID.randomUUID().toString(), 4322));
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableZuulProxy
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "badhost", configuration = TestConfig.BadHostRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "simple", configuration = ZuulProxyTestBase.SimpleRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "another", configuration = ZuulProxyTestBase.AnotherRibbonClientConfiguration.class) })
|
||||
static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
@RequestMapping("/trailing-slash")
|
||||
public String trailingSlash(HttpServletRequest request) {
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
@RequestMapping("/content-type")
|
||||
public String contentType(HttpServletRequest request) {
|
||||
String header = request.getHeader("Content-Type");
|
||||
return header == null ? "<NONE>" : header;
|
||||
}
|
||||
|
||||
}
|
||||
@RequestMapping("/add-header")
|
||||
public ResponseEntity<String> addHeader(HttpServletRequest request) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-Header", "FOO");
|
||||
ResponseEntity<String> result = new ResponseEntity<String>(
|
||||
request.getRequestURI(), headers, HttpStatus.OK);
|
||||
return result;
|
||||
}
|
||||
|
||||
static class MyRouteLocator extends DiscoveryClientRouteLocator {
|
||||
@RequestMapping("/query")
|
||||
public String addQuery(HttpServletRequest request, @RequestParam String foo) {
|
||||
return request.getRequestURI() + "?foo=" + foo;
|
||||
}
|
||||
|
||||
public MyRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties) {
|
||||
super(servletPath, discovery, properties);
|
||||
@RequestMapping("/matrix/{name}/{another}")
|
||||
public String matrix(@PathVariable("name") String name,
|
||||
@MatrixVariable(value = "p", pathVar = "name") int p,
|
||||
@MatrixVariable(value = "q", pathVar = "name") int q,
|
||||
@PathVariable("another") String another,
|
||||
@MatrixVariable(value = "x", pathVar = "another") int x) {
|
||||
return name + "=" + p + "-" + q + ";" + another + "=" + x;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
SpringClientFactory clientFactory) {
|
||||
return new MyRibbonCommandFactory(clientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouteLocator routeLocator(DiscoveryClient discoveryClient, ZuulProperties zuulProperties) {
|
||||
return new MyRouteLocator("/", discoveryClient, zuulProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyErrorController myErrorController(ErrorAttributes errorAttributes) {
|
||||
return new MyErrorController(errorAttributes);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(TestConfig.class, args);
|
||||
}
|
||||
|
||||
public static class MyRibbonCommandFactory extends RestClientRibbonCommandFactory {
|
||||
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
public MyRibbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
super(clientFactory);
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
@SneakyThrows
|
||||
public RestClientRibbonCommandFactory.RestClientRibbonCommand create(RibbonCommandContext context) {
|
||||
String uri = context.getUri();
|
||||
if (uri.startsWith("/throwexception/")) {
|
||||
String code = uri.replace("/throwexception/", "");
|
||||
RestClient restClient = clientFactory
|
||||
.getClient(context.getServiceId(), RestClient.class);
|
||||
return new MyCommand(Integer.parseInt(code), context.getServiceId(),
|
||||
restClient, context);
|
||||
}
|
||||
return super.create(context);
|
||||
}
|
||||
}
|
||||
|
||||
static class MyCommand extends RestClientRibbonCommandFactory.RestClientRibbonCommand {
|
||||
|
||||
private int errorCode;
|
||||
|
||||
public MyCommand(int errorCode, String commandKey, RestClient restClient,
|
||||
RibbonCommandContext context) {
|
||||
super(commandKey, restClient, context);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse run() throws Exception {
|
||||
if (this.errorCode == 503) {
|
||||
throw new ClientException(ClientException.ErrorType.SERVER_THROTTLED);
|
||||
}
|
||||
return new MockClientHttpResponse((byte[]) null,
|
||||
HttpStatus.valueOf(this.errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "simple" pointing to bad host
|
||||
@Configuration
|
||||
static class BadHostRibbonClientConfiguration {
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server(UUID.randomUUID().toString(), 4322));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyRouteLocator extends DiscoveryClientRouteLocator {
|
||||
|
||||
public MyRouteLocator(String servletPath, DiscoveryClient discovery, ZuulProperties properties) {
|
||||
super(servletPath, discovery, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,21 @@
|
||||
package org.springframework.cloud.netflix.zuul;
|
||||
/*
|
||||
* 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.route.support;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -8,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -17,6 +35,7 @@ import org.springframework.boot.autoconfigure.web.ErrorAttributes;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorProperties;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.cloud.netflix.ribbon.StaticServerList;
|
||||
import org.springframework.cloud.netflix.zuul.RoutesEndpoint;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
|
||||
@@ -41,8 +60,10 @@ import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -213,9 +234,31 @@ public abstract class ZuulProxyTestBase {
|
||||
assertEquals("Received {key=[overridden]}", result.getBody());
|
||||
}
|
||||
|
||||
protected static abstract class AbstractZuulProxyApplication
|
||||
@Test
|
||||
public void patchOnSelfViaSimpleHostRoutingFilter() {
|
||||
assumeThat(supportsPatch(), is(true));
|
||||
|
||||
this.routes.addRoute("/self/**", "http://localhost:" + this.port + "/local");
|
||||
this.endpoint.reset();
|
||||
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/self/1", HttpMethod.PATCH,
|
||||
new HttpEntity<>("TestPatch"), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Patched 1!", result.getBody());
|
||||
}
|
||||
|
||||
protected abstract boolean supportsPatch();
|
||||
|
||||
public static abstract class AbstractZuulProxyApplication
|
||||
extends DelegatingWebMvcConfiguration {
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH)
|
||||
public String patch(@PathVariable final String id,
|
||||
@RequestBody final String body) {
|
||||
return "Patched " + id + "!";
|
||||
}
|
||||
|
||||
@RequestMapping("/testing123")
|
||||
public String testing123() {
|
||||
throw new RuntimeException("myerror");
|
||||
@@ -298,64 +341,64 @@ public abstract class ZuulProxyTestBase {
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "simple" pointing to localhost
|
||||
@Configuration
|
||||
class SimpleRibbonClientConfiguration {
|
||||
// Load balancer with fixed server list for "simple" pointing to localhost
|
||||
@Configuration
|
||||
public static class SimpleRibbonClientConfiguration {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
}
|
||||
|
||||
}
|
||||
@Configuration
|
||||
public static class AnotherRibbonClientConfiguration {
|
||||
|
||||
@Configuration
|
||||
class AnotherRibbonClientConfiguration {
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
}
|
||||
|
||||
}
|
||||
public static class MyErrorController extends BasicErrorController {
|
||||
ThreadLocal<String> uriToMatch = new ThreadLocal<>();
|
||||
|
||||
class MyErrorController extends BasicErrorController {
|
||||
ThreadLocal<String> uriToMatch = new ThreadLocal<>();
|
||||
AtomicBoolean controllerUsed = new AtomicBoolean();
|
||||
|
||||
AtomicBoolean controllerUsed = new AtomicBoolean();
|
||||
public MyErrorController(ErrorAttributes errorAttributes) {
|
||||
super(errorAttributes, new ErrorProperties());
|
||||
}
|
||||
|
||||
public MyErrorController(ErrorAttributes errorAttributes) {
|
||||
super(errorAttributes, new ErrorProperties());
|
||||
}
|
||||
@Override
|
||||
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
|
||||
String errorUri = (String) request.getAttribute("javax.servlet.error.request_uri");
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
|
||||
String errorUri = (String) request.getAttribute("javax.servlet.error.request_uri");
|
||||
|
||||
if (errorUri != null && errorUri.equals(this.uriToMatch.get())) {
|
||||
controllerUsed.set(true);
|
||||
if (errorUri != null && errorUri.equals(this.uriToMatch.get())) {
|
||||
controllerUsed.set(true);
|
||||
}
|
||||
this.uriToMatch.remove();
|
||||
return super.error(request);
|
||||
}
|
||||
this.uriToMatch.remove();
|
||||
return super.error(request);
|
||||
}
|
||||
|
||||
public void setUriToMatch(String uri) {
|
||||
this.uriToMatch.set(uri);
|
||||
}
|
||||
public void setUriToMatch(String uri) {
|
||||
this.uriToMatch.set(uri);
|
||||
}
|
||||
|
||||
public boolean wasControllerUsed() {
|
||||
return this.controllerUsed.get();
|
||||
}
|
||||
public boolean wasControllerUsed() {
|
||||
return this.controllerUsed.get();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.controllerUsed.set(false);
|
||||
public void clear() {
|
||||
this.controllerUsed.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user