diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java index 3ba90087e..feffd0f49 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/HeaderConstant.java @@ -37,6 +37,16 @@ public final class HeaderConstant { */ public static final String INTERNAL_CALLEE_SERVICE_ID = "internal-callee-serviceid"; + /** + * The name information of the called instance host. + */ + public static final String INTERNAL_CALLEE_INSTANCE_HOST = "internal-callee-instance-host"; + + /** + * The name information of the called instance port. + */ + public static final String INTERNAL_CALLEE_INSTANCE_PORT = "internal-callee-instance-port"; + private HeaderConstant() { } } diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index ab74ae73a..4266ab877 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -73,7 +73,7 @@ 1.11.0-2021.0.6-SNAPSHOT - 1.12.0-SNAPSHOT + 1.11.3 31.0.1-jre 1.2.11 4.5.1 diff --git a/spring-cloud-tencent-rpc-enhancement/pom.xml b/spring-cloud-tencent-rpc-enhancement/pom.xml index e566905be..bf566a053 100644 --- a/spring-cloud-tencent-rpc-enhancement/pom.xml +++ b/spring-cloud-tencent-rpc-enhancement/pom.xml @@ -50,6 +50,12 @@ true + + org.springframework.cloud + spring-cloud-gateway-server + true + + com.tencent.polaris polaris-test-common diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java index d23f7be94..a382f17ac 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/config/RpcEnhancementAutoConfiguration.java @@ -30,6 +30,8 @@ import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.ExceptionPolarisR import com.tencent.cloud.rpc.enhancement.feign.plugin.reporter.SuccessPolarisReporter; import com.tencent.cloud.rpc.enhancement.resttemplate.BlockingLoadBalancerClientAspect; import com.tencent.cloud.rpc.enhancement.resttemplate.EnhancedRestTemplateReporter; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpHeadersFilter; +import com.tencent.cloud.rpc.enhancement.scg.EnhancedPolarisHttpClientCustomizer; import com.tencent.cloud.rpc.enhancement.webclient.EnhancedWebClientReporter; import com.tencent.polaris.api.core.ConsumerAPI; import com.tencent.polaris.client.api.SDKContext; @@ -44,6 +46,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.gateway.config.HttpClientCustomizer; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -156,6 +160,27 @@ public class RpcEnhancementAutoConfiguration { RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { return new EnhancedWebClientReporter(properties, consumerAPI); } + } + + /** + * Configuration for Polaris {@link org.springframework.web.reactive.function.client.WebClient} which can automatically bring in the call + * results for reporting. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(name = "org.springframework.cloud.gateway.config.GatewayAutoConfiguration") + @Role(RootBeanDefinition.ROLE_INFRASTRUCTURE) + protected static class PolarisGatewayAutoConfiguration { + + @Bean + public HttpHeadersFilter enhancedPolarisHttpHeadersFilter() { + return new EnhancedPolarisHttpHeadersFilter(); + } + + @Bean + public HttpClientCustomizer httpClientCustomizer( + RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + return new EnhancedPolarisHttpClientCustomizer(properties, context, consumerAPI); + } } } diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java new file mode 100644 index 000000000..f83ba6f1d --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClient.java @@ -0,0 +1,178 @@ +/* + * 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.rpc.enhancement.scg; + +import java.net.SocketTimeoutException; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; + +import com.tencent.cloud.common.constant.HeaderConstant; +import com.tencent.cloud.common.metadata.MetadataContext; +import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.api.pojo.RetStatus; +import com.tencent.polaris.api.pojo.ServiceKey; +import com.tencent.polaris.api.rpc.ServiceCallResult; +import com.tencent.polaris.client.api.SDKContext; +import io.netty.handler.codec.http.HttpHeaders; +import org.apache.commons.lang.StringUtils; +import reactor.netty.Connection; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientConfig; +import reactor.netty.http.client.HttpClientRequest; +import reactor.netty.http.client.HttpClientResponse; + +import org.springframework.http.HttpStatus; + +public class EnhancedPolarisHttpClient extends HttpClient { + + private final RpcEnhancementReporterProperties properties; + private final SDKContext context; + private final ConsumerAPI consumerAPI; + private final Reporter adapter; + private final BiConsumer handler = new BiConsumer() { + @Override + public void accept(HttpClientResponse httpClientResponse, Throwable throwable) { + if (Objects.isNull(consumerAPI)) { + return; + } + HttpHeaders responseHeaders = httpClientResponse.responseHeaders(); + + ServiceCallResult result = new ServiceCallResult(); + result.setCallerService(new ServiceKey(MetadataContext.LOCAL_NAMESPACE, MetadataContext.LOCAL_SERVICE)); + result.setNamespace(MetadataContext.LOCAL_NAMESPACE); + + Map metadata = MetadataContextHolder.get().getLoadbalancerMetadata(); + result.setDelay(System.currentTimeMillis() - Long.parseLong(metadata.get("startTime"))); + result.setService(metadata.get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID)); + result.setHost(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST)); + result.setPort(Integer.parseInt(metadata.get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT))); + RetStatus status = RetStatus.RetSuccess; + if (Objects.isNull(throwable)) { + if (EnhancedPolarisHttpClient.this.adapter.apply(HttpStatus.valueOf(httpClientResponse.status() + .code()))) { + status = RetStatus.RetFail; + } + status = getRetStatusFromRequest(responseHeaders, status); + } + else { + if (throwable instanceof SocketTimeoutException) { + status = RetStatus.RetTimeout; + } + } + result.setMethod(httpClientResponse.uri()); + result.setRetCode(httpClientResponse.status().code()); + result.setRuleName(getActiveRuleNameFromRequest(responseHeaders)); + result.setRetStatus(status); + if (Objects.nonNull(context)) { + result.setCallerIp(context.getConfig().getGlobal().getAPI().getBindIP()); + } + consumerAPI.updateServiceCallResult(result); + } + }; + private HttpClient target; + + public EnhancedPolarisHttpClient( + HttpClient client, + RpcEnhancementReporterProperties properties, + SDKContext context, + ConsumerAPI consumerAPI) { + this.properties = properties; + this.context = context; + this.consumerAPI = consumerAPI; + this.target = client; + this.adapter = new Reporter(properties); + this.registerReportHandler(); + } + + private static RetStatus getRetStatusFromRequest(HttpHeaders headers, RetStatus defaultVal) { + if (headers.contains(HeaderConstant.INTERNAL_CALLEE_RET_STATUS)) { + String retStatusVal = headers.get(HeaderConstant.INTERNAL_CALLEE_RET_STATUS); + if (Objects.equals(retStatusVal, RetStatus.RetFlowControl.getDesc())) { + return RetStatus.RetFlowControl; + } + if (Objects.equals(retStatusVal, RetStatus.RetReject.getDesc())) { + return RetStatus.RetReject; + } + } + return defaultVal; + } + + private static String getActiveRuleNameFromRequest(HttpHeaders headers) { + if (headers.contains(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME)) { + String val = headers.get(HeaderConstant.INTERNAL_ACTIVE_RULE_NAME); + return val; + } + return ""; + } + + @Override + public HttpClientConfig configuration() { + return target.configuration(); + } + + @Override + protected HttpClient duplicate() { + return new EnhancedPolarisHttpClient(target, properties, context, consumerAPI); + } + + private void registerReportHandler() { + target = target.doOnRequest(new BiConsumer() { + @Override + public void accept(HttpClientRequest request, Connection connection) { + String serviceId = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID); + String host = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST); + String port = request.requestHeaders().get(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT); + if (StringUtils.isNotBlank(serviceId)) { + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, serviceId); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, host); + MetadataContextHolder.get().setLoadbalancer(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, port); + MetadataContextHolder.get().setLoadbalancer("startTime", System.currentTimeMillis() + ""); + } + + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_SERVICE_ID); + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST); + request.requestHeaders().remove(HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT); + } + }); + target = target.doOnResponse((httpClientResponse, connection) -> handler.accept(httpClientResponse, null)); + target = target.doOnResponseError(handler); + } + + + private static class Reporter extends AbstractPolarisReporterAdapter { + + /** + * Constructor With {@link RpcEnhancementReporterProperties} . + * + * @param reportProperties instance of {@link RpcEnhancementReporterProperties}. + */ + protected Reporter(RpcEnhancementReporterProperties reportProperties) { + super(reportProperties); + } + + @Override + public boolean apply(HttpStatus httpStatus) { + return super.apply(httpStatus); + } + } + +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java new file mode 100644 index 000000000..630feb349 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpClientCustomizer.java @@ -0,0 +1,43 @@ +/* + * 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.rpc.enhancement.scg; + +import com.tencent.cloud.rpc.enhancement.config.RpcEnhancementReporterProperties; +import com.tencent.polaris.api.core.ConsumerAPI; +import com.tencent.polaris.client.api.SDKContext; +import reactor.netty.http.client.HttpClient; + +import org.springframework.cloud.gateway.config.HttpClientCustomizer; + +public class EnhancedPolarisHttpClientCustomizer implements HttpClientCustomizer { + + private final RpcEnhancementReporterProperties properties; + private final SDKContext context; + private final ConsumerAPI consumerAPI; + + public EnhancedPolarisHttpClientCustomizer(RpcEnhancementReporterProperties properties, SDKContext context, ConsumerAPI consumerAPI) { + this.properties = properties; + this.context = context; + this.consumerAPI = consumerAPI; + } + + @Override + public HttpClient customize(HttpClient httpClient) { + return new EnhancedPolarisHttpClient(httpClient, properties, context, consumerAPI); + } +} diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java new file mode 100644 index 000000000..a251b67a1 --- /dev/null +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/scg/EnhancedPolarisHttpHeadersFilter.java @@ -0,0 +1,71 @@ +/* + * 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.rpc.enhancement.scg; + +import java.util.List; + +import com.tencent.cloud.common.constant.HeaderConstant; + +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.Response; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; + +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; + +public class EnhancedPolarisHttpHeadersFilter implements HttpHeadersFilter { + + public EnhancedPolarisHttpHeadersFilter() { + } + + @Override + public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { + Response serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR); + if (serviceInstanceResponse == null || !serviceInstanceResponse.hasServer()) { + return input; + } + ServiceInstance instance = serviceInstanceResponse.getServer(); + write(input, HeaderConstant.INTERNAL_CALLEE_SERVICE_ID, instance.getServiceId(), true); + write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_HOST, instance.getHost(), true); + write(input, HeaderConstant.INTERNAL_CALLEE_INSTANCE_PORT, instance.getPort() + "", true); + return input; + } + + @Override + public boolean supports(Type type) { + return Type.REQUEST.equals(type); + } + + private void write(HttpHeaders headers, String name, String value, boolean append) { + if (value == null) { + return; + } + if (append) { + headers.add(name, value); + // these headers should be treated as a single comma separated header + List values = headers.get(name); + String delimitedValue = StringUtils.collectionToCommaDelimitedString(values); + headers.set(name, delimitedValue); + } + else { + headers.set(name, value); + } + } +}