From 26481c13de6771e2d13c1cc5cf3fd39868ece9ae Mon Sep 17 00:00:00 2001
From: hongyihui <1023746826@qq.com>
Date: Fri, 3 Dec 2021 19:19:07 +0800
Subject: [PATCH] support zuul polaris remote process call
---
.../src/main/resources/application.yml | 1 +
.../pom.xml | 5 +
.../PolarisGatewayAutoConfiguration.java | 20 +
.../discovery/PolarisProviderDiscovery.java | 97 ++++
.../filter/Metadata2HeaderZuulFilter.java | 2 +-
.../zuul/filter/PolarisRpcZuulFilter.java | 442 ++++++++++++++++++
6 files changed, 566 insertions(+), 1 deletion(-)
create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/discovery/PolarisProviderDiscovery.java
create mode 100644 spring-cloud-tencent-starters/spring-cloud-tencent-polaris-gateway/src/main/java/com/tencent/cloud/polaris/gateway/core/zuul/filter/PolarisRpcZuulFilter.java
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