From d9cf9a030197c7dd76784207f76ec81041503bae Mon Sep 17 00:00:00 2001 From: Haotian Zhang <928016560@qq.com> Date: Sun, 9 Oct 2022 16:18:54 +0800 Subject: [PATCH] feat:report the labels when using RestTemplate. (#629) * feat:report the labels when using RestTemplate. * fix checkstyle error. --- CHANGELOG.md | 1 + .../polaris/router/PolarisRouterContext.java | 16 ++----- ...arisRouterServiceInstanceListSupplier.java | 12 ++--- .../feign/RouterLabelFeignInterceptor.java | 6 +-- .../MetadataRouterRequestInterceptor.java | 5 ++- .../RuleBasedRouterRequestInterceptor.java | 5 ++- .../RouterLabelRestTemplateInterceptor.java | 16 +++++-- ...larisReactiveLoadBalancerClientFilter.java | 6 +-- .../router/PolarisRouterContextTest.java | 31 ++++++------- ...RouterServiceInstanceListSupplierTest.java | 9 ++-- .../RouterLabelFeignInterceptorTest.java | 4 +- ...outerLabelRestTemplateInterceptorTest.java | 45 ++++++++++++++++--- ...sReactiveLoadBalancerClientFilterTest.java | 4 +- ...uterConstants.java => RouterConstant.java} | 14 ++++-- .../src/main/resources/bootstrap.yml | 6 +++ ...eatureEnvRouterRequestInterceptorTest.java | 10 +++-- .../feign/plugin/reporter/ReporterUtils.java | 4 +- .../EnhancedRestTemplateReporter.java | 29 ++++++++++-- .../plugin/reporter/ReporterUtilsTest.java | 4 +- 19 files changed, 151 insertions(+), 76 deletions(-) rename spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/{RouterConstants.java => RouterConstant.java} (74%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6e3bab3..cd8f8044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,4 @@ - [fix pr 606: modify a part of changes requested by review.](https://github.com/Tencent/spring-cloud-tencent/pull/620) - [fix pr 613: modify a judgment logic](https://github.com/Tencent/spring-cloud-tencent/pull/618) - [Feature: support new label expression](https://github.com/Tencent/spring-cloud-tencent/pull/627) +- [feat:report the labels when using RestTemplate.](https://github.com/Tencent/spring-cloud-tencent/pull/629) diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java index 25873f24..0061758f 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterContext.java @@ -25,6 +25,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import com.tencent.cloud.common.constant.RouterConstant; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -33,19 +34,10 @@ import org.springframework.util.LinkedCaseInsensitiveMap; /** * the context for router. * - *@author lepdou 2022-05-17 + * @author lepdou, Hoatian Zhang */ public class PolarisRouterContext { - /** - * the labels for rule router, contain transitive metadata. - */ - public static final String ROUTER_LABELS = "allMetadata"; - /** - * transitive labels. - */ - public static final String TRANSITIVE_LABELS = "transitiveMetadata"; - private Map> labels; public Map getLabels(String labelType) { @@ -80,7 +72,7 @@ public class PolarisRouterContext { } public String getLabel(String labelKey) { - Map routerLabels = labels.get(ROUTER_LABELS); + Map routerLabels = labels.get(RouterConstant.ROUTER_LABELS); if (CollectionUtils.isEmpty(routerLabels)) { return StringUtils.EMPTY; } @@ -88,7 +80,7 @@ public class PolarisRouterContext { } public Set getLabelAsSet(String labelKey) { - Map routerLabels = labels.get(ROUTER_LABELS); + Map routerLabels = labels.get(RouterConstant.ROUTER_LABELS); if (CollectionUtils.isEmpty(routerLabels)) { return Collections.emptySet(); } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java index bf3b06e7..77517f22 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplier.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.pojo.PolarisServiceInstance; @@ -59,11 +59,11 @@ import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; /** * Service routing entrance. - * + *

* Rule routing needs to rely on request parameters for server filtering. * The interface cannot obtain the context object of the request granularity, * so the routing capability cannot be achieved through ServerListFilter. - * + *

* And {@link PolarisRouterServiceInstanceListSupplier#get(Request)} provides the ability to pass in http headers, * so routing capabilities are implemented through IRule. * @@ -114,7 +114,7 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI //set method to public for unit test PolarisRouterContext buildRouterContext(HttpHeaders headers) { - Collection labelHeaderValues = headers.get(RouterConstants.ROUTER_LABEL_HEADER); + Collection labelHeaderValues = headers.get(RouterConstant.ROUTER_LABEL_HEADER); if (CollectionUtils.isEmpty(labelHeaderValues)) { return null; @@ -122,7 +122,7 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, MetadataContextHolder.get() + routerContext.putLabels(RouterConstant.TRANSITIVE_LABELS, MetadataContextHolder.get() .getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)); Map labelHeaderValuesMap = new HashMap<>(); @@ -137,7 +137,7 @@ public class PolarisRouterServiceInstanceListSupplier extends DelegatingServiceI catch (UnsupportedEncodingException e) { throw new RuntimeException("unsupported charset exception " + UTF_8); } - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labelHeaderValuesMap); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labelHeaderValuesMap); return routerContext; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java index cad34d33..3d396160 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptor.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.StaticMetadataManager; @@ -49,7 +49,7 @@ import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; /** * Resolver labels from request. * - *@author lepdou 2022-05-12 + * @author lepdou, Hoatian Zhang */ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered { private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelFeignInterceptor.class); @@ -121,7 +121,7 @@ public class RouterLabelFeignInterceptor implements RequestInterceptor, Ordered catch (UnsupportedEncodingException e) { throw new RuntimeException("unsupported charset exception " + UTF_8); } - requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, encodedLabelsContent); + requestTemplate.header(RouterConstant.ROUTER_LABEL_HEADER, encodedLabelsContent); } private Map getRuleExpressionLabels(RequestTemplate requestTemplate, Set labelKeys) { diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java index 9b9a78f2..54f91244 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/MetadataRouterRequestInterceptor.java @@ -21,6 +21,7 @@ package com.tencent.cloud.polaris.router.interceptor; import java.util.Map; import java.util.Set; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.config.properties.PolarisMetadataRouterProperties; import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; @@ -29,7 +30,7 @@ import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; /** * Router request interceptor for metadata router. - * @author lepdou 2022-07-06 + * @author lepdou, Hoatian Zhang */ public class MetadataRouterRequestInterceptor implements RouterRequestInterceptor { private static final String LABEL_KEY_METADATA_ROUTER_KEYS = "system-metadata-router-keys"; @@ -49,7 +50,7 @@ public class MetadataRouterRequestInterceptor implements RouterRequestIntercepto // 1. get metadata router label keys Set metadataRouterKeys = routerContext.getLabelAsSet(LABEL_KEY_METADATA_ROUTER_KEYS); // 2. get metadata router labels - Map metadataRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS, + Map metadataRouterLabels = routerContext.getLabels(RouterConstant.ROUTER_LABELS, metadataRouterKeys); // 3. set metadata router labels to request request.addRouterMetadata(MetadataRouter.ROUTER_TYPE_METADATA, metadataRouterLabels); diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java index 9abba566..cdc07f02 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/interceptor/RuleBasedRouterRequestInterceptor.java @@ -21,6 +21,7 @@ package com.tencent.cloud.polaris.router.interceptor; import java.util.HashMap; import java.util.Map; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.cloud.polaris.router.config.properties.PolarisRuleBasedRouterProperties; import com.tencent.cloud.polaris.router.spi.RouterRequestInterceptor; @@ -29,7 +30,7 @@ import com.tencent.polaris.router.api.rpc.ProcessRoutersRequest; /** * Router request interceptor for rule based router. - * @author lepdou 2022-07-06 + * @author lepdou, Hoatian Zhang */ public class RuleBasedRouterRequestInterceptor implements RouterRequestInterceptor { @@ -52,7 +53,7 @@ public class RuleBasedRouterRequestInterceptor implements RouterRequestIntercept // is placed in the metadata of the source service for transmission. // Later, can consider putting it in routerMetadata like other routers. if (ruleBasedRouterEnabled) { - Map ruleRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + Map ruleRouterLabels = routerContext.getLabels(RouterConstant.ROUTER_LABELS); request.getSourceService().setMetadata(ruleRouterLabels); } } diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptor.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptor.java index 42e7ad8f..93bfe10d 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptor.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptor.java @@ -27,9 +27,10 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.StaticMetadataManager; @@ -57,7 +58,7 @@ import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; * Interceptor used for adding the route label in http headers from context when web client * is RestTemplate. * - * @author liuye 2022-09-14 + * @author liuye, Hoatian Zhang */ public class RouterLabelRestTemplateInterceptor implements ClientHttpRequestInterceptor, Ordered { private static final Logger LOGGER = LoggerFactory.getLogger(RouterLabelRestTemplateInterceptor.class); @@ -99,7 +100,14 @@ public class RouterLabelRestTemplateInterceptor implements ClientHttpRequestInte setLabelsToHeaders(request, body, peerServiceName); - return clientHttpRequestExecution.execute(request, body); + ClientHttpResponse response = clientHttpRequestExecution.execute(request, body); + + if (!CollectionUtils.isEmpty(request.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER))) { + response.getHeaders().addAll(RouterConstant.ROUTER_LABEL_HEADER, Objects.requireNonNull(request.getHeaders() + .get(RouterConstant.ROUTER_LABEL_HEADER))); + } + + return response; } void setLabelsToHeaders(HttpRequest request, byte[] body, String peerServiceName) { @@ -143,7 +151,7 @@ public class RouterLabelRestTemplateInterceptor implements ClientHttpRequestInte catch (UnsupportedEncodingException e) { throw new RuntimeException("unsupported charset exception " + UTF_8); } - request.getHeaders().set(RouterConstants.ROUTER_LABEL_HEADER, encodedLabelsContent); + request.getHeaders().set(RouterConstant.ROUTER_LABEL_HEADER, encodedLabelsContent); } private Map getExpressionLabels(HttpRequest request, Set labelKeys) { diff --git a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java index a75c432e..74b254a1 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java +++ b/spring-cloud-starter-tencent-polaris-router/src/main/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilter.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.StaticMetadataManager; @@ -78,7 +78,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a * with PolarisReactiveLoadBalancerClientFilter. The passed route labels are used in * {@link PolarisRouterServiceInstanceListSupplier}. * - * @author lepdou 2022-06-20 + * @author lepdou, Hoatian Zhang */ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalancerClientFilter { private static final Logger log = LoggerFactory.getLogger(PolarisReactiveLoadBalancerClientFilter.class); @@ -210,7 +210,7 @@ public class PolarisReactiveLoadBalancerClientFilter extends ReactiveLoadBalance // the router label is passed through the http header uniformly instead of the original hint mechanism. HttpHeaders genRouterHttpHeaders(ServerWebExchange exchange, String peerServiceName) { HttpHeaders headers = new HttpHeaders(); - headers.add(RouterConstants.ROUTER_LABEL_HEADER, genRouterHint(exchange, peerServiceName)); + headers.add(RouterConstant.ROUTER_LABEL_HEADER, genRouterHint(exchange, peerServiceName)); return headers; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java index 356b3dbf..8b21c48c 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterContextTest.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; import com.google.common.collect.Sets; +import com.tencent.cloud.common.constant.RouterConstant; import org.junit.Assert; import org.junit.Test; @@ -40,28 +41,28 @@ public class PolarisRouterContextTest { labels.put("k2", "v2"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(2, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); - Assert.assertEquals("v1", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k1")); - Assert.assertEquals("v2", routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k2")); - Assert.assertNull(routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).get("k3")); + Assert.assertEquals(0, routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).size()); + Assert.assertEquals(2, routerContext.getLabels(RouterConstant.ROUTER_LABELS).size()); + Assert.assertEquals("v1", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k1")); + Assert.assertEquals("v2", routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k2")); + Assert.assertNull(routerContext.getLabels(RouterConstant.ROUTER_LABELS).get("k3")); } @Test public void testSetNull() { PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, null); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, null); + Assert.assertEquals(0, routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(RouterConstant.ROUTER_LABELS).size()); } @Test public void testGetEmptyRouterContext() { PolarisRouterContext routerContext = new PolarisRouterContext(); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.TRANSITIVE_LABELS).size()); - Assert.assertEquals(0, routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(RouterConstant.TRANSITIVE_LABELS).size()); + Assert.assertEquals(0, routerContext.getLabels(RouterConstant.ROUTER_LABELS).size()); } @Test @@ -72,9 +73,9 @@ public class PolarisRouterContextTest { labels.put("k3", "v3"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); - Map resolvedLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS, + Map resolvedLabels = routerContext.getLabels(RouterConstant.ROUTER_LABELS, Sets.newHashSet("k1", "k2", "k4")); Assert.assertEquals(2, resolvedLabels.size()); @@ -90,7 +91,7 @@ public class PolarisRouterContextTest { labels.put("k3", "v3"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); String resolvedLabel = routerContext.getLabel("k1"); @@ -103,7 +104,7 @@ public class PolarisRouterContextTest { labels.put("k1", "v1,v2,v3"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); Set resolvedLabels = routerContext.getLabelAsSet("k1"); diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java index effd2509..06399757 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/PolarisRouterServiceInstanceListSupplierTest.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.pojo.PolarisServiceInstance; @@ -115,10 +116,10 @@ public class PolarisRouterServiceInstanceListSupplierTest { ServiceInstances serviceInstances = assembleServiceInstances(); PolarisRouterContext routerContext = assembleRouterContext(); - Map oldRouterLabels = routerContext.getLabels(PolarisRouterContext.ROUTER_LABELS); + Map oldRouterLabels = routerContext.getLabels(RouterConstant.ROUTER_LABELS); Map newRouterLabels = new HashMap<>(oldRouterLabels); newRouterLabels.put("system-metadata-router-keys", "k2"); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, newRouterLabels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, newRouterLabels); ProcessRoutersRequest request = polarisSupplier.buildProcessRoutersRequest(serviceInstances, routerContext); polarisSupplier.processRouterRequestInterceptors(request, routerContext); @@ -246,8 +247,8 @@ public class PolarisRouterServiceInstanceListSupplierTest { Map routerLabels = new HashMap<>(); routerLabels.put("k2", "v2"); routerLabels.put("k3", "v3"); - routerContext.putLabels(PolarisRouterContext.TRANSITIVE_LABELS, transitiveLabels); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, routerLabels); + routerContext.putLabels(RouterConstant.TRANSITIVE_LABELS, transitiveLabels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, routerLabels); return routerContext; } diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java index 17b5fe2b..74ef3db2 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/feign/RouterLabelFeignInterceptorTest.java @@ -27,7 +27,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.StaticMetadataManager; @@ -118,7 +118,7 @@ public class RouterLabelFeignInterceptorTest { routerLabelFeignInterceptor.apply(requestTemplate); - Collection routerLabels = requestTemplate.headers().get(RouterConstants.ROUTER_LABEL_HEADER); + Collection routerLabels = requestTemplate.headers().get(RouterConstant.ROUTER_LABEL_HEADER); Assert.assertNotNull(routerLabels); for (String value : routerLabels) { diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptorTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptorTest.java index 9a2a61d8..25c10345 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptorTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/resttemplate/RouterLabelRestTemplateInterceptorTest.java @@ -19,20 +19,24 @@ package com.tencent.cloud.polaris.router.resttemplate; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; +import java.net.URLEncoder; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.StaticMetadataManager; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.cloud.common.util.JacksonUtils; +import com.tencent.cloud.common.util.expresstion.SpringWebExpressionLabelUtils; import com.tencent.cloud.polaris.context.config.PolarisContextProperties; import com.tencent.cloud.polaris.router.RouterRuleLabelResolver; import com.tencent.cloud.polaris.router.spi.SpringWebRouterLabelResolver; @@ -49,14 +53,22 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.client.MockClientHttpResponse; +import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * test for {@link RouterLabelRestTemplateInterceptor} - * @author liuye 2022-09-16 + * Test for {@link RouterLabelRestTemplateInterceptor}. + * + * @author liuye, Haotian Zhang */ @RunWith(MockitoJUnitRunner.class) public class RouterLabelRestTemplateInterceptorTest { @@ -72,6 +84,9 @@ public class RouterLabelRestTemplateInterceptorTest { @Mock private PolarisContextProperties polarisContextProperties; + @Mock + private ClientHttpRequestExecution clientHttpRequestExecution; + @BeforeClass public static void beforeClass() { mockedApplicationContextAwareUtils = Mockito.mockStatic(ApplicationContextAwareUtils.class); @@ -98,19 +113,21 @@ public class RouterLabelRestTemplateInterceptorTest { localMetadata.put("k1", "v1"); localMetadata.put("k2", "v2"); when(staticMetadataManager.getMergedStaticMetadata()).thenReturn(localMetadata); + Map routerLabels = new HashMap<>(localMetadata); // mock expression rule labels - Set expressionKeys = new HashSet<>(); expressionKeys.add("${http.method}"); expressionKeys.add("${http.uri}"); when(routerRuleLabelResolver.getExpressionLabelKeys(callerService, callerService, calleeService)).thenReturn(expressionKeys); + routerLabels.putAll(SpringWebExpressionLabelUtils.resolve(request, expressionKeys)); // mock custom resolved from request Map customResolvedLabels = new HashMap<>(); customResolvedLabels.put("k2", "v22"); customResolvedLabels.put("k4", "v4"); when(routerLabelResolver.resolve(request, null, expressionKeys)).thenReturn(customResolvedLabels); + routerLabels.putAll(customResolvedLabels); MetadataContext metadataContext = Mockito.mock(MetadataContext.class); @@ -119,26 +136,40 @@ public class RouterLabelRestTemplateInterceptorTest { transitiveLabels.put("k1", "v1"); transitiveLabels.put("k2", "v22"); when(metadataContext.getFragmentContext(MetadataContext.FRAGMENT_TRANSITIVE)).thenReturn(transitiveLabels); + routerLabels.putAll(transitiveLabels); mockedMetadataContextHolder.when(MetadataContextHolder::get).thenReturn(metadataContext); RouterLabelRestTemplateInterceptor routerLabelRestTemplateInterceptor = new RouterLabelRestTemplateInterceptor( Collections.singletonList(routerLabelResolver), staticMetadataManager, routerRuleLabelResolver, polarisContextProperties); - routerLabelRestTemplateInterceptor.setLabelsToHeaders(request, null, calleeService); + ClientHttpResponse mockedResponse = new MockClientHttpResponse(new byte[] {}, HttpStatus.OK); + when(clientHttpRequestExecution.execute(eq(request), any())).thenReturn(mockedResponse); + + routerLabelRestTemplateInterceptor.intercept(request, null, clientHttpRequestExecution); verify(staticMetadataManager).getMergedStaticMetadata(); verify(routerRuleLabelResolver).getExpressionLabelKeys(callerService, callerService, calleeService); verify(routerLabelResolver).resolve(request, null, expressionKeys); - Map headers = JacksonUtils.deserialize2Map(URLDecoder.decode(request.getHeaders() - .get(RouterConstants.ROUTER_LABEL_HEADER).get(0), "UTF-8")); + Map headers = JacksonUtils.deserialize2Map(URLDecoder.decode(Objects.requireNonNull(request.getHeaders() + .get(RouterConstant.ROUTER_LABEL_HEADER)).get(0), "UTF-8")); Assertions.assertThat("v1").isEqualTo(headers.get("k1")); Assertions.assertThat("v22").isEqualTo(headers.get("k2")); Assertions.assertThat("v4").isEqualTo(headers.get("k4")); Assertions.assertThat("GET").isEqualTo(headers.get("${http.method}")); Assertions.assertThat("/user/get").isEqualTo(headers.get("${http.uri}")); + String encodedLabelsContent; + try { + encodedLabelsContent = URLEncoder.encode(JacksonUtils.serialize2Json(routerLabels), UTF_8); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("unsupported charset exception " + UTF_8); + } + Assertions.assertThat(mockedResponse.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER).get(0)) + .isEqualTo(encodedLabelsContent); + } static class MockedHttpRequest implements HttpRequest { diff --git a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java index 5a5a4769..67e97dff 100644 --- a/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java +++ b/spring-cloud-starter-tencent-polaris-router/src/test/java/com/tencent/cloud/polaris/router/scg/PolarisReactiveLoadBalancerClientFilterTest.java @@ -27,7 +27,7 @@ import java.util.Set; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.common.metadata.StaticMetadataManager; @@ -135,7 +135,7 @@ public class PolarisReactiveLoadBalancerClientFilterTest { HttpHeaders headers = filter.genRouterHttpHeaders(webExchange, calleeService); Assert.assertNotNull(headers); - List routerHeaders = headers.get(RouterConstants.ROUTER_LABEL_HEADER); + List routerHeaders = headers.get(RouterConstant.ROUTER_LABEL_HEADER); Assert.assertFalse(CollectionUtils.isEmpty(routerHeaders)); Map routerLabels = JacksonUtils.deserialize2Map(URLDecoder.decode(routerHeaders.get(0), UTF_8)); diff --git a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/RouterConstants.java b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/RouterConstant.java similarity index 74% rename from spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/RouterConstants.java rename to spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/RouterConstant.java index 55eee75e..bfec4bb0 100644 --- a/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/RouterConstants.java +++ b/spring-cloud-tencent-commons/src/main/java/com/tencent/cloud/common/constant/RouterConstant.java @@ -21,18 +21,26 @@ package com.tencent.cloud.common.constant; /** * Router constants. * - * @author lepdou 2022-05-17 + * @author lepdou, Hoatian Zhang */ -public final class RouterConstants { +public final class RouterConstant { /** * the header of router label. */ public static final String ROUTER_LABEL_HEADER = "internal-router-label"; + /** + * the labels for rule router, contain transitive metadata. + */ + public static final String ROUTER_LABELS = "allMetadata"; + /** + * transitive labels. + */ + public static final String TRANSITIVE_LABELS = "transitiveMetadata"; /** * Default Private Constructor. */ - private RouterConstants() { + private RouterConstant() { } } diff --git a/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/resources/bootstrap.yml b/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/resources/bootstrap.yml index 7a53c2f0..148f3da0 100644 --- a/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/resources/bootstrap.yml +++ b/spring-cloud-tencent-examples/polaris-router-example/router-caller-service/src/main/resources/bootstrap.yml @@ -8,12 +8,18 @@ spring: metadata: content: k1: v1 + rpc-enhancement: + reporter: + enabled: true polaris: address: grpc://183.47.111.80:8091 namespace: default enabled: true loadbalancer: enabled: true + stat: + enabled: true + port: 28081 management: endpoints: web: diff --git a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java index 8a5a6f35..6f63bb82 100644 --- a/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java +++ b/spring-cloud-tencent-plugin-starters/spring-cloud-tencent-featureenv-plugin/src/test/java/com/tencent/cloud/plugin/featureenv/FeatureEnvRouterRequestInterceptorTest.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.polaris.router.PolarisRouterContext; import com.tencent.polaris.api.pojo.DefaultServiceInstances; import com.tencent.polaris.api.pojo.ServiceInstances; @@ -36,7 +37,8 @@ import org.mockito.junit.MockitoJUnitRunner; /** * Test for {@link FeatureEnvRouterRequestInterceptor}. - * @author lepdou 2022-07-12 + * + * @author lepdou, Hoatian Zhang */ @RunWith(MockitoJUnitRunner.class) public class FeatureEnvRouterRequestInterceptorTest { @@ -46,7 +48,7 @@ public class FeatureEnvRouterRequestInterceptorTest { Map labels = new HashMap<>(); labels.put("featureenv", "blue"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); ProcessRoutersRequest request = new ProcessRoutersRequest(); ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); @@ -67,7 +69,7 @@ public class FeatureEnvRouterRequestInterceptorTest { labels.put("system-feature-env-router-label", "specify-env"); labels.put("specify-env", "blue"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); ProcessRoutersRequest request = new ProcessRoutersRequest(); ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); @@ -87,7 +89,7 @@ public class FeatureEnvRouterRequestInterceptorTest { Map labels = new HashMap<>(); labels.put("system-feature-env-router-label", "specify-env"); PolarisRouterContext routerContext = new PolarisRouterContext(); - routerContext.putLabels(PolarisRouterContext.ROUTER_LABELS, labels); + routerContext.putLabels(RouterConstant.ROUTER_LABELS, labels); ProcessRoutersRequest request = new ProcessRoutersRequest(); ServiceInstances serviceInstances = new DefaultServiceInstances(Mockito.mock(ServiceKey.class), new ArrayList<>()); diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java index f0af78d0..289a3d5e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtils.java @@ -22,7 +22,7 @@ import java.net.URI; import java.net.URLDecoder; import java.util.Collection; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.polaris.api.pojo.RetStatus; import com.tencent.polaris.api.pojo.ServiceKey; @@ -55,7 +55,7 @@ public final class ReporterUtils { RequestTemplate requestTemplate = request.requestTemplate(); String serviceName = requestTemplate.feignTarget().name(); resultRequest.setService(serviceName); - Collection labels = requestTemplate.headers().get(RouterConstants.ROUTER_LABEL_HEADER); + Collection labels = requestTemplate.headers().get(RouterConstant.ROUTER_LABEL_HEADER); if (CollectionUtils.isNotEmpty(labels) && labels.iterator().hasNext()) { String label = labels.iterator().next(); try { diff --git a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java index 06379a8c..8920530e 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java +++ b/spring-cloud-tencent-rpc-enhancement/src/main/java/com/tencent/cloud/rpc/enhancement/resttemplate/EnhancedRestTemplateReporter.java @@ -18,9 +18,13 @@ package com.tencent.cloud.rpc.enhancement.resttemplate; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; +import java.net.URLDecoder; +import java.util.List; import java.util.Map; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.metadata.MetadataContextHolder; import com.tencent.cloud.rpc.enhancement.AbstractPolarisReporterAdapter; @@ -29,6 +33,7 @@ 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.api.utils.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +46,8 @@ import org.springframework.http.client.ClientHttpResponse; import org.springframework.lang.NonNull; import org.springframework.web.client.ResponseErrorHandler; +import static com.tencent.cloud.common.constant.ContextConstant.UTF_8; + /** * Extend ResponseErrorHandler to get request information. * @@ -48,10 +55,8 @@ import org.springframework.web.client.ResponseErrorHandler; */ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter implements ResponseErrorHandler, ApplicationContextAware { - private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); - static final String HEADER_HAS_ERROR = "X-SCT-Has-Error"; - + private static final Logger LOGGER = LoggerFactory.getLogger(EnhancedRestTemplateReporter.class); private final ConsumerAPI consumerAPI; private ResponseErrorHandler delegateHandler; @@ -129,6 +134,18 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter resultRequest.setRetStatus(RetStatus.RetFail); } + List labels = response.getHeaders().get(RouterConstant.ROUTER_LABEL_HEADER); + if (CollectionUtils.isNotEmpty(labels)) { + String label = labels.get(0); + try { + label = URLDecoder.decode(label, UTF_8); + } + catch (UnsupportedEncodingException e) { + LOGGER.error("unsupported charset exception " + UTF_8, e); + } + resultRequest.setLabels(convertLabel(label)); + } + // processing report with consumerAPI . LOGGER.debug("Will report result of {}. URL=[{}]. Response=[{}].", resultRequest.getRetStatus().name(), url, response); @@ -142,6 +159,12 @@ public class EnhancedRestTemplateReporter extends AbstractPolarisReporterAdapter } } + private String convertLabel(String label) { + label = label.replaceAll("\"|\\{|\\}", "") + .replaceAll(",", "|"); + return label; + } + private void invokeDelegateHandler(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { if (realHasError(response)) { delegateHandler.handleError(url, method, response); diff --git a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java index 918b04ea..795b918b 100644 --- a/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java +++ b/spring-cloud-tencent-rpc-enhancement/src/test/java/com/tencent/cloud/rpc/enhancement/feign/plugin/reporter/ReporterUtilsTest.java @@ -20,7 +20,7 @@ package com.tencent.cloud.rpc.enhancement.feign.plugin.reporter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import com.tencent.cloud.common.constant.RouterConstants; +import com.tencent.cloud.common.constant.RouterConstant; import com.tencent.cloud.common.metadata.MetadataContext; import com.tencent.cloud.common.util.ApplicationContextAwareUtils; import com.tencent.polaris.api.pojo.RetStatus; @@ -83,7 +83,7 @@ public class ReporterUtilsTest { RequestTemplate requestTemplate = new RequestTemplate(); requestTemplate.feignTarget(target); try { - requestTemplate.header(RouterConstants.ROUTER_LABEL_HEADER, URLEncoder.encode("{\"k1\":\"v1\",\"k2\":\"v2\"}", UTF_8)); + requestTemplate.header(RouterConstant.ROUTER_LABEL_HEADER, URLEncoder.encode("{\"k1\":\"v1\",\"k2\":\"v2\"}", UTF_8)); } catch (UnsupportedEncodingException e) { throw new RuntimeException("unsupported charset exception " + UTF_8);