From 546a6072c7304202d1b1d7a13f60e81635166271 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Mon, 10 Nov 2025 17:14:10 +0800 Subject: [PATCH] feat:support async metadata transfer. (#1744) Signed-off-by: Haotian Zhang <928016560@qq.com> --- CHANGELOG.md | 1 + .../MetadataTransferAutoConfiguration.java | 9 +- .../DecodeTransferMetadataReactiveFilter.java | 16 +- .../DecodeTransferMetadataServletFilter.java | 10 +- .../pojo/SnapshotHttpServletRequest.java | 510 ++++++++++++++++++ .../pojo/SnapshotServerHttpRequest.java | 253 +++++++++ .../FeignRequestTemplateMetadataProvider.java | 4 +- .../provider/ReactiveMetadataProvider.java | 42 +- .../RestTemplateMetadataProvider.java | 4 +- .../provider/ServletMetadataProvider.java | 58 +- ...MetadataTransferAutoConfigurationTest.java | 7 +- ...odeTransferMetadataReactiveFilterTest.java | 6 +- ...java => ReactiveMetadataProviderTest.java} | 68 +-- .../provider/ServletMetadataProviderTest.java | 138 +++++ .../common/async/PolarisAsyncProperties.java | 49 ++ ...larisAsyncPropertiesAutoConfiguration.java | 36 ++ .../config/MetadataLocalProperties.java | 10 + .../util/PolarisCompletableFutureUtils.java | 6 + .../main/resources/META-INF/spring.factories | 4 +- ...sAsyncPropertiesAutoConfigurationTest.java | 83 +++ .../async/PolarisAsyncPropertiesTest.java | 53 ++ .../config/MetadataLocalPropertiesTest.java | 15 +- .../PolarisCompletableFutureUtilsTest.java | 46 ++ .../src/test/resources/application-test.yml | 2 + spring-cloud-tencent-dependencies/pom.xml | 2 +- .../caller/QuickstartCallerController.java | 49 ++ 26 files changed, 1391 insertions(+), 90 deletions(-) create mode 100644 spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotHttpServletRequest.java create mode 100644 spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotServerHttpRequest.java rename spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/{MetadataProviderTest.java => ReactiveMetadataProviderTest.java} (61%) create mode 100644 spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ServletMetadataProviderTest.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncProperties.java create mode 100644 spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfiguration.java create mode 100644 spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfigurationTest.java create mode 100644 spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2335e4f51..f30a948bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ - [feat: upgrade springdoc to 1.8.0.](https://github.com/Tencent/spring-cloud-tencent/pull/1737) - [refactor:optimize auto configuration.](https://github.com/Tencent/spring-cloud-tencent/pull/1740) - [refactor:optimize config locate.](https://github.com/Tencent/spring-cloud-tencent/pull/1742) +- [feat:support async metadata transfer.](https://github.com/Tencent/spring-cloud-tencent/pull/1744) diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java index 8df293f1c..6720f1fc9 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfiguration.java @@ -17,6 +17,7 @@ package com.tencent.cloud.metadata.config; +import com.tencent.cloud.common.async.PolarisAsyncProperties; import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.metadata.core.DecodeTransferMetadataReactiveFilter; import com.tencent.cloud.metadata.core.DecodeTransferMetadataServletFilter; @@ -64,8 +65,8 @@ public class MetadataTransferAutoConfiguration { } @Bean - public DecodeTransferMetadataServletFilter metadataServletFilter() { - return new DecodeTransferMetadataServletFilter(); + public DecodeTransferMetadataServletFilter metadataServletFilter(PolarisAsyncProperties polarisAsyncProperties) { + return new DecodeTransferMetadataServletFilter(polarisAsyncProperties); } } @@ -77,8 +78,8 @@ public class MetadataTransferAutoConfiguration { protected static class MetadataReactiveFilterConfig { @Bean - public DecodeTransferMetadataReactiveFilter metadataReactiveFilter() { - return new DecodeTransferMetadataReactiveFilter(); + public DecodeTransferMetadataReactiveFilter metadataReactiveFilter(PolarisAsyncProperties polarisAsyncProperties) { + return new DecodeTransferMetadataReactiveFilter(polarisAsyncProperties); } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java index 46924062d..3956d8701 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilter.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import com.tencent.cloud.common.async.PolarisAsyncProperties; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.metadata.MetadataContext; @@ -56,6 +57,12 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataReactiveFilter.class); + private final PolarisAsyncProperties polarisAsyncProperties; + + public DecodeTransferMetadataReactiveFilter(PolarisAsyncProperties polarisAsyncProperties) { + this.polarisAsyncProperties = polarisAsyncProperties; + } + @Override public int getOrder() { return OrderConstant.Server.Reactive.DECODE_TRANSFER_METADATA_FILTER_ORDER; @@ -106,7 +113,8 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered } }).build(); // message metadata - ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, callerIp.get()); + ReactiveMetadataProvider callerMessageMetadataProvider = new ReactiveMetadataProvider(serverHttpRequest, + callerIp.get(), polarisAsyncProperties.getEnabled()); MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider); @@ -115,10 +123,12 @@ public class DecodeTransferMetadataReactiveFilter implements WebFilter, Ordered MetadataConstant.HeaderName.METADATA_CONTEXT, MetadataContextHolder.get()); - String targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.NAMESPACE); + String targetNamespace = serverWebExchange.getRequest().getHeaders() + .getFirst(MetadataConstant.HeaderName.NAMESPACE); // Compatible with TSF if (StringUtils.isBlank(targetNamespace)) { - targetNamespace = serverWebExchange.getRequest().getHeaders().getFirst(MetadataConstant.HeaderName.TSF_NAMESPACE_ID); + targetNamespace = serverWebExchange.getRequest().getHeaders() + .getFirst(MetadataConstant.HeaderName.TSF_NAMESPACE_ID); } if (StringUtils.isNotBlank(targetNamespace)) { diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java index 582f1ece8..f7dffe531 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataServletFilter.java @@ -27,6 +27,7 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.tencent.cloud.common.async.PolarisAsyncProperties; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.metadata.MetadataContextHolder; @@ -58,6 +59,12 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter { private static final Logger LOG = LoggerFactory.getLogger(DecodeTransferMetadataServletFilter.class); + private final PolarisAsyncProperties polarisAsyncProperties; + + public DecodeTransferMetadataServletFilter(PolarisAsyncProperties polarisAsyncProperties) { + this.polarisAsyncProperties = polarisAsyncProperties; + } + @Override protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, FilterChain filterChain) @@ -100,7 +107,8 @@ public class DecodeTransferMetadataServletFilter extends OncePerRequestFilter { // add headers httpServletRequest = new HttpServletRequestHeaderWrapper(httpServletRequest, addHeaders); // message metadata - ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, callerIp.get()); + ServletMetadataProvider callerMessageMetadataProvider = new ServletMetadataProvider(httpServletRequest, + callerIp.get(), polarisAsyncProperties.getEnabled()); MetadataContextHolder.init(mergedTransitiveMetadata, mergedDisposableMetadata, mergedApplicationMetadata, callerMessageMetadataProvider); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotHttpServletRequest.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotHttpServletRequest.java new file mode 100644 index 000000000..368dbb305 --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotHttpServletRequest.java @@ -0,0 +1,510 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.metadata.pojo; + +import java.io.BufferedReader; +import java.io.IOException; +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +import org.springframework.http.HttpHeaders; + +/** + * Snapshot of HttpServletRequest. + * + * @author Haotian Zhang + */ +public final class SnapshotHttpServletRequest implements HttpServletRequest { + + /** + * HTTP method. + */ + private final String method; + + /** + * Request URI. + */ + private final String requestURI; + + /** + * Query string. + */ + private final String queryString; + + /** + * HTTP headers. + */ + private final HttpHeaders headers; + + /** + * HTTP cookies. + */ + private final Cookie[] cookies; + + private SnapshotHttpServletRequest(Builder builder) { + this.method = builder.method; + this.requestURI = builder.requestURI; + this.queryString = builder.queryString; + this.headers = HttpHeaders.readOnlyHttpHeaders(builder.headers); + this.cookies = builder.cookies != null ? builder.cookies.clone() : null; + } + + /** + * create SnapshotHttpServletRequest from HttpServletRequest. + * + * @param request original request + * @return snapshot + */ + public static SnapshotHttpServletRequest from(HttpServletRequest request) { + Builder builder = new Builder() + .method(request.getMethod()) + .requestURI(request.getRequestURI()) + .queryString(request.getQueryString()) + .cookies(request.getCookies()); + + Enumeration headerNames = request.getHeaderNames(); + if (headerNames != null) { + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + Enumeration headerValues = request.getHeaders(headerName); + if (headerValues != null) { + while (headerValues.hasMoreElements()) { + builder.header(headerName, headerValues.nextElement()); + } + } + } + } + + return builder.build(); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public String getMethod() { + return method; + } + + @Override + public String getRequestURI() { + return requestURI; + } + + @Override + public String getQueryString() { + return queryString; + } + + @Override + public String getHeader(String name) { + return headers.getFirst(name); + } + + @Override + public Enumeration getHeaders(String name) { + List values = headers.get(name); + if (values != null && !values.isEmpty()) { + return Collections.enumeration(values); + } + return Collections.enumeration(Collections.emptyList()); + } + + @Override + public Enumeration getHeaderNames() { + return Collections.enumeration(headers.keySet()); + } + + @Override + public Cookie[] getCookies() { + return cookies != null ? cookies.clone() : null; + } + + // ========== Unsupported operations ========== + @Override + public String getAuthType() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getPathInfo() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getPathTranslated() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getContextPath() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getRemoteUser() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isUserInRole(String role) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Principal getUserPrincipal() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getRequestedSessionId() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public StringBuffer getRequestURL() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getServletPath() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public HttpSession getSession(boolean create) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public HttpSession getSession() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String changeSessionId() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isRequestedSessionIdValid() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isRequestedSessionIdFromURL() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public void login(String username, String password) throws ServletException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public void logout() throws ServletException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Collection getParts() throws IOException, ServletException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Part getPart(String name) throws IOException, ServletException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public T upgrade(Class handlerClass) throws IOException, ServletException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public long getContentLengthLong() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getParameter(String name) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Enumeration getParameterNames() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String[] getParameterValues(String name) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Map getParameterMap() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getProtocol() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getScheme() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getServerName() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public int getServerPort() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public BufferedReader getReader() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getRemoteAddr() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getRemoteHost() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public void setAttribute(String name, Object o) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public void removeAttribute(String name) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Locale getLocale() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Enumeration getLocales() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isSecure() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getRealPath(String path) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public int getRemotePort() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getLocalName() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getLocalAddr() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public int getLocalPort() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public ServletContext getServletContext() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isAsyncStarted() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public boolean isAsyncSupported() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public AsyncContext getAsyncContext() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public DispatcherType getDispatcherType() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Object getAttribute(String name) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public Enumeration getAttributeNames() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getCharacterEncoding() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public void setCharacterEncoding(String env) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public int getContentLength() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public String getContentType() { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public int getIntHeader(String name) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + @Override + public long getDateHeader(String name) { + throw new UnsupportedOperationException("Snapshot request does not support this operation"); + } + + public static class Builder { + private String method; + private String requestURI; + private String queryString; + private HttpHeaders headers = new HttpHeaders(); + private Cookie[] cookies; + + public Builder method(String method) { + this.method = method; + return this; + } + + public Builder requestURI(String requestURI) { + this.requestURI = requestURI; + return this; + } + + public Builder queryString(String queryString) { + this.queryString = queryString; + return this; + } + + public Builder header(String name, String value) { + if (name != null && value != null) { + this.headers.add(name, value); + } + return this; + } + + public Builder cookies(Cookie[] cookies) { + if (cookies != null) { + this.cookies = cookies.clone(); + } + return this; + } + + public SnapshotHttpServletRequest build() { + return new SnapshotHttpServletRequest(this); + } + } +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotServerHttpRequest.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotServerHttpRequest.java new file mode 100644 index 000000000..fa3f79b18 --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/pojo/SnapshotServerHttpRequest.java @@ -0,0 +1,253 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.metadata.pojo; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import reactor.core.publisher.Flux; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.server.RequestPath; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Snapshot of ServerHttpRequest. + * + * @author Haotian Zhang + */ +public final class SnapshotServerHttpRequest implements ServerHttpRequest { + + /** + * HTTP method. + */ + private final HttpMethod method; + + /** + * HTTP URI. + */ + private final URI uri; + + /** + * HTTP attributes. + */ + private final Map attributes; + + /** + * HTTP path. + */ + private final String path; + + /** + * HTTP headers. + */ + private final HttpHeaders headers; + + /** + * HTTP query params. + */ + private final MultiValueMap queryParams; + + /** + * HTTP cookies. + */ + private final MultiValueMap cookies; + + /** + * HTTP remote address. + */ + private final InetSocketAddress remoteAddress; + + /** + * HTTP local address. + */ + private final InetSocketAddress localAddress; + + /** + * HTTP request id. + */ + private final String id; + + private SnapshotServerHttpRequest(Builder builder) { + this.method = builder.method; + this.uri = builder.uri; + this.attributes = builder.attributes; + this.path = builder.path; + this.headers = HttpHeaders.readOnlyHttpHeaders(builder.headers); + this.queryParams = new LinkedMultiValueMap<>(builder.queryParams); + this.cookies = new LinkedMultiValueMap<>(builder.cookies); + this.remoteAddress = builder.remoteAddress; + this.localAddress = builder.localAddress; + this.id = builder.id; + } + + /** + * create SnapshotServerHttpRequest from ServerHttpRequest. + * + * @param request original request + * @return snapshot + */ + public static SnapshotServerHttpRequest from(ServerHttpRequest request) { + return new Builder() + .method(request.getMethod()) + .uri(request.getURI()) + .path(request.getPath().value()) + .headers(request.getHeaders()) + .queryParams(request.getQueryParams()) + .cookies(request.getCookies()) + .remoteAddress(request.getRemoteAddress()) + .localAddress(request.getLocalAddress()) + .id(request.getId()) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public String getMethodValue() { + return method.name(); + } + + @Override + public URI getURI() { + return uri; + } + + @Override + public RequestPath getPath() { + return RequestPath.parse(path, null); + } + + @Override + public HttpHeaders getHeaders() { + return headers; + } + + @Override + public MultiValueMap getQueryParams() { + return queryParams; + } + + @Override + public MultiValueMap getCookies() { + return cookies; + } + + @Override + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public InetSocketAddress getLocalAddress() { + return localAddress; + } + + @Override + public String getId() { + return id; + } + + @Override + public Flux getBody() { + throw new UnsupportedOperationException("Snapshot request does not support body access"); + } + + public static class Builder { + private HttpMethod method; + private URI uri; + private Map attributes = new HashMap<>(); + private String path; + private HttpHeaders headers = new HttpHeaders(); + private MultiValueMap queryParams = new LinkedMultiValueMap<>(); + private MultiValueMap cookies = new LinkedMultiValueMap<>(); + private InetSocketAddress remoteAddress; + private InetSocketAddress localAddress; + private String id; + + public Builder method(HttpMethod method) { + this.method = method; + return this; + } + + public Builder uri(URI uri) { + this.uri = uri; + return this; + } + + public Builder attributes(Map attributes) { + this.attributes = attributes; + return this; + } + + public Builder path(String path) { + this.path = path; + return this; + } + + public Builder headers(HttpHeaders headers) { + if (headers != null) { + this.headers = new HttpHeaders(); + this.headers.putAll(headers); + } + return this; + } + + public Builder queryParams(MultiValueMap queryParams) { + if (queryParams != null) { + this.queryParams = new LinkedMultiValueMap<>(queryParams); + } + return this; + } + + public Builder cookies(MultiValueMap cookies) { + if (cookies != null) { + this.cookies = new LinkedMultiValueMap<>(cookies); + } + return this; + } + + public Builder remoteAddress(InetSocketAddress remoteAddress) { + this.remoteAddress = remoteAddress; + return this; + } + + public Builder localAddress(InetSocketAddress localAddress) { + this.localAddress = localAddress; + return this; + } + + public Builder id(String id) { + this.id = id; + return this; + } + + public SnapshotServerHttpRequest build() { + return new SnapshotServerHttpRequest(this); + } + } +} diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/FeignRequestTemplateMetadataProvider.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/FeignRequestTemplateMetadataProvider.java index 2b412acae..59b343a05 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/FeignRequestTemplateMetadataProvider.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/FeignRequestTemplateMetadataProvider.java @@ -43,7 +43,7 @@ public class FeignRequestTemplateMetadataProvider implements MetadataProvider { } @Override - public String getRawMetadataStringValue(String key) { + public String doGetRawMetadataStringValue(String key) { switch (key) { case MessageMetadataContainer.LABEL_KEY_METHOD: return requestTemplate.method(); @@ -59,7 +59,7 @@ public class FeignRequestTemplateMetadataProvider implements MetadataProvider { } @Override - public String getRawMetadataMapValue(String key, String mapKey) { + public String doGetRawMetadataMapValue(String key, String mapKey) { Map> headers = requestTemplate.headers(); switch (key) { case MessageMetadataContainer.LABEL_MAP_KEY_HEADER: diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java index f483155ac..ad75ee5a1 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProvider.java @@ -19,6 +19,8 @@ package com.tencent.cloud.metadata.provider; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; +import com.tencent.cloud.metadata.pojo.SnapshotServerHttpRequest; +import com.tencent.polaris.annonation.JustForTest; import com.tencent.polaris.metadata.core.MessageMetadataContainer; import com.tencent.polaris.metadata.core.MetadataProvider; @@ -31,17 +33,26 @@ import org.springframework.http.server.reactive.ServerHttpRequest; */ public class ReactiveMetadataProvider implements MetadataProvider { - private ServerHttpRequest serverHttpRequest; + private final ServerHttpRequest serverHttpRequest; - private String callerIp; + private final String callerIp; public ReactiveMetadataProvider(ServerHttpRequest serverHttpRequest, String callerIp) { - this.serverHttpRequest = serverHttpRequest; + this(serverHttpRequest, callerIp, false); + } + + public ReactiveMetadataProvider(ServerHttpRequest serverHttpRequest, String callerIp, boolean isAsync) { + if (isAsync) { + this.serverHttpRequest = SnapshotServerHttpRequest.from(serverHttpRequest); + } + else { + this.serverHttpRequest = serverHttpRequest; + } this.callerIp = callerIp; } @Override - public String getRawMetadataStringValue(String key) { + public String doGetRawMetadataStringValue(String key) { switch (key) { case MessageMetadataContainer.LABEL_KEY_METHOD: return serverHttpRequest.getMethod().name(); @@ -55,16 +66,21 @@ public class ReactiveMetadataProvider implements MetadataProvider { } @Override - public String getRawMetadataMapValue(String key, String mapKey) { + public String doGetRawMetadataMapValue(String key, String mapKey) { switch (key) { - case MessageMetadataContainer.LABEL_MAP_KEY_HEADER: - return UrlUtils.decode(SpringWebExpressionLabelUtils.getHeaderValue(serverHttpRequest, mapKey, null)); - case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE: - return UrlUtils.decode(SpringWebExpressionLabelUtils.getCookieValue(serverHttpRequest, mapKey, null)); - case MessageMetadataContainer.LABEL_MAP_KEY_QUERY: - return UrlUtils.decode(SpringWebExpressionLabelUtils.getQueryValue(serverHttpRequest, mapKey, null)); - default: - return null; + case MessageMetadataContainer.LABEL_MAP_KEY_HEADER: + return UrlUtils.decode(SpringWebExpressionLabelUtils.getHeaderValue(serverHttpRequest, mapKey, null)); + case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE: + return UrlUtils.decode(SpringWebExpressionLabelUtils.getCookieValue(serverHttpRequest, mapKey, null)); + case MessageMetadataContainer.LABEL_MAP_KEY_QUERY: + return UrlUtils.decode(SpringWebExpressionLabelUtils.getQueryValue(serverHttpRequest, mapKey, null)); + default: + return null; } } + + @JustForTest + ServerHttpRequest getServerHttpRequest() { + return serverHttpRequest; + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/RestTemplateMetadataProvider.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/RestTemplateMetadataProvider.java index 216e62a17..108bff73d 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/RestTemplateMetadataProvider.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/RestTemplateMetadataProvider.java @@ -40,7 +40,7 @@ public class RestTemplateMetadataProvider implements MetadataProvider { } @Override - public String getRawMetadataStringValue(String key) { + public String doGetRawMetadataStringValue(String key) { switch (key) { case MessageMetadataContainer.LABEL_KEY_METHOD: return request.getMethod().toString(); @@ -55,7 +55,7 @@ public class RestTemplateMetadataProvider implements MetadataProvider { } @Override - public String getRawMetadataMapValue(String key, String mapKey) { + public String doGetRawMetadataMapValue(String key, String mapKey) { switch (key) { case MessageMetadataContainer.LABEL_MAP_KEY_HEADER: return UrlUtils.decode(SpringWebExpressionLabelUtils.getHeaderValue(request, mapKey)); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java index 3e4e2d8bb..05871112f 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/main/java/com/tencent/cloud/metadata/provider/ServletMetadataProvider.java @@ -21,6 +21,8 @@ import javax.servlet.http.HttpServletRequest; import com.tencent.cloud.common.util.UrlUtils; import com.tencent.cloud.common.util.expresstion.ExpressionLabelUtils; import com.tencent.cloud.common.util.expresstion.ServletExpressionLabelUtils; +import com.tencent.cloud.metadata.pojo.SnapshotHttpServletRequest; +import com.tencent.polaris.annonation.JustForTest; import com.tencent.polaris.metadata.core.MessageMetadataContainer; import com.tencent.polaris.metadata.core.MetadataProvider; @@ -32,40 +34,54 @@ import com.tencent.polaris.metadata.core.MetadataProvider; */ public class ServletMetadataProvider implements MetadataProvider { - private HttpServletRequest httpServletRequest; + private final HttpServletRequest httpServletRequest; - private String callerIp; + private final String callerIp; public ServletMetadataProvider(HttpServletRequest httpServletRequest, String callerIp) { - this.httpServletRequest = httpServletRequest; + this(httpServletRequest, callerIp, false); + } + + public ServletMetadataProvider(HttpServletRequest httpServletRequest, String callerIp, boolean isAsync) { + if (isAsync) { + this.httpServletRequest = SnapshotHttpServletRequest.from(httpServletRequest); + } + else { + this.httpServletRequest = httpServletRequest; + } this.callerIp = callerIp; } @Override - public String getRawMetadataStringValue(String key) { + public String doGetRawMetadataStringValue(String key) { switch (key) { - case MessageMetadataContainer.LABEL_KEY_METHOD: - return httpServletRequest.getMethod(); - case MessageMetadataContainer.LABEL_KEY_PATH: - return UrlUtils.decode(httpServletRequest.getRequestURI()); - case MessageMetadataContainer.LABEL_KEY_CALLER_IP: - return callerIp; - default: - return null; + case MessageMetadataContainer.LABEL_KEY_METHOD: + return httpServletRequest.getMethod(); + case MessageMetadataContainer.LABEL_KEY_PATH: + return UrlUtils.decode(httpServletRequest.getRequestURI()); + case MessageMetadataContainer.LABEL_KEY_CALLER_IP: + return callerIp; + default: + return null; } } @Override - public String getRawMetadataMapValue(String key, String mapKey) { + public String doGetRawMetadataMapValue(String key, String mapKey) { switch (key) { - case MessageMetadataContainer.LABEL_MAP_KEY_HEADER: - return UrlUtils.decode(httpServletRequest.getHeader(mapKey)); - case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE: - return UrlUtils.decode(ServletExpressionLabelUtils.getCookieValue(httpServletRequest.getCookies(), mapKey, null)); - case MessageMetadataContainer.LABEL_MAP_KEY_QUERY: - return UrlUtils.decode(ExpressionLabelUtils.getQueryValue(httpServletRequest.getQueryString(), mapKey, null)); - default: - return null; + case MessageMetadataContainer.LABEL_MAP_KEY_HEADER: + return UrlUtils.decode(httpServletRequest.getHeader(mapKey)); + case MessageMetadataContainer.LABEL_MAP_KEY_COOKIE: + return UrlUtils.decode(ServletExpressionLabelUtils.getCookieValue(httpServletRequest.getCookies(), mapKey, null)); + case MessageMetadataContainer.LABEL_MAP_KEY_QUERY: + return UrlUtils.decode(ExpressionLabelUtils.getQueryValue(httpServletRequest.getQueryString(), mapKey, null)); + default: + return null; } } + + @JustForTest + HttpServletRequest getHttpServletRequest() { + return httpServletRequest; + } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java index 7813e5add..dd1db2fbd 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/config/MetadataTransferAutoConfigurationTest.java @@ -17,6 +17,7 @@ package com.tencent.cloud.metadata.config; +import com.tencent.cloud.common.async.PolarisAsyncProperties; import com.tencent.cloud.metadata.core.EncodeTransferMedataFeignEnhancedPlugin; import com.tencent.cloud.metadata.core.EncodeTransferMedataWebClientEnhancedPlugin; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; @@ -61,7 +62,11 @@ public class MetadataTransferAutoConfigurationTest { */ @Test public void test2() { - this.reactiveWebApplicationContextRunner.withConfiguration(AutoConfigurations.of(MetadataTransferAutoConfiguration.class, PolarisContextProperties.class)) + this.reactiveWebApplicationContextRunner.withConfiguration( + AutoConfigurations.of( + MetadataTransferAutoConfiguration.class, + PolarisContextProperties.class, + PolarisAsyncProperties.class)) .run(context -> { assertThat(context).hasSingleBean(MetadataTransferAutoConfiguration.MetadataTransferFeignInterceptorConfig.class); assertThat(context).hasSingleBean(EncodeTransferMedataFeignEnhancedPlugin.class); diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java index d34635f4e..19460df0f 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/core/DecodeTransferMetadataReactiveFilterTest.java @@ -17,6 +17,7 @@ package com.tencent.cloud.metadata.core; +import com.tencent.cloud.common.async.PolarisAsyncProperties; import com.tencent.cloud.common.constant.MetadataConstant; import com.tencent.cloud.common.constant.OrderConstant; import com.tencent.cloud.common.metadata.config.MetadataLocalProperties; @@ -50,11 +51,14 @@ public class DecodeTransferMetadataReactiveFilterTest { @Autowired private MetadataLocalProperties metadataLocalProperties; + @Autowired + private PolarisAsyncProperties polarisAsyncProperties; + private DecodeTransferMetadataReactiveFilter metadataReactiveFilter; @BeforeEach public void setUp() { - this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(); + this.metadataReactiveFilter = new DecodeTransferMetadataReactiveFilter(polarisAsyncProperties); } @Test diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/MetadataProviderTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProviderTest.java similarity index 61% rename from spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/MetadataProviderTest.java rename to spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProviderTest.java index cc05fbee1..7bcc443a8 100644 --- a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/MetadataProviderTest.java +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ReactiveMetadataProviderTest.java @@ -18,28 +18,26 @@ package com.tencent.cloud.metadata.provider; import com.tencent.cloud.common.util.UrlUtils; +import com.tencent.cloud.metadata.pojo.SnapshotServerHttpRequest; import com.tencent.polaris.metadata.core.MessageMetadataContainer; import org.junit.jupiter.api.Test; import org.springframework.http.HttpCookie; -import org.springframework.http.HttpMethod; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.MockCookie; -import org.springframework.mock.web.MockHttpServletRequest; import static org.assertj.core.api.Assertions.assertThat; /** - * Test for {@link ReactiveMetadataProvider} and {@link ServletMetadataProvider}. + * Test for {@link ReactiveMetadataProvider}. * - * @author quan, Shedfree Wu + * @author Haotian Zhang, Shedfree Wu */ -public class MetadataProviderTest { +public class ReactiveMetadataProviderTest { private static final String notExistKey = "empty"; @Test - public void testReactiveMetadataProvider() { + public void testReactiveMetadataProviderWithoutAsync() { String headerKey1 = "header1"; String headerKey2 = "header2"; String headerValue1 = "value1"; @@ -64,6 +62,7 @@ public class MetadataProviderTest { .build(); ReactiveMetadataProvider reactiveMetadataProvider = new ReactiveMetadataProvider(request, callerIp); + assertThat(reactiveMetadataProvider.getServerHttpRequest()).isNotInstanceOf(SnapshotServerHttpRequest.class); assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1); assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2); // com.tencent.polaris.metadata.core.manager.ComposeMetadataProvider.getRawMetadataMapValue need return null when key don't exist @@ -89,7 +88,7 @@ public class MetadataProviderTest { } @Test - public void testServletMetadataProvider() { + public void testReactiveMetadataProviderWithAsync() { String headerKey1 = "header1"; String headerKey2 = "header2"; String headerValue1 = "value1"; @@ -104,35 +103,38 @@ public class MetadataProviderTest { String cookieValue2 = "cv2/test"; String path = "/echo/test"; String callerIp = "localhost"; - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader(headerKey1, headerValue1); - request.addHeader(headerKey2, UrlUtils.encode(headerValue2)); - request.setCookies(new MockCookie(cookieKey1, cookieValue1), new MockCookie(cookieKey2, UrlUtils.encode(cookieValue2))); - request.setMethod(HttpMethod.GET.name()); - request.setRequestURI(path); - request.setQueryString(queryKey1 + "=" + queryValue1 + "&" + queryKey2 + "=" + UrlUtils.encode(queryValue2)); - - ServletMetadataProvider servletMetadataProvider = new ServletMetadataProvider(request, callerIp); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2); + MockServerHttpRequest request = MockServerHttpRequest.get(path) + .header(headerKey1, headerValue1) + .header(headerKey2, UrlUtils.encode(headerValue2)) + .queryParam(queryKey1, queryValue1) + .queryParam(queryKey2, UrlUtils.encode(queryValue2)) + .cookie(new HttpCookie(cookieKey1, cookieValue1)) + .cookie(new HttpCookie(cookieKey2, UrlUtils.encode(cookieValue2))) + .build(); + + ReactiveMetadataProvider reactiveMetadataProvider = new ReactiveMetadataProvider(request, callerIp, true); + assertThat(reactiveMetadataProvider.getServerHttpRequest()).isInstanceOf(SnapshotServerHttpRequest.class); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2); // com.tencent.polaris.metadata.core.manager.ComposeMetadataProvider.getRawMetadataMapValue need return null when key don't exist - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, notExistKey)).isNull(); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, notExistKey)).isNull(); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey1)).isEqualTo(cookieValue1); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey2)).isEqualTo(cookieValue2); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, notExistKey)).isNull(); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey1)).isEqualTo(cookieValue1); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey2)).isEqualTo(cookieValue2); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, notExistKey)).isNull(); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey1)).isEqualTo(queryValue1); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey2)).isEqualTo(queryValue2); - assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, notExistKey)).isNull(); - assertThat(servletMetadataProvider.getRawMetadataMapValue(notExistKey, queryKey1)).isNull(); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey1)).isEqualTo(queryValue1); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey2)).isEqualTo(queryValue2); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, notExistKey)).isNull(); + assertThat(reactiveMetadataProvider.getRawMetadataMapValue(notExistKey, queryKey1)).isNull(); - assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_METHOD)).isEqualTo("GET"); - assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo(path); - assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_CALLER_IP)).isEqualTo(callerIp); - assertThat(servletMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull(); + assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_METHOD)).isEqualTo("GET"); + assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo(path); + assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_CALLER_IP)).isEqualTo(callerIp); + assertThat(reactiveMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull(); - request.setRequestURI("/echo/" + UrlUtils.decode("a@b")); - assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b"); + request = MockServerHttpRequest.get("/echo/" + UrlUtils.decode("a@b")).build(); + reactiveMetadataProvider = new ReactiveMetadataProvider(request, callerIp); + assertThat(reactiveMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b"); } } diff --git a/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ServletMetadataProviderTest.java b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ServletMetadataProviderTest.java new file mode 100644 index 000000000..5f760e21b --- /dev/null +++ b/spring-cloud-starter-tencent-metadata-transfer/src/test/java/com/tencent/cloud/metadata/provider/ServletMetadataProviderTest.java @@ -0,0 +1,138 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.metadata.provider; + +import com.tencent.cloud.common.util.UrlUtils; +import com.tencent.cloud.metadata.pojo.SnapshotHttpServletRequest; +import com.tencent.polaris.metadata.core.MessageMetadataContainer; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpMethod; +import org.springframework.mock.web.MockCookie; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link ServletMetadataProvider}. + * + * @author Haotian Zhang, Shedfree Wu + */ +public class ServletMetadataProviderTest { + + private static final String notExistKey = "empty"; + + @Test + public void testServletMetadataProviderWithoutAsync() { + String headerKey1 = "header1"; + String headerKey2 = "header2"; + String headerValue1 = "value1"; + String headerValue2 = "value2/test"; + String queryKey1 = "qk1"; + String queryKey2 = "qk2"; + String queryValue1 = "qv1"; + String queryValue2 = "qv2/test"; + String cookieKey1 = "ck1"; + String cookieKey2 = "ck2"; + String cookieValue1 = "cv1"; + String cookieValue2 = "cv2/test"; + String path = "/echo/test"; + String callerIp = "localhost"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(headerKey1, headerValue1); + request.addHeader(headerKey2, UrlUtils.encode(headerValue2)); + request.setCookies(new MockCookie(cookieKey1, cookieValue1), new MockCookie(cookieKey2, UrlUtils.encode(cookieValue2))); + request.setMethod(HttpMethod.GET.name()); + request.setRequestURI(path); + request.setQueryString(queryKey1 + "=" + queryValue1 + "&" + queryKey2 + "=" + UrlUtils.encode(queryValue2)); + + ServletMetadataProvider servletMetadataProvider = new ServletMetadataProvider(request, callerIp); + assertThat(servletMetadataProvider.getHttpServletRequest()).isNotInstanceOf(SnapshotHttpServletRequest.class); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2); + // com.tencent.polaris.metadata.core.manager.ComposeMetadataProvider.getRawMetadataMapValue need return null when key don't exist + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, notExistKey)).isNull(); + + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey1)).isEqualTo(cookieValue1); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey2)).isEqualTo(cookieValue2); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, notExistKey)).isNull(); + + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey1)).isEqualTo(queryValue1); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey2)).isEqualTo(queryValue2); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, notExistKey)).isNull(); + assertThat(servletMetadataProvider.getRawMetadataMapValue(notExistKey, queryKey1)).isNull(); + + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_METHOD)).isEqualTo("GET"); + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo(path); + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_CALLER_IP)).isEqualTo(callerIp); + assertThat(servletMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull(); + + request.setRequestURI("/echo/" + UrlUtils.decode("a@b")); + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b"); + } + + @Test + public void testServletMetadataProviderWithAsync() { + String headerKey1 = "header1"; + String headerKey2 = "header2"; + String headerValue1 = "value1"; + String headerValue2 = "value2/test"; + String queryKey1 = "qk1"; + String queryKey2 = "qk2"; + String queryValue1 = "qv1"; + String queryValue2 = "qv2/test"; + String cookieKey1 = "ck1"; + String cookieKey2 = "ck2"; + String cookieValue1 = "cv1"; + String cookieValue2 = "cv2/test"; + String path = "/echo/test"; + String callerIp = "localhost"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader(headerKey1, headerValue1); + request.addHeader(headerKey2, UrlUtils.encode(headerValue2)); + request.setCookies(new MockCookie(cookieKey1, cookieValue1), new MockCookie(cookieKey2, UrlUtils.encode(cookieValue2))); + request.setMethod(HttpMethod.GET.name()); + request.setRequestURI(path); + request.setQueryString(queryKey1 + "=" + queryValue1 + "&" + queryKey2 + "=" + UrlUtils.encode(queryValue2)); + + ServletMetadataProvider servletMetadataProvider = new ServletMetadataProvider(request, callerIp, true); + assertThat(servletMetadataProvider.getHttpServletRequest()).isInstanceOf(SnapshotHttpServletRequest.class); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey1)).isEqualTo(headerValue1); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, headerKey2)).isEqualTo(headerValue2); + // com.tencent.polaris.metadata.core.manager.ComposeMetadataProvider.getRawMetadataMapValue need return null when key don't exist + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_HEADER, notExistKey)).isNull(); + + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey1)).isEqualTo(cookieValue1); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, cookieKey2)).isEqualTo(cookieValue2); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_COOKIE, notExistKey)).isNull(); + + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey1)).isEqualTo(queryValue1); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, queryKey2)).isEqualTo(queryValue2); + assertThat(servletMetadataProvider.getRawMetadataMapValue(MessageMetadataContainer.LABEL_MAP_KEY_QUERY, notExistKey)).isNull(); + assertThat(servletMetadataProvider.getRawMetadataMapValue(notExistKey, queryKey1)).isNull(); + + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_METHOD)).isEqualTo("GET"); + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo(path); + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_CALLER_IP)).isEqualTo(callerIp); + assertThat(servletMetadataProvider.getRawMetadataStringValue(notExistKey)).isNull(); + + request.setRequestURI("/echo/" + UrlUtils.decode("a@b")); + servletMetadataProvider = new ServletMetadataProvider(request, callerIp, true); + assertThat(servletMetadataProvider.getRawMetadataStringValue(MessageMetadataContainer.LABEL_KEY_PATH)).isEqualTo("/echo/a@b"); + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncProperties.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncProperties.java new file mode 100644 index 000000000..85ed38840 --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncProperties.java @@ -0,0 +1,49 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.common.async; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Metadata Properties from local properties file. + * + * @author Haotian Zhang + */ +@ConfigurationProperties(prefix = "spring.cloud.tencent.async") +public class PolarisAsyncProperties { + + /** + * Enable async or not. + */ + private Boolean enabled = false; + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public String toString() { + return "PolarisAsyncProperties{" + + "enabled=" + enabled + + '}'; + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfiguration.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfiguration.java new file mode 100644 index 000000000..575c6d67f --- /dev/null +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfiguration.java @@ -0,0 +1,36 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.common.async; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Autoconfiguration of polaris async. + * + * @author Haotian Zhang + */ +@Configuration(proxyBeanMethods = false) +public class PolarisAsyncPropertiesAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public PolarisAsyncProperties polarisAsyncProperties() { + return new PolarisAsyncProperties(); + } +} diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataLocalProperties.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataLocalProperties.java index d1535a55c..5ae0af141 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataLocalProperties.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/metadata/config/MetadataLocalProperties.java @@ -96,4 +96,14 @@ public class MetadataLocalProperties { public void setHeaders(List headers) { this.headers = headers; } + + @Override + public String toString() { + return "MetadataLocalProperties{" + + "content=" + content + + ", transitive=" + transitive + + ", disposable=" + disposable + + ", headers=" + headers + + '}'; + } } diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtils.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtils.java index 17fc4e52f..dbbeebed2 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtils.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtils.java @@ -22,6 +22,8 @@ import java.util.function.Supplier; import com.tencent.polaris.threadlocal.cross.CompletableFutureUtils; +import org.springframework.util.Assert; + import static com.tencent.cloud.common.metadata.CrossThreadMetadataContext.CROSS_THREAD_METADATA_CONTEXT_CONSUMER; import static com.tencent.cloud.common.metadata.CrossThreadMetadataContext.CROSS_THREAD_METADATA_CONTEXT_SUPPLIER; @@ -36,10 +38,14 @@ public final class PolarisCompletableFutureUtils { } public static CompletableFuture supplyAsync(Supplier supplier) { + Assert.notNull(supplier, "Supplier must not be null"); + return CompletableFutureUtils.supplyAsync(supplier, CROSS_THREAD_METADATA_CONTEXT_SUPPLIER, CROSS_THREAD_METADATA_CONTEXT_CONSUMER); } public static CompletableFuture runAsync(Runnable runnable) { + Assert.notNull(runnable, "Runnable must not be null"); + return CompletableFutureUtils.runAsync(runnable, CROSS_THREAD_METADATA_CONTEXT_SUPPLIER, CROSS_THREAD_METADATA_CONTEXT_CONSUMER); } } diff --git a/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories b/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories index d99cbf89a..e962f0ff3 100644 --- a/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-tencent-commons/src/main/resources/META-INF/spring.factories @@ -2,6 +2,8 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.tencent.cloud.common.util.inet.PolarisInetUtilsAutoConfiguration,\ com.tencent.cloud.common.util.ApplicationContextAwareUtils,\ com.tencent.cloud.common.metadata.config.MetadataAutoConfiguration,\ - com.tencent.cloud.common.metadata.endpoint.PolarisMetadataEndpointAutoConfiguration + com.tencent.cloud.common.metadata.endpoint.PolarisMetadataEndpointAutoConfiguration,\ + com.tencent.cloud.common.async.PolarisAsyncPropertiesAutoConfiguration,\ + com.tencent.cloud.common.async.PolarisAsyncConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.tencent.cloud.common.util.inet.PolarisInetUtilsBootstrapConfiguration diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfigurationTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfigurationTest.java new file mode 100644 index 000000000..78232dc5d --- /dev/null +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesAutoConfigurationTest.java @@ -0,0 +1,83 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.common.async; + + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; + +/** + * Test for {@link PolarisAsyncPropertiesAutoConfiguration}. + * + * @author Haotian Zhang + */ +public class PolarisAsyncPropertiesAutoConfigurationTest { + + private final ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner().withPropertyValues( + "spring.application.name=test" + ); + + private final WebApplicationContextRunner webApplicationContextRunner = new WebApplicationContextRunner().withPropertyValues( + "spring.application.name=test" + ); + + private final ReactiveWebApplicationContextRunner reactiveWebApplicationContextRunner = new ReactiveWebApplicationContextRunner().withPropertyValues( + "spring.application.name=test" + ); + + /** + * No any web application. + */ + @Test + public void test1() { + this.applicationContextRunner + .withConfiguration(AutoConfigurations.of(PolarisAsyncPropertiesAutoConfiguration.class)) + .run(context -> { + Assertions.assertThat(context).hasSingleBean(PolarisAsyncProperties.class); + }); + } + + /** + * web application. + */ + @Test + public void test2() { + this.webApplicationContextRunner + .withConfiguration(AutoConfigurations.of(PolarisAsyncPropertiesAutoConfiguration.class)) + .run(context -> { + Assertions.assertThat(context).hasSingleBean(PolarisAsyncProperties.class); + }); + } + + /** + * reactive web application. + */ + @Test + public void test3() { + this.reactiveWebApplicationContextRunner + .withConfiguration(AutoConfigurations.of(PolarisAsyncPropertiesAutoConfiguration.class)) + .run(context -> { + Assertions.assertThat(context).hasSingleBean(PolarisAsyncProperties.class); + }); + } +} diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesTest.java new file mode 100644 index 000000000..c01af56c1 --- /dev/null +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/async/PolarisAsyncPropertiesTest.java @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making spring-cloud-tencent available. + * + * Copyright (C) 2021 Tencent. 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.common.async; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for {@link PolarisAsyncProperties}. + * + * @author Haotian Zhang + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + classes = PolarisAsyncPropertiesTest.TestApplication.class, + properties = {"spring.config.location = classpath:application-test.yml"}) +public class PolarisAsyncPropertiesTest { + + @Autowired + private PolarisAsyncProperties polarisAsyncProperties; + + @Test + public void test() { + assertThat(polarisAsyncProperties.getEnabled()).isTrue(); + } + + @SpringBootApplication + protected static class TestApplication { + + } +} diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataLocalPropertiesTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataLocalPropertiesTest.java index ef0327e01..c3c935a38 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataLocalPropertiesTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/metadata/config/MetadataLocalPropertiesTest.java @@ -17,7 +17,6 @@ package com.tencent.cloud.common.metadata.config; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,6 +25,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; +import static org.assertj.core.api.Assertions.assertThat; + /** * Test for {@link MetadataLocalProperties}. * @@ -42,20 +43,20 @@ public class MetadataLocalPropertiesTest { @Test public void test1() { - Assertions.assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); - Assertions.assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); - Assertions.assertThat(metadataLocalProperties.getContent().get("c")).isNull(); + assertThat(metadataLocalProperties.getContent().get("a")).isEqualTo("1"); + assertThat(metadataLocalProperties.getContent().get("b")).isEqualTo("2"); + assertThat(metadataLocalProperties.getContent().get("c")).isNull(); } @Test public void test2() { - Assertions.assertThat(metadataLocalProperties.getTransitive().contains("b")).isTrue(); + assertThat(metadataLocalProperties.getTransitive().contains("b")).isTrue(); } @Test public void test3() { - Assertions.assertThat(metadataLocalProperties.getHeaders().contains("c")).isTrue(); - Assertions.assertThat(metadataLocalProperties.getHeaders().contains("d")).isTrue(); + assertThat(metadataLocalProperties.getHeaders().contains("c")).isTrue(); + assertThat(metadataLocalProperties.getHeaders().contains("d")).isTrue(); } @SpringBootApplication diff --git a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtilsTest.java b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtilsTest.java index 70fd5fc2f..65226f105 100644 --- a/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtilsTest.java +++ b/spring-cloud-tencent-commons/src/test/java/com/tencent/cloud/common/util/PolarisCompletableFutureUtilsTest.java @@ -17,6 +17,8 @@ package com.tencent.cloud.common.util; +import java.util.concurrent.CompletableFuture; + import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import org.junit.jupiter.api.AfterEach; @@ -24,6 +26,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Test for {@link PolarisCompletableFutureUtils}. @@ -45,6 +48,22 @@ public class PolarisCompletableFutureUtilsTest { @Test public void testSupplyAsync() { + // can not be found and set + CompletableFuture.supplyAsync(() -> { + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key1")).isNull(); + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key2")).isNull(); + return "test"; + }).thenAccept(result -> { + assertThat(result).isEqualTo("test"); + }).join(); + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key1")).isEqualTo("value1"); + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key2")).isNull(); + + // can be found and set PolarisCompletableFutureUtils.supplyAsync(() -> { assertThat(MetadataContextHolder.get() .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key1")).isEqualTo("value1"); @@ -61,8 +80,28 @@ public class PolarisCompletableFutureUtilsTest { .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key2")).isEqualTo("value2"); } + @Test + public void testSupplyAsyncWithNullSupplier() { + assertThatThrownBy(() -> PolarisCompletableFutureUtils.supplyAsync(null)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Supplier must not be null"); + } + @Test public void testRunAsync() { + // can not be found and set + CompletableFuture.runAsync(() -> { + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key1")).isNull(); + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key2")).isNull(); + }).join(); + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key1")).isEqualTo("value1"); + assertThat(MetadataContextHolder.get() + .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key2")).isNull(); + + // can be found and set PolarisCompletableFutureUtils.runAsync(() -> { assertThat(MetadataContextHolder.get() .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key1")).isEqualTo("value1"); @@ -75,4 +114,11 @@ public class PolarisCompletableFutureUtilsTest { assertThat(MetadataContextHolder.get() .getContext(MetadataContext.FRAGMENT_TRANSITIVE, "key2")).isEqualTo("value3"); } + + @Test + public void testRunAsyncWithNullRunnable() { + assertThatThrownBy(() -> PolarisCompletableFutureUtils.runAsync(null)) + .isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Runnable must not be null"); + } } diff --git a/spring-cloud-tencent-commons/src/test/resources/application-test.yml b/spring-cloud-tencent-commons/src/test/resources/application-test.yml index 569c1aa81..2a9b4e313 100644 --- a/spring-cloud-tencent-commons/src/test/resources/application-test.yml +++ b/spring-cloud-tencent-commons/src/test/resources/application-test.yml @@ -17,3 +17,5 @@ spring: headers: - c - d + async: + enabled: true diff --git a/spring-cloud-tencent-dependencies/pom.xml b/spring-cloud-tencent-dependencies/pom.xml index d1256db95..24d557540 100644 --- a/spring-cloud-tencent-dependencies/pom.xml +++ b/spring-cloud-tencent-dependencies/pom.xml @@ -74,7 +74,7 @@ 2.1.1.0-2021.0.9-SNAPSHOT - 2.1.0.0 + 2.1.1.0-SNAPSHOT 1.78.1 diff --git a/spring-cloud-tencent-examples/quickstart-example/quickstart-caller-service/src/main/java/com/tencent/cloud/quickstart/caller/QuickstartCallerController.java b/spring-cloud-tencent-examples/quickstart-example/quickstart-caller-service/src/main/java/com/tencent/cloud/quickstart/caller/QuickstartCallerController.java index 97bf1abaf..28c70aaca 100644 --- a/spring-cloud-tencent-examples/quickstart-example/quickstart-caller-service/src/main/java/com/tencent/cloud/quickstart/caller/QuickstartCallerController.java +++ b/spring-cloud-tencent-examples/quickstart-example/quickstart-caller-service/src/main/java/com/tencent/cloud/quickstart/caller/QuickstartCallerController.java @@ -20,10 +20,12 @@ package com.tencent.cloud.quickstart.caller; import java.util.Collections; import java.util.Map; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; +import com.tencent.cloud.common.util.PolarisCompletableFutureUtils; import com.tencent.cloud.quickstart.caller.pojo.User; import com.tencent.polaris.api.utils.StringUtils; import org.slf4j.Logger; @@ -34,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -229,6 +232,52 @@ public class QuickstartCallerController { return restTemplate.getForObject(path, String.class); } + /** + * Get information of callee with async and delay. + * @param headerMap request headers + * @param delay delay time in milliseconds + * @return information of callee + */ + @GetMapping("/rest-async") + public ResponseEntity restAsync(@RequestHeader Map headerMap, + @RequestParam(defaultValue = "1000") long delay) { + String url = "http://QuickstartCalleeService/quickstart/callee/info"; + + HttpHeaders headers = new HttpHeaders(); + for (Map.Entry entry : headerMap.entrySet()) { + if (StringUtils.isNotBlank(entry.getKey()) && StringUtils.isNotBlank(entry.getValue()) + && !entry.getKey().contains("sct-") + && !entry.getKey().contains("SCT-") + && !entry.getKey().contains("polaris-") + && !entry.getKey().contains("POLARIS-")) { + headers.add(entry.getKey(), entry.getValue()); + } + } + + HttpEntity entity = new HttpEntity<>(headers); + + PolarisCompletableFutureUtils.runAsync(() -> { + try { + TimeUnit.MILLISECONDS.sleep(delay); + LOG.info("begin rest"); + ResponseEntity result = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + LOG.info("result: {}", result.getBody()); + } + catch (HttpClientErrorException | HttpServerErrorException httpClientErrorException) { + LOG.error("HttpClientErrorException: {}", httpClientErrorException.getResponseBodyAsString()); + } + catch (InterruptedException e) { + LOG.error("InterruptedException: {}", e.getMessage()); + } + catch (Throwable t) { + LOG.error("Throwable", t); + } + }); + + LOG.info("return ok"); + return new ResponseEntity<>("ok", HttpStatus.OK); + } + /** * Get information of callee. * @return information of callee