Skip to content

Commit

Permalink
router plugin: interceptor with xDS router
Browse files Browse the repository at this point in the history
Signed-off-by: daizhenyu <[email protected]>
  • Loading branch information
daizhenyu committed Sep 14, 2024
1 parent 4cb6327 commit 2fd56e0
Show file tree
Hide file tree
Showing 19 changed files with 1,088 additions and 70 deletions.
4 changes: 4 additions & 0 deletions sermant-plugins/sermant-router/config/config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
router.plugin:
# whether compatible with the sermant-springboot-registry plugin
enabled-registry-plugin-adaptation: false
# whether to use xds route
enabled-xds-route: false
# whether to use secure protocol to invoke spring cloud downstream service with xds route, example: http or https
enabled-springcloud-xds-route-secure: false
# Whether to use request information for routing
use-request-router: false
# Use request information as tags when routing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ public class RouterConfig implements PluginConfig {
@ConfigFieldKey("enabled-registry-plugin-adaptation")
private boolean enabledRegistryPluginAdaptation;

/**
* whether to use xds route
*/
@ConfigFieldKey("enabled-xds-route")
private boolean enabledXdsRoute;

/**
* whether to use secure protocol to invoke downstream service with xds route, example: http or https
*/
@ConfigFieldKey("enabled-springcloud-xds-route-secure")
private boolean enabledSpringCloudXdsRouteSecure;

/**
* whether to use the request information for routing
*/
Expand Down Expand Up @@ -197,4 +209,20 @@ public boolean isEnabledPreviousRule() {
public void setEnabledPreviousRule(boolean enabledPreviousRule) {
this.enabledPreviousRule = enabledPreviousRule;
}

public boolean isEnabledXdsRoute() {
return enabledXdsRoute;
}

public void setEnabledXdsRoute(boolean enabledXdsRoute) {
this.enabledXdsRoute = enabledXdsRoute;
}

public boolean isEnabledSpringCloudXdsRouteSecure() {
return enabledSpringCloudXdsRouteSecure;
}

public void setEnabledSpringCloudXdsRouteSecure(boolean enabledSpringCloudXdsRouteSecure) {
this.enabledSpringCloudXdsRouteSecure = enabledSpringCloudXdsRouteSecure;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ public BaseRegistryPluginAdaptationDeclarer() {

@Override
public boolean isEnabled() {
return routerConfig.isEnabledRegistryPluginAdaptation();
return routerConfig.isEnabledXdsRoute() || routerConfig.isEnabledRegistryPluginAdaptation();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public class OkHttp3ClientDeclarer extends BaseRegistryPluginAdaptationDeclarer
* The fully qualified name of the enhanced okhttp request
*/
private static final String[] ENHANCE_CLASSES = {
"okhttp3.RealCall"
"okhttp3.RealCall",
"okhttp3.internal.connection.RealCall"
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2024-2024 Sermant Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 io.sermant.router.spring.declarer;

import io.sermant.core.plugin.agent.declarer.InterceptDeclarer;
import io.sermant.core.plugin.agent.matcher.ClassMatcher;
import io.sermant.core.plugin.agent.matcher.MethodMatcher;
import io.sermant.router.spring.interceptor.OkHttpClientInterceptorChainInterceptor;

/**
* For OKHTTP requests, modify the URL of request
*
* @author daizhenyu
* @since 2024-09-06
*/
public class OkHttpClientInterceptorChainDeclarer extends BaseRegistryPluginAdaptationDeclarer {
private static final String[] ENHANCE_CLASSES = {
"com.squareup.okhttp.Call$ApplicationInterceptorChain"
};

@Override
public ClassMatcher getClassMatcher() {
return ClassMatcher.nameContains(ENHANCE_CLASSES);
}

@Override
public InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader) {
return new InterceptDeclarer[]{
InterceptDeclarer.build(MethodMatcher.nameContains("proceed"),
new OkHttpClientInterceptorChainInterceptor())
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@

import io.sermant.core.plugin.agent.entity.ExecuteContext;
import io.sermant.core.plugin.agent.interceptor.AbstractInterceptor;
import io.sermant.core.plugin.config.PluginConfigManager;
import io.sermant.core.plugin.service.PluginServiceManager;
import io.sermant.core.service.xds.entity.ServiceInstance;
import io.sermant.router.common.config.RouterConfig;
import io.sermant.router.common.request.RequestData;
import io.sermant.router.common.request.RequestTag;
import io.sermant.router.common.utils.CollectionUtils;
import io.sermant.router.common.utils.ReflectUtils;
import io.sermant.router.common.utils.ThreadLocalUtils;
import io.sermant.router.common.xds.XdsRouterHandler;
import io.sermant.router.spring.service.LoadBalancerService;
import io.sermant.router.spring.utils.BaseHttpRouterUtils;
import io.sermant.router.spring.utils.SpringRouterUtils;

import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -36,6 +42,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

Expand All @@ -50,28 +57,47 @@ public class BaseLoadBalancerInterceptor extends AbstractInterceptor {

private final boolean canLoadZuul;

private final RouterConfig routerConfig;

/**
* Constructor
*/
public BaseLoadBalancerInterceptor() {
loadBalancerService = PluginServiceManager.getPluginService(LoadBalancerService.class);
canLoadZuul = canLoadZuul();
routerConfig = PluginConfigManager.getPluginConfig(RouterConfig.class);
}

@Override
public ExecuteContext before(ExecuteContext context) {
Object object = context.getObject();
if (object instanceof BaseLoadBalancer) {
List<Object> serverList = getServerList(context.getMethod().getName(), object);
if (CollectionUtils.isEmpty(serverList)) {
return context;
}
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) object;
String name = loadBalancer.getName();
RequestData requestData = getRequestData().orElse(null);
List<Object> targetInstances = loadBalancerService.getTargetInstances(name, serverList, requestData);
context.skip(Collections.unmodifiableList(targetInstances));
if (!(object instanceof BaseLoadBalancer)) {
return context;
}
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) object;
String name = loadBalancer.getName();
RequestData requestData = getRequestData().orElse(null);

// use xds route to find service instances
Set<ServiceInstance> serviceInstanceByXdsRoute = null;
if (routerConfig.isEnabledXdsRoute()) {
serviceInstanceByXdsRoute = XdsRouterHandler.INSTANCE
.getServiceInstanceByXdsRoute(name, requestData.getPath(),
BaseHttpRouterUtils.processHeaders(requestData.getTag()));
}
if (serviceInstanceByXdsRoute != null && !serviceInstanceByXdsRoute.isEmpty()) {
context.skip(Collections.unmodifiableList(SpringRouterUtils
.getSpringCloudServiceInstanceByXds(serviceInstanceByXdsRoute)));
return context;
}

List<Object> serverList = getServerList(context.getMethod().getName(), object);
if (CollectionUtils.isEmpty(serverList)) {
return context;
}

List<Object> targetInstances = loadBalancerService.getTargetInstances(name, serverList, requestData);
context.skip(Collections.unmodifiableList(targetInstances));
return context;
}

Expand Down Expand Up @@ -104,6 +130,7 @@ private Optional<RequestData> getRequestData() {
header.putAll(requestTag.getTag());
}
HttpServletRequest request = context.getRequest();
String scheme = request.getScheme();
Enumeration<?> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = (String) headerNames.nextElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,30 @@

package io.sermant.router.spring.interceptor;

import io.sermant.core.common.LoggerFactory;
import io.sermant.core.plugin.agent.entity.ExecuteContext;
import io.sermant.core.plugin.config.PluginConfigManager;
import io.sermant.core.service.xds.entity.ServiceInstance;
import io.sermant.core.utils.LogUtils;
import io.sermant.core.utils.StringUtils;
import io.sermant.router.common.config.RouterConfig;
import io.sermant.router.common.request.RequestData;
import io.sermant.router.common.utils.FlowContextUtils;
import io.sermant.router.common.utils.ThreadLocalUtils;
import io.sermant.router.spring.utils.RequestInterceptorUtils;
import io.sermant.router.spring.utils.BaseHttpRouterUtils;

import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.HttpRequestBase;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* HTTP interception only for version 4. x
Expand All @@ -40,6 +48,10 @@
* @since 2022-10-25
*/
public class HttpClient4xInterceptor extends MarkInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger();

private RouterConfig routerConfig = PluginConfigManager.getPluginConfig(RouterConfig.class);

/**
* Pre-trigger point
*
Expand All @@ -50,31 +62,33 @@ public class HttpClient4xInterceptor extends MarkInterceptor {
@Override
public ExecuteContext doBefore(ExecuteContext context) throws Exception {
LogUtils.printHttpRequestBeforePoint(context);
Object httpRequestObject = context.getArguments()[1];
if (httpRequestObject instanceof HttpRequest) {
final HttpRequest httpRequest = (HttpRequest) httpRequestObject;
final Optional<URI> optionalUri = RequestInterceptorUtils.formatUri(httpRequest.getRequestLine().getUri());
if (!optionalUri.isPresent()) {
return context;
}
URI uri = optionalUri.get();
if (StringUtils.isBlank(FlowContextUtils.getTagName())) {
return context;
}
Header[] headers = httpRequest.getHeaders(FlowContextUtils.getTagName());
Map<String, List<String>> flowTags = new HashMap<>();
if (headers != null && headers.length > 0) {
for (Header header : headers) {
String headerValue = header.getValue();
Map<String, List<String>> stringListMap = FlowContextUtils.decodeTags(headerValue);
flowTags.putAll(stringListMap);
}
}
if (flowTags.size() > 0) {
ThreadLocalUtils.setRequestData(new RequestData(
flowTags, uri.getPath(), httpRequest.getRequestLine().getMethod()));
Object[] arguments = context.getArguments();

Object httpRequestObject = arguments[1];
if (!(httpRequestObject instanceof HttpRequestBase)) {
return context;
}
final HttpRequestBase httpRequest = (HttpRequestBase) httpRequestObject;
URI uri = httpRequest.getURI();

handleXdsRouterAndUpdateHttpRequest(arguments, httpRequest);

if (StringUtils.isBlank(FlowContextUtils.getTagName())) {
return context;
}
Header[] headers = httpRequest.getHeaders(FlowContextUtils.getTagName());
Map<String, List<String>> flowTags = new HashMap<>();
if (headers != null && headers.length > 0) {
for (Header header : headers) {
String headerValue = header.getValue();
Map<String, List<String>> stringListMap = FlowContextUtils.decodeTags(headerValue);
flowTags.putAll(stringListMap);
}
}
if (flowTags.size() > 0) {
ThreadLocalUtils.setRequestData(new RequestData(
flowTags, uri.getPath(), httpRequest.getRequestLine().getMethod()));
}
return context;
}

Expand All @@ -98,4 +112,32 @@ public ExecuteContext onThrow(ExecuteContext context) {
LogUtils.printHttpRequestOnThrowPoint(context);
return context;
}

private Map<String, String> getHeaders(HttpRequestBase httpRequest) {
Map<String, String> headerMap = new HashMap<>();
for (Header header : httpRequest.getAllHeaders()) {
headerMap.putIfAbsent(header.getName(), header.getValue());
}
return headerMap;
}

private void handleXdsRouterAndUpdateHttpRequest(Object[] arguments, HttpRequestBase httpRequest) {
URI uri = httpRequest.getURI();
String host = uri.getHost();

// use xds route to find a service instance, and modify url by it
if (routerConfig.isEnabledXdsRoute() && BaseHttpRouterUtils.isXdsRouteRequired(host)) {
Optional<ServiceInstance> serviceInstanceOptional = BaseHttpRouterUtils
.chooseServiceInstanceByXds(host.split("\\.")[0], uri.getPath(), getHeaders(httpRequest));
if (serviceInstanceOptional.isPresent()) {
ServiceInstance instance = serviceInstanceOptional.get();
try {
httpRequest.setURI(new URI(BaseHttpRouterUtils.rebuildUrlByXdsServiceInstance(uri, instance)));
arguments[0] = new HttpHost(instance.getHost(), instance.getPort());
} catch (URISyntaxException e) {
LOGGER.log(Level.WARNING, "Create uri using xds service instance failed.", e.getMessage());
}
}
}
}
}
Loading

0 comments on commit 2fd56e0

Please sign in to comment.