support zuul polaris remote process call

pull/15/head
hongyihui 4 years ago
parent 3fcfbbb820
commit 26481c13de

@ -18,3 +18,4 @@ zuul:
GatewayCalleeService:
serviceId: GatewayCalleeService
path: /GatewayCalleeService/**
# stripPrefix: true

@ -29,6 +29,11 @@
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-ratelimit</artifactId>
</dependency>
<dependency>
<groupId>com.tencent.cloud</groupId>
<artifactId>spring-cloud-starter-tencent-polaris-discovery</artifactId>
</dependency>
<!-- Spring Cloud Tencent dependencies end -->
<dependency>

@ -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)

@ -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<String, List<Instance>> 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<String, ZuulProperties.ZuulRoute> zuulRouteMap = zuulProperties.getRoutes();
if (zuulRouteMap != null && zuulRouteMap.size() > 0) {
ConsumerAPI consumerAPI = DiscoveryAPIFactory.createConsumerAPI();
for (Map.Entry<String, ZuulProperties.ZuulRoute> 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<Instance> 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);
}
}

@ -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

@ -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<EnvironmentChangeEvent> {
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<String, String> headers = this.helper.buildZuulRequestHeaders(request);
MultiValueMap<String, String> 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<String, String> headers,
MultiValueMap<String, String> 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<String, String> headers, MultiValueMap<String, String> 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<String, String> headers) {
List<Header> 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<String, String> revertHeaders(Header[] headers) {
MultiValueMap<String, String> 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());
}
}
Loading…
Cancel
Save