Browse Source
This is an implementation of a RibbonCommand and a factory which makes use of a loadbalancing http client based on apache's http-components http-client. With this command/factory it is possible to route PATCH http requests via zuul/ribbon as well. fixes gh-412pull/6/head
6 changed files with 879 additions and 0 deletions
@ -0,0 +1,92 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue