Merge pull request #543 from ChristianLohmann/create-httpclient-ribbon-command
* create-httpclient-ribbon-command: an Apache http client zuul ribbon command
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.apache;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
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 lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
@Getter
|
||||
public class RibbonApacheHttpRequest extends ClientRequest implements Cloneable {
|
||||
|
||||
private final String method;
|
||||
|
||||
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 = method;
|
||||
this.uri = uri;
|
||||
this.isRetriable = retryable;
|
||||
this.headers = headers;
|
||||
this.params = params;
|
||||
this.requestEntity = requestEntity;
|
||||
}
|
||||
|
||||
public HttpUriRequest toRequest(final RequestConfig requestConfig) {
|
||||
final RequestBuilder builder = RequestBuilder.create(this.method);
|
||||
builder.setUri(this.uri);
|
||||
for (final String name : this.headers.keySet()) {
|
||||
final List<String> values = this.headers.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 value : values) {
|
||||
builder.addParameter(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.requestEntity != null) {
|
||||
final BasicHttpEntity entity;
|
||||
entity = new BasicHttpEntity();
|
||||
entity.setContent(this.requestEntity);
|
||||
builder.setEntity(entity);
|
||||
}
|
||||
|
||||
builder.setConfig(requestConfig);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public RibbonApacheHttpRequest withNewUri(final URI uri) {
|
||||
return new RibbonApacheHttpRequest(this.method, uri, this.isRetriable,
|
||||
this.headers, this.params, this.requestEntity);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.apache;
|
||||
|
||||
import java.io.IOException;
|
||||
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.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.http.CaseInsensitiveMultiMap;
|
||||
import com.netflix.client.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
public class RibbonApacheHttpResponse implements com.netflix.client.http.HttpResponse {
|
||||
|
||||
private HttpResponse httpResponse;
|
||||
private URI uri;
|
||||
|
||||
public RibbonApacheHttpResponse(final HttpResponse httpResponse, final URI uri) {
|
||||
this.httpResponse = httpResponse;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPayload() throws ClientException {
|
||||
try {
|
||||
return this.httpResponse.getEntity().getContent();
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new ClientException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload() {
|
||||
return this.httpResponse.getEntity() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return this.httpResponse.getStatusLine().getStatusCode() == 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestedURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return httpResponse.getStatusLine().getStatusCode();
|
||||
}
|
||||
|
||||
public String getStatusLine() {
|
||||
return httpResponse.getStatusLine().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Collection<String>> getHeaders() {
|
||||
final Map<String, Collection<String>> headers = new HashMap<>();
|
||||
for (final Header header : this.httpResponse.getAllHeaders()) {
|
||||
if (headers.containsKey(header.getName())) {
|
||||
headers.get(header.getName()).add(header.getValue());
|
||||
}
|
||||
else {
|
||||
final List<String> values = new ArrayList<>();
|
||||
values.add(header.getValue());
|
||||
headers.put(header.getName(), values);
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHttpHeaders() {
|
||||
final CaseInsensitiveMultiMap headers = new CaseInsensitiveMultiMap();
|
||||
for (final Header header : httpResponse.getAllHeaders()) {
|
||||
headers.addHeader(header.getName(), header.getValue());
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (this.httpResponse != null && this.httpResponse.getEntity() != null) {
|
||||
try {
|
||||
this.httpResponse.getEntity().getContent().close();
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
try {
|
||||
return this.httpResponse.getEntity().getContent();
|
||||
}
|
||||
catch (final IOException e) {
|
||||
throw new RuntimeException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasEntity() {
|
||||
return hasPayload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used
|
||||
*/
|
||||
@Override
|
||||
public <T> T getEntity(final Class<T> type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used
|
||||
*/
|
||||
@Override
|
||||
public <T> T getEntity(final Type type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used
|
||||
*/
|
||||
@Override
|
||||
public <T> T getEntity(final TypeToken<T> type) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.apache;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
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.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.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
public class RibbonLoadBalancingHttpClient
|
||||
extends AbstractLoadBalancerAwareClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
private final HttpClient delegate = HttpClientBuilder.create().build();
|
||||
|
||||
@Setter
|
||||
private int connectTimeout;
|
||||
|
||||
@Setter
|
||||
private int readTimeout;
|
||||
|
||||
@Setter
|
||||
private boolean secure;
|
||||
|
||||
@Setter
|
||||
private IClientConfig clientConfig;
|
||||
|
||||
public RibbonLoadBalancingHttpClient() {
|
||||
super(null);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
}
|
||||
|
||||
public RibbonLoadBalancingHttpClient(final ILoadBalancer lb) {
|
||||
super(lb);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
final RibbonApacheHttpRequest request, final IClientConfig requestConfig) {
|
||||
if (this.clientConfig.get(CommonClientConfigKey.OkToRetryOnAllOperations, false)) {
|
||||
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
|
||||
public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request,
|
||||
final IClientConfig configOverride) throws Exception {
|
||||
final RequestConfig.Builder builder = RequestConfig.custom();
|
||||
if (configOverride != null) {
|
||||
builder.setConnectTimeout(configOverride.get(
|
||||
CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
|
||||
builder.setConnectionRequestTimeout(configOverride.get(
|
||||
CommonClientConfigKey.ReadTimeout, this.readTimeout));
|
||||
}
|
||||
else {
|
||||
builder.setConnectTimeout(this.connectTimeout);
|
||||
builder.setConnectionRequestTimeout(this.readTimeout);
|
||||
}
|
||||
|
||||
final RequestConfig requestConfig = builder.build();
|
||||
|
||||
if (isSecure(configOverride)) {
|
||||
final URI secureUri = UriComponentsBuilder.fromUri(request.getUri())
|
||||
.scheme("https").build().toUri();
|
||||
request = request.withNewUri(secureUri);
|
||||
}
|
||||
|
||||
final HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
|
||||
final HttpResponse httpResponse = this.delegate.execute(httpUriRequest);
|
||||
return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
|
||||
}
|
||||
|
||||
private boolean isSecure(final IClientConfig config) {
|
||||
return (config != null) ? config.get(CommonClientConfigKey.IsSecure) : secure;
|
||||
}
|
||||
}
|
||||
@@ -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.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;
|
||||
|
||||
/**
|
||||
* @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();
|
||||
URI uriInstance = new URI(this.uri);
|
||||
RibbonApacheHttpRequest request = new RibbonApacheHttpRequest(this.method,
|
||||
uriInstance, this.retryable, this.headers, this.params,
|
||||
this.requestEntity);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class HttpClientRibbonCommandFactory implements
|
||||
RibbonCommandFactory<HttpClientRibbonCommand> {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
@Override
|
||||
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
|
||||
final String serviceId = context.getServiceId();
|
||||
final RibbonLoadBalancingHttpClient client = clientFactory.getClient(serviceId,
|
||||
RibbonLoadBalancingHttpClient.class);
|
||||
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
|
||||
client.setClientConfig(this.clientFactory.getClientConfig(serviceId));
|
||||
|
||||
final HttpClientRibbonCommand httpClientRibbonCommand = new HttpClientRibbonCommand(
|
||||
serviceId, client, context.getVerb(), context.getUri(),
|
||||
context.getHeaders(), context.getParams(), context.getRequestEntity(),
|
||||
context.getRetryable());
|
||||
return httpClientRibbonCommand;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
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.filters.ProxyRouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = SampleHttpClientZuulProxyApplication.class)
|
||||
@WebAppConfiguration
|
||||
@IntegrationTest({ "server.port: 0",
|
||||
"zuul.routes.other: /test/**=http://localhost:7777/local",
|
||||
"zuul.routes.another: /another/twolevel/**", "zuul.routes.simple: /simple/**" })
|
||||
@DirtiesContext
|
||||
public class SampleZuulProxyAppTestsWithHttpClient {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private ProxyRouteLocator routes;
|
||||
|
||||
@Autowired
|
||||
private RoutesEndpoint endpoint;
|
||||
|
||||
@Autowired
|
||||
private RibbonCommandFactory ribbonCommandFactory;
|
||||
|
||||
@Test
|
||||
public void bindRouteUsingPhysicalRoute() {
|
||||
assertEquals("http://localhost:7777/local",
|
||||
this.routes.getRoutes().get("/test/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindRouteUsingOnlyPath() {
|
||||
assertEquals("simple", this.routes.getRoutes().get("/simple/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getOnSelfViaRibbonRoutingFilter() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple/local/1", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Gotten 1!", result.getBody());
|
||||
}
|
||||
|
||||
@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 deleteOnSelfViaSimpleHostRoutingFilter() {
|
||||
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.DELETE,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
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 stripPrefixFalseAppendsPath() {
|
||||
this.routes.addRoute(new ZuulProperties.ZuulRoute("strip", "/strip/**", "strip",
|
||||
"http://localhost:" + this.port + "/local", false, false));
|
||||
this.endpoint.reset();
|
||||
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/strip", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
|
||||
// Prefix not stripped to it goes to /local/strip
|
||||
assertEquals("Gotten strip!", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFound() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/myinvalidpath", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.NOT_FOUND, result.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSecondLevel() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/another/twolevel/local/1",
|
||||
HttpMethod.GET, new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Gotten 1!", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ribbonRouteWithSpace() {
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/simple/spa ce", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Hello space", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleHostRouteWithSpace() {
|
||||
routes.addRoute("/self/**", "http://localhost:" + this.port);
|
||||
this.endpoint.reset();
|
||||
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + "/self/spa ce", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Hello space", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleHostRouteWithOriginalQString() {
|
||||
routes.addRoute("/self/**", "http://localhost:" + this.port);
|
||||
this.endpoint.reset();
|
||||
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port
|
||||
+ "/self/qstring?original=value1&original=value2",
|
||||
HttpMethod.GET, new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Received {original=[value1, value2]}", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleHostRouteWithOverriddenQString() {
|
||||
routes.addRoute("/self/**", "http://localhost:" + this.port);
|
||||
this.endpoint.reset();
|
||||
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port
|
||||
+ "/self/qstring?override=true&different=key", HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("Received {key=[overridden]}", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ribbonCommandFactoryOverridden() {
|
||||
assertTrue("ribbonCommandFactory not a MyRibbonCommandFactory",
|
||||
ribbonCommandFactory instanceof HttpClientRibbonCommandFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
public static void main(final String[] args) {
|
||||
SpringApplication.run(SampleZuulProxyApplication.class, args);
|
||||
}
|
||||
|
||||
@RequestMapping("/testing123")
|
||||
public String testing123() {
|
||||
throw new RuntimeException("myerror");
|
||||
}
|
||||
|
||||
@RequestMapping("/local")
|
||||
public String local() {
|
||||
return "Hello local";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.DELETE)
|
||||
public String delete(@PathVariable final String id) {
|
||||
return "Deleted " + id + "!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH)
|
||||
public String patch(@PathVariable final String id, @RequestBody final String body) {
|
||||
return "Patched " + id + "!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.GET)
|
||||
public String get(@PathVariable final String id) {
|
||||
return "Gotten " + id + "!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.POST)
|
||||
public String post(@PathVariable final String id, @RequestBody final String body) {
|
||||
return "Posted " + id + "!";
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/qstring")
|
||||
public String qstring(@RequestParam final MultiValueMap<String, String> params) {
|
||||
return "Received " + params.toString();
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
public String home() {
|
||||
return "Hello world";
|
||||
}
|
||||
|
||||
@RequestMapping("/spa ce")
|
||||
public String space() {
|
||||
return "Hello space";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RibbonCommandFactory ribbonCommandFactory(
|
||||
final SpringClientFactory clientFactory) {
|
||||
return new HttpClientRibbonCommandFactory(clientFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter sampleFilter() {
|
||||
return new ZuulFilter() {
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
if (RequestContext.getCurrentContext().getRequest().getParameterMap()
|
||||
.containsKey("override")) {
|
||||
Map<String, List<String>> overridden = new HashMap<>();
|
||||
overridden.put("key", Arrays.asList("overridden"));
|
||||
RequestContext.getCurrentContext().setRequestQueryParams(overridden);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user