diff --git a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/src/main/resources/application.yml b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/src/main/resources/application.yml index c191c5e4b..ded37143d 100644 --- a/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/src/main/resources/application.yml +++ b/spring-cloud-tencent-examples/polaris-gateway-example/gateway-zuul-service/src/main/resources/application.yml @@ -18,3 +18,4 @@ zuul: GatewayCalleeService: serviceId: GatewayCalleeService path: /GatewayCalleeService/** + # stripPrefix: true diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/pom.xml b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/pom.xml index 261bd215c..daa231d93 100644 --- a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/pom.xml +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/pom.xml @@ -29,6 +29,11 @@ com.tencent.cloud spring-cloud-starter-tencent-polaris-ratelimit + + + com.tencent.cloud + spring-cloud-starter-tencent-polaris-discovery + diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/config/PolarisGatewayAutoConfiguration.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/config/PolarisGatewayAutoConfiguration.java index cf8c17430..69e2a9fc3 100644 --- a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/config/PolarisGatewayAutoConfiguration.java +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/config/PolarisGatewayAutoConfiguration.java @@ -22,11 +22,17 @@ import com.netflix.zuul.http.ZuulServlet; import com.tencent.cloud.polaris.gateway.core.scg.filter.Metadata2HeaderScgFilter; import com.tencent.cloud.polaris.gateway.core.scg.filter.MetadataFirstScgFilter; import com.tencent.cloud.polaris.gateway.core.scg.filter.RateLimitScgFilter; +import com.tencent.cloud.polaris.gateway.core.zuul.discovery.PolarisProviderDiscovery; import com.tencent.cloud.polaris.gateway.core.zuul.filter.Metadata2HeaderZuulFilter; import com.tencent.cloud.polaris.gateway.core.zuul.filter.MetadataFirstZuulFilter; +import com.tencent.cloud.polaris.gateway.core.zuul.filter.PolarisRpcZuulFilter; import com.tencent.cloud.polaris.gateway.core.zuul.filter.RateLimitZuulFilter; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -50,10 +56,24 @@ public class PolarisGatewayAutoConfiguration { public ZuulFilter rateLimitZuulFilter() { return new RateLimitZuulFilter(); } + @Bean public ZuulFilter metadata2HeaderZuulFilter() { return new Metadata2HeaderZuulFilter(); } + + @Bean + public PolarisProviderDiscovery polaprisProviderDiscovery(ZuulProperties zuulProperties) { + return new PolarisProviderDiscovery(zuulProperties); + } + + @Bean + public PolarisRpcZuulFilter polarisRpcZuulFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties, + ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory, + PolarisProviderDiscovery polarisProviderDiscovery) { + return new PolarisRpcZuulFilter(helper, zuulProperties, connectionManagerFactory, httpClientFactory, polarisProviderDiscovery); + } } @Configuration(proxyBeanMethods = false) diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/discovery/PolarisProviderDiscovery.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/discovery/PolarisProviderDiscovery.java new file mode 100644 index 000000000..979153a80 --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/discovery/PolarisProviderDiscovery.java @@ -0,0 +1,97 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.polaris.gateway.core.zuul.discovery; + +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.Instance; +import com.tencent.polaris.api.rpc.GetAllInstancesRequest; +import com.tencent.polaris.api.rpc.InstancesResponse; +import com.tencent.polaris.factory.api.DiscoveryAPIFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author: hongyihui + */ +public class PolarisProviderDiscovery { + + private static final Logger LOG = LoggerFactory.getLogger(PolarisProviderDiscovery.class); + private static final Map> polarisProviderMap = new ConcurrentHashMap<>(); + private static final Timer getAllServiceProviderTimer = new Timer("PolarisProviderDiscovery.getAllServiceProviderTimer", true); + + private ZuulProperties zuulProperties; + + public PolarisProviderDiscovery(ZuulProperties zuulProperties) { + this.zuulProperties = zuulProperties; + + getAllServiceProvider(); + + getAllServiceProviderTimer.schedule(new TimerTask() { + + @Override + public void run() { + getAllServiceProvider(); + } + }, 10000, 30000); + } + + private void getAllServiceProvider() { + Map zuulRouteMap = zuulProperties.getRoutes(); + if (zuulRouteMap != null && zuulRouteMap.size() > 0) { + ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPI(); + for (Map.Entry entry : zuulRouteMap.entrySet()) { + ZuulProperties.ZuulRoute zuulRoute = entry.getValue(); + String serviceId = zuulRoute.getServiceId(); + + GetAllInstancesRequest getAllInstancesRequest = new GetAllInstancesRequest(); + getAllInstancesRequest.setNamespace(MetadataContextHolder.LOCAL_NAMESPACE); + getAllInstancesRequest.setService(serviceId); + InstancesResponse instancesResponse = consumerAPI.getAllInstance(getAllInstancesRequest); + for (Instance instance : instancesResponse.getInstances()) { + LOG.info("instance is " + instance.getHost() + ":" + instance.getPort()); + } + polarisProviderMap.put(MetadataContextHolder.LOCAL_NAMESPACE + "." + serviceId, Arrays.asList(instancesResponse.getInstances())); + } + consumerAPI.destroy(); + } + } + + public Instance selectProvider(String namespace, String serviceId) { + if (polarisProviderMap.size() == 0) { + return null; + } + List instances = polarisProviderMap.get(namespace + "." + serviceId); + if (instances == null || instances.size() == 0) { + return null; + } + + // random select + Random random = new Random(); + int selectIndex = random.nextInt(instances.size()); + return instances.get(selectIndex); + } +} \ No newline at end of file diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/Metadata2HeaderZuulFilter.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/Metadata2HeaderZuulFilter.java index 4b743f070..30234ec28 100644 --- a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/Metadata2HeaderZuulFilter.java +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/Metadata2HeaderZuulFilter.java @@ -47,7 +47,7 @@ public class Metadata2HeaderZuulFilter extends ZuulFilter { @Override public int filterOrder() { - return RIBBON_ROUTING_FILTER_ORDER - 1; + return RIBBON_ROUTING_FILTER_ORDER - 2; } @Override diff --git a/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/PolarisRpcZuulFilter.java b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/PolarisRpcZuulFilter.java new file mode 100644 index 000000000..9d13b3c8c --- /dev/null +++ b/spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/PolarisRpcZuulFilter.java @@ -0,0 +1,442 @@ +/* + * Tencent is pleased to support the open source community by making Spring Cloud Tencent available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the BSD 3-Clause License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + * + * 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 com.tencent.cloud.polaris.gateway.core.zuul.filter; + +import com.netflix.client.ClientException; +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; +import com.netflix.zuul.exception.ZuulException; +import com.tencent.cloud.metadata.constant.MetadataConstant; +import com.tencent.cloud.metadata.context.MetadataContextHolder; +import com.tencent.cloud.polaris.gateway.core.zuul.discovery.PolarisProviderDiscovery; +import com.tencent.cloud.polaris.ratelimit.utils.Consts; +import com.tencent.polaris.api.pojo.Instance; +import org.apache.http.Header; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicHttpEntityEnclosingRequest; +import org.apache.http.message.BasicHttpRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; +import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; +import org.springframework.cloud.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; +import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.Host; +import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; +import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException; +import org.springframework.context.ApplicationListener; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +/** + * @author: hongyihui + */ +public class PolarisRpcZuulFilter extends ZuulFilter implements ApplicationListener { + + private static final Logger LOG = LoggerFactory.getLogger(PolarisRpcZuulFilter.class); + + private final Timer connectionManagerTimer = new Timer("PolarisRpcZuulFilter.connectionManagerTimer", true); + + private boolean sslHostnameValidationEnabled; + + private boolean forceOriginalQueryStringEncoding; + + private ProxyRequestHelper helper; + + private ZuulProperties properties; + + private Host hostProperties; + + private ApacheHttpClientConnectionManagerFactory connectionManagerFactory; + + private ApacheHttpClientFactory httpClientFactory; + + private HttpClientConnectionManager connectionManager; + + private CloseableHttpClient httpClient; + + private boolean useServlet31 = true; + + private PolarisProviderDiscovery polarisProviderDiscovery; + + @Override + @SuppressWarnings("Deprecation") + public void onApplicationEvent(EnvironmentChangeEvent event) { + onPropertyChange(event); + } + + @Deprecated + public void onPropertyChange(EnvironmentChangeEvent event) { + boolean createNewClient = false; + + for (String key : event.getKeys()) { + if (key.startsWith("zuul.host.")) { + createNewClient = true; + break; + } + } + + if (createNewClient) { + try { + this.httpClient.close(); + } catch (IOException ex) { + LOG.error("error closing client", ex); + } + // Re-create connection manager (may be shut down on HTTP client close) + try { + this.connectionManager.shutdown(); + } catch (RuntimeException ex) { + LOG.error("error shutting down connection manager", ex); + } + + this.connectionManager = newConnectionManager(); + this.httpClient = newClient(); + } + } + + public PolarisRpcZuulFilter(ProxyRequestHelper helper, ZuulProperties properties, + ApacheHttpClientConnectionManagerFactory connectionManagerFactory, + ApacheHttpClientFactory httpClientFactory, PolarisProviderDiscovery polarisProviderDiscovery) { + this.helper = helper; + + this.properties = properties; + this.hostProperties = properties.getHost(); + this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled(); + this.forceOriginalQueryStringEncoding = properties.isForceOriginalQueryStringEncoding(); + + this.connectionManagerFactory = connectionManagerFactory; + this.httpClientFactory = httpClientFactory; + + this.polarisProviderDiscovery = polarisProviderDiscovery; + checkServletVersion(); + } + + protected void checkServletVersion() { + // To support Servlet API 3.1 we need to check if getContentLengthLong exists + // Spring 5 minimum support is 3.0, so this stays + try { + HttpServletRequest.class.getMethod("getContentLengthLong"); + useServlet31 = true; + } catch (NoSuchMethodException e) { + useServlet31 = false; + } + } + + @PostConstruct + private void initialize() { + this.connectionManager = newConnectionManager(); + this.httpClient = newClient(); + + this.connectionManagerTimer.schedule(new TimerTask() { + + @Override + public void run() { + if (PolarisRpcZuulFilter.this.connectionManager == null) { + return; + } + PolarisRpcZuulFilter.this.connectionManager.closeExpiredConnections(); + } + }, 30000, 5000); + } + + protected HttpClientConnectionManager newConnectionManager() { + return connectionManagerFactory.newConnectionManager( + !this.sslHostnameValidationEnabled, + this.hostProperties.getMaxTotalConnections(), + this.hostProperties.getMaxPerRouteConnections(), + this.hostProperties.getTimeToLive(), this.hostProperties.getTimeUnit(), + null); + } + + protected CloseableHttpClient newClient() { + final RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(this.hostProperties.getConnectionRequestTimeoutMillis()) + .setSocketTimeout(this.hostProperties.getSocketTimeoutMillis()) + .setConnectTimeout(this.hostProperties.getConnectTimeoutMillis()) + .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); + return httpClientFactory.createBuilder().setDefaultRequestConfig(requestConfig) + .setConnectionManager(this.connectionManager).disableRedirectHandling() + .build(); + } + + @PreDestroy + public void stop() { + this.connectionManagerTimer.cancel(); + } + + @Override + public String filterType() { + return FilterConstants.ROUTE_TYPE; + } + + @Override + public int filterOrder() { + return FilterConstants.RIBBON_ROUTING_FILTER_ORDER - 1; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + RequestContext context = RequestContext.getCurrentContext(); + HttpServletRequest request = context.getRequest(); + MultiValueMap headers = this.helper.buildZuulRequestHeaders(request); + MultiValueMap params = this.helper.buildZuulRequestQueryParams(request); + String verb = getVerb(request); + InputStream requestEntity = getRequestBody(request); + if (getContentLength(request) < 0) { + context.setChunkedRequestBody(); + } + + String uri = this.helper.buildZuulRequestURI(request); + this.helper.addIgnoredHeaders(); + + try { + String peerNamespace = MetadataContextHolder.get().getSystemMetadata(MetadataConstant.SystemMetadataKey.PEER_NAMESPACE); + String serviceId = (String) context.get(FilterConstants.SERVICE_ID_KEY); + String proxy = (String) context.get(FilterConstants.PROXY_KEY); + // TODO retry + Boolean retryable = (Boolean) context.get(FilterConstants.RETRYABLE_KEY); + + Instance instance = polarisProviderDiscovery.selectProvider(peerNamespace, serviceId); + if (instance == null) { + // skip later execution + context.setSendZuulResponse(false); + + context.setResponseStatusCode(HttpStatus.BAD_GATEWAY.value()); + context.getResponse().getWriter().write(Consts.QUOTA_LIMITED_INFO + "no providers"); + + return null; + } + + uri = handleUri(instance, proxy, uri); + CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); + setResponse(response); + + // skip later execution + context.setSendZuulResponse(false); + } catch (Exception ex) { + throw new ZuulRuntimeException(handleException(ex)); + } + return null; + } + + protected String getVerb(HttpServletRequest request) { + String sMethod = request.getMethod(); + return sMethod.toUpperCase(); + } + + protected InputStream getRequestBody(HttpServletRequest request) { + InputStream requestEntity = null; + try { + requestEntity = (InputStream) RequestContext.getCurrentContext().get(FilterConstants.REQUEST_ENTITY_KEY); + if (requestEntity == null) { + requestEntity = request.getInputStream(); + } + } catch (IOException ex) { + LOG.error("error during getRequestBody", ex); + } + return requestEntity; + } + + // Get the header value as a long in order to more correctly proxy very large requests + protected long getContentLength(HttpServletRequest request) { + if (useServlet31) { + return request.getContentLengthLong(); + } + String contentLengthHeader = request.getHeader(HttpHeaders.CONTENT_LENGTH); + if (contentLengthHeader != null) { + try { + return Long.parseLong(contentLengthHeader); + } catch (NumberFormatException ignored) { + } + } + return request.getContentLength(); + } + + protected String handleUri(Instance instance, String prefix, String uri) { + if (!uri.startsWith("/")) { + uri = "/" + uri; + } + + // handle stripPrefix + if (uri.startsWith("/" + prefix) && this.properties.isStripPrefix()) { + uri = uri.substring(1 + prefix.length()); + } + + return "http://" + instance.getHost() + ":" + instance.getPort() + uri; + } + + protected CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb, String uri, + HttpServletRequest request, MultiValueMap headers, + MultiValueMap params, InputStream requestEntity) throws Exception { + URL host = new URL(uri); + HttpHost httpHost = getHttpHost(host); + long contentLength = getContentLength(request); + + ContentType contentType = null; + + if (request.getContentType() != null) { + contentType = ContentType.parse(request.getContentType()); + } + + InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength, contentType); + + HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params, request); + LOG.debug(httpHost.getHostName() + " " + httpHost.getPort() + " " + httpHost.getSchemeName()); + return forwardRequest(httpclient, httpHost, httpRequest); + } + + protected HttpHost getHttpHost(URL host) { + return new HttpHost(host.getHost(), host.getPort(), host.getProtocol()); + } + + protected HttpRequest buildHttpRequest(String verb, String uri, InputStreamEntity entity, + MultiValueMap headers, MultiValueMap params, + HttpServletRequest request) { + HttpRequest httpRequest; + String uriWithQueryString = uri + (this.forceOriginalQueryStringEncoding ? getEncodedQueryString(request) + : this.helper.getQueryString(params)); + + switch (verb.toUpperCase()) { + case "POST": + HttpPost httpPost = new HttpPost(uriWithQueryString); + httpRequest = httpPost; + httpPost.setEntity(entity); + break; + case "PUT": + HttpPut httpPut = new HttpPut(uriWithQueryString); + httpRequest = httpPut; + httpPut.setEntity(entity); + break; + case "PATCH": + HttpPatch httpPatch = new HttpPatch(uriWithQueryString); + httpRequest = httpPatch; + httpPatch.setEntity(entity); + break; + case "DELETE": + BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest(verb, uriWithQueryString); + httpRequest = entityRequest; + entityRequest.setEntity(entity); + break; + default: + httpRequest = new BasicHttpRequest(verb, uriWithQueryString); + LOG.debug(uriWithQueryString); + } + + httpRequest.setHeaders(convertHeaders(headers)); + + return httpRequest; + } + + protected String getEncodedQueryString(HttpServletRequest request) { + String query = request.getQueryString(); + return (query != null) ? "?" + query : ""; + } + + protected Header[] convertHeaders(MultiValueMap headers) { + List
list = new ArrayList<>(); + for (String name : headers.keySet()) { + for (String value : headers.get(name)) { + list.add(new BasicHeader(name, value)); + } + } + return list.toArray(new BasicHeader[0]); + } + + protected MultiValueMap revertHeaders(Header[] headers) { + MultiValueMap map = new LinkedMultiValueMap<>(); + for (Header header : headers) { + String name = header.getName(); + if (!map.containsKey(name)) { + map.put(name, new ArrayList<>()); + } + map.get(name).add(header.getValue()); + } + return map; + } + + protected CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient, HttpHost httpHost, + HttpRequest httpRequest) throws IOException { + return httpclient.execute(httpHost, httpRequest); + } + + protected void setResponse(HttpResponse response) throws IOException { + RequestContext.getCurrentContext().set("zuulResponse", response); + this.helper.setResponse(response.getStatusLine().getStatusCode(), + response.getEntity() == null ? null : response.getEntity().getContent(), + revertHeaders(response.getAllHeaders())); + } + + protected ZuulException handleException(Exception ex) { + int statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value(); + Throwable cause = ex; + String message = ex.getMessage(); + + ClientException clientException = findClientException(ex); + + if (clientException != null) { + if (clientException + .getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) { + statusCode = HttpStatus.SERVICE_UNAVAILABLE.value(); + } + cause = clientException; + message = clientException.getErrorType().toString(); + } + return new ZuulException(cause, "Forwarding error", statusCode, message); + } + + protected ClientException findClientException(Throwable t) { + if (t == null) { + return null; + } + if (t instanceof ClientException) { + return (ClientException) t; + } + return findClientException(t.getCause()); + } +} \ No newline at end of file