diff --git a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java index 8d2c361537..53ab814f61 100644 --- a/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java +++ b/sermant-plugins/sermant-flowcontrol/flowcontrol-plugin/src/main/java/io/sermant/flowcontrol/DispatcherServletInterceptor.java @@ -18,17 +18,23 @@ import io.sermant.core.plugin.agent.entity.ExecuteContext; import io.sermant.core.utils.LogUtils; +import io.sermant.core.utils.ReflectUtils; import io.sermant.flowcontrol.common.config.ConfigConst; import io.sermant.flowcontrol.common.entity.FlowControlResult; import io.sermant.flowcontrol.common.entity.HttpRequestEntity; import io.sermant.flowcontrol.common.entity.RequestEntity.RequestType; import io.sermant.flowcontrol.service.InterceptorSupporter; +import java.io.IOException; +import java.io.PrintWriter; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -42,6 +48,28 @@ public class DispatcherServletInterceptor extends InterceptorSupporter { private final String className = DispatcherServletInterceptor.class.getName(); + private Function getRequestUri; + + private Function getPathInfo; + + private Function getMethod; + + private Function> getHeaderNames; + + private BiFunction getHeader; + + private Function getWriter; + + private BiConsumer setStatus; + + /** + * 构造方法 + */ + public DispatcherServletInterceptor() { + super(); + initFunction(); + } + /** * http request data conversion adapts to plugin -> service data transfer Note that this method is not * extractable,Because host dependencies can only be loaded by this interceptor, pulling out results in classes not @@ -50,18 +78,18 @@ public class DispatcherServletInterceptor extends InterceptorSupporter { * @param request request * @return HttpRequestEntity */ - private Optional convertToHttpEntity(HttpServletRequest request) { + private Optional convertToHttpEntity(Object request) { if (request == null) { return Optional.empty(); } - String uri = request.getRequestURI(); + String uri = getRequestUri.apply(request); return Optional.of(new HttpRequestEntity.Builder() .setRequestType(RequestType.SERVER) - .setPathInfo(request.getPathInfo()) + .setPathInfo(getPathInfo.apply(request)) .setServletPath(uri) .setHeaders(getHeaders(request)) - .setMethod(request.getMethod()) - .setServiceName(request.getHeader(ConfigConst.FLOW_REMOTE_SERVICE_NAME_HEADER_KEY)) + .setMethod(getMethod.apply(request)) + .setServiceName(getHeader.apply(request, ConfigConst.FLOW_REMOTE_SERVICE_NAME_HEADER_KEY)) .build()); } @@ -71,12 +99,12 @@ private Optional convertToHttpEntity(HttpServletRequest reque * @param request request information * @return headers */ - private Map getHeaders(HttpServletRequest request) { - final Enumeration headerNames = request.getHeaderNames(); + private Map getHeaders(Object request) { + final Enumeration headerNames = getHeaderNames.apply(request); final Map headers = new HashMap<>(); while (headerNames.hasMoreElements()) { final String headerName = headerNames.nextElement(); - headers.put(headerName, request.getHeader(headerName)); + headers.put(headerName, getHeader.apply(request, headerName)); } return Collections.unmodifiableMap(headers); } @@ -85,19 +113,19 @@ private Map getHeaders(HttpServletRequest request) { protected final ExecuteContext doBefore(ExecuteContext context) throws Exception { LogUtils.printHttpRequestBeforePoint(context); final Object[] allArguments = context.getArguments(); - final HttpServletRequest argument = (HttpServletRequest) allArguments[0]; + final Object request = allArguments[0]; final FlowControlResult result = new FlowControlResult(); - final Optional httpRequestEntity = convertToHttpEntity(argument); + final Optional httpRequestEntity = convertToHttpEntity(request); if (!httpRequestEntity.isPresent()) { return context; } chooseHttpService().onBefore(className, httpRequestEntity.get(), result); if (result.isSkip()) { context.skip(null); - final HttpServletResponse response = (HttpServletResponse) allArguments[1]; + final Object response = allArguments[1]; if (response != null) { - response.setStatus(result.getResponse().getCode()); - response.getWriter().print(result.buildResponseMsg()); + setStatus.accept(response, result.getResponse().getCode()); + getWriter.apply(response).print(result.buildResponseMsg()); } } return context; @@ -116,4 +144,75 @@ protected final ExecuteContext doThrow(ExecuteContext context) { LogUtils.printHttpRequestOnThrowPoint(context); return context; } + + private String getRequestUri(Object httpServletRequest) { + return getString(httpServletRequest, "getRequestURI"); + } + + private String getPathInfo(Object httpServletRequest) { + return getString(httpServletRequest, "getPathInfo"); + } + + private String getMethod(Object httpServletRequest) { + return getString(httpServletRequest, "getMethod"); + } + + private Enumeration getHeaderNames(Object httpServletRequest) { + return (Enumeration) ReflectUtils.invokeMethodWithNoneParameter(httpServletRequest, "getHeaderNames") + .orElse(null); + } + + private String getHeader(Object httpServletRequest, String key) { + return (String) ReflectUtils.invokeMethod(httpServletRequest, "getHeader", new Class[]{String.class}, + new Object[]{key}).orElse(null); + } + + private PrintWriter getWriter(Object httpServletRequest) { + return (PrintWriter) ReflectUtils.invokeMethodWithNoneParameter(httpServletRequest, "getWriter") + .orElse(null); + } + + private void setStatus(Object httpServletResponse, int code) { + ReflectUtils.invokeMethod(httpServletResponse, "setStatus", new Class[]{int.class}, new Object[]{code}); + } + + private String getString(Object object, String method) { + return (String) ReflectUtils.invokeMethodWithNoneParameter(object, method).orElse(null); + } + + private void initFunction() { + boolean canLoadLowVersion = canLoadLowVersion(); + if (canLoadLowVersion) { + getRequestUri = obj -> ((HttpServletRequest) obj).getRequestURI(); + getPathInfo = obj -> ((HttpServletRequest) obj).getPathInfo(); + getMethod = obj -> ((HttpServletRequest) obj).getMethod(); + getHeaderNames = obj -> ((HttpServletRequest) obj).getHeaderNames(); + getHeader = (obj, key) -> ((HttpServletRequest) obj).getHeader(key); + getWriter = obj -> { + try { + return ((HttpServletResponse) obj).getWriter(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + }; + setStatus = (obj, code) -> ((HttpServletResponse) obj).setStatus(code); + } else { + getRequestUri = this::getRequestUri; + getPathInfo = this::getPathInfo; + getMethod = this::getMethod; + getHeaderNames = this::getHeaderNames; + getHeader = this::getHeader; + getWriter = this::getWriter; + setStatus = this::setStatus; + } + } + + private boolean canLoadLowVersion() { + try { + Class.forName(HttpServletRequest.class.getCanonicalName()); + } catch (NoClassDefFoundError | ClassNotFoundException error) { + return false; + } + return true; + } } diff --git a/sermant-plugins/sermant-monitor/monitor-plugin/src/main/java/io/sermant/monitor/interceptor/DispatcherServletInterceptor.java b/sermant-plugins/sermant-monitor/monitor-plugin/src/main/java/io/sermant/monitor/interceptor/DispatcherServletInterceptor.java index c584783547..ea8ee78e94 100644 --- a/sermant-plugins/sermant-monitor/monitor-plugin/src/main/java/io/sermant/monitor/interceptor/DispatcherServletInterceptor.java +++ b/sermant-plugins/sermant-monitor/monitor-plugin/src/main/java/io/sermant/monitor/interceptor/DispatcherServletInterceptor.java @@ -20,9 +20,12 @@ import io.sermant.core.plugin.agent.entity.ExecuteContext; import io.sermant.core.plugin.agent.interceptor.AbstractInterceptor; import io.sermant.core.utils.LogUtils; +import io.sermant.core.utils.ReflectUtils; import io.sermant.monitor.common.MetricCalEntity; import io.sermant.monitor.util.MonitorCacheUtil; +import java.util.function.Function; + import javax.servlet.http.HttpServletRequest; /** @@ -34,6 +37,15 @@ public class DispatcherServletInterceptor extends AbstractInterceptor { private static final String START_TIME = "startTime"; + private Function getRequestUri; + + /** + * 构造方法 + */ + public DispatcherServletInterceptor() { + initFunction(); + } + @Override public ExecuteContext before(ExecuteContext context) { LogUtils.printHttpRequestBeforePoint(context); @@ -50,7 +62,7 @@ public ExecuteContext after(ExecuteContext context) { LogUtils.printHttpRequestAfterPoint(context); return context; } - String uri = ((HttpServletRequest) context.getArguments()[0]).getRequestURI(); + String uri = getRequestUri.apply(context.getArguments()[0]); MetricCalEntity metricCalEntity = MonitorCacheUtil.getMetricCalEntity(uri); metricCalEntity.getReqNum().incrementAndGet(); long startTime = (Long) context.getExtMemberFieldValue(START_TIME); @@ -70,11 +82,33 @@ public ExecuteContext onThrow(ExecuteContext context) { LogUtils.printHttpRequestOnThrowPoint(context); return context; } - String uri = ((HttpServletRequest) context.getArguments()[0]).getRequestURI(); + String uri = getRequestUri.apply(context.getArguments()[0]); MetricCalEntity metricCalEntity = MonitorCacheUtil.getMetricCalEntity(uri); metricCalEntity.getReqNum().incrementAndGet(); metricCalEntity.getFailedReqNum().incrementAndGet(); LogUtils.printHttpRequestOnThrowPoint(context); return context; } + + private String getRequestUri(Object httpServletRequest) { + return (String) ReflectUtils.invokeMethodWithNoneParameter(httpServletRequest, "getRequestURI").orElse(null); + } + + private void initFunction() { + boolean canLoadLowVersion = canLoadLowVersion(); + if (canLoadLowVersion) { + getRequestUri = obj -> ((HttpServletRequest) obj).getRequestURI(); + } else { + getRequestUri = this::getRequestUri; + } + } + + private boolean canLoadLowVersion() { + try { + Class.forName(HttpServletRequest.class.getCanonicalName()); + } catch (NoClassDefFoundError | ClassNotFoundException error) { + return false; + } + return true; + } } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/pom.xml b/sermant-plugins/sermant-router/spring-router-plugin/pom.xml index 4cc53ae902..7df543416e 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/pom.xml +++ b/sermant-plugins/sermant-router/spring-router-plugin/pom.xml @@ -61,12 +61,6 @@ ${jakarta.el.version} provided - - org.springframework - spring-web - ${spring.version} - provided - org.springframework spring-webflux @@ -79,12 +73,6 @@ ${spring.version} provided - - org.springframework - spring-webflux - ${spring.version} - provided - io.projectreactor.netty reactor-netty diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/declarer/HandlerExecutionChainDeclarer.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/declarer/DispatcherServletDeclarer.java similarity index 70% rename from sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/declarer/HandlerExecutionChainDeclarer.java rename to sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/declarer/DispatcherServletDeclarer.java index c339190a03..4a2933acfb 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/declarer/HandlerExecutionChainDeclarer.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/declarer/DispatcherServletDeclarer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (C) 2022-2024 Huawei Technologies Co., Ltd. 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. @@ -19,23 +19,23 @@ import io.sermant.core.plugin.agent.matcher.ClassMatcher; /** - * Add an injection interceptor by intercepting and inject a spring web interceptor + * get http request data * * @author provenceee * @since 2022-07-12 */ -public class HandlerExecutionChainDeclarer extends AbstractDeclarer { - private static final String ENHANCE_CLASS = "org.springframework.web.servlet.HandlerExecutionChain"; +public class DispatcherServletDeclarer extends AbstractDeclarer { + private static final String ENHANCE_CLASS = "org.springframework.web.servlet.DispatcherServlet"; private static final String INTERCEPT_CLASS - = "io.sermant.router.spring.interceptor.HandlerExecutionChainInterceptor"; + = "io.sermant.router.spring.interceptor.DispatcherServletInterceptor"; - private static final String METHOD_NAME = "applyPreHandle"; + private static final String METHOD_NAME = "doService"; /** * Constructor */ - public HandlerExecutionChainDeclarer() { + public DispatcherServletDeclarer() { super(ENHANCE_CLASS, INTERCEPT_CLASS, METHOD_NAME); } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptor.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptor.java index 0a9e755674..83ef7ed83a 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptor.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptor.java @@ -59,6 +59,7 @@ public ExecuteContext before(ExecuteContext context) { Object serviceName = ReflectUtils.getFieldValue(obj, "serviceName").orElse(null); if (serviceName instanceof String) { AppCache.INSTANCE.setAppName((String) serviceName); + configService.init(RouterConstant.SPRING_CACHE_NAME, AppCache.INSTANCE.getAppName()); } else { LOGGER.warning("Service name is null or not instanceof string."); } @@ -69,7 +70,6 @@ public ExecuteContext before(ExecuteContext context) { @Override public ExecuteContext after(ExecuteContext context) { - configService.init(RouterConstant.SPRING_CACHE_NAME, AppCache.INSTANCE.getAppName()); return context; } } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/RouteHandlerInterceptor.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DispatcherServletInterceptor.java similarity index 55% rename from sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/RouteHandlerInterceptor.java rename to sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DispatcherServletInterceptor.java index 4fae55be27..c94d95a8ee 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/RouteHandlerInterceptor.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/DispatcherServletInterceptor.java @@ -16,6 +16,8 @@ package io.sermant.router.spring.interceptor; +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.agent.interceptor.AbstractInterceptor; import io.sermant.core.plugin.service.PluginServiceManager; import io.sermant.router.common.handler.Handler; import io.sermant.router.common.utils.CollectionUtils; @@ -25,9 +27,7 @@ import io.sermant.router.spring.handler.LaneRequestTagHandler; import io.sermant.router.spring.handler.RouteRequestTagHandler; import io.sermant.router.spring.service.SpringConfigService; - -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; +import io.sermant.router.spring.utils.SpringRouterUtils; import java.util.ArrayList; import java.util.Collections; @@ -37,65 +37,82 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; /** - * Spring Interceptor + * get http request data * * @author provenceee * @since 2022-07-12 */ -public class RouteHandlerInterceptor implements HandlerInterceptor { +public class DispatcherServletInterceptor extends AbstractInterceptor { private final List handlers; private final SpringConfigService configService; + private Function> getParameterMap; + + private Function getRequestUri; + + private Function getMethod; + + private Function> getHeaderNames; + + private BiFunction> getHeaders; + /** * Constructor */ - public RouteHandlerInterceptor() { + public DispatcherServletInterceptor() { configService = PluginServiceManager.getPluginService(SpringConfigService.class); handlers = new ArrayList<>(); handlers.add(new LaneRequestTagHandler()); handlers.add(new RouteRequestTagHandler()); handlers.sort(Comparator.comparingInt(Handler::getOrder)); + initFunction(); } @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object obj) { + public ExecuteContext before(ExecuteContext context) { Set matchKeys = configService.getMatchKeys(); Set injectTags = configService.getInjectTags(); if (CollectionUtils.isEmpty(matchKeys) && CollectionUtils.isEmpty(injectTags)) { // The staining mark is empty, which means that there are no staining rules, and it is returned directly - return true; + return context; } + Object request = context.getArguments()[0]; Map> headers = getHeaders(request); - Map parameterMap = request.getParameterMap(); - String path = request.getRequestURI(); - String method = request.getMethod(); + Map parameterMap = getParameterMap.apply(request); + String path = getRequestUri.apply(request); + String method = getMethod.apply(request); handlers.forEach(handler -> ThreadLocalUtils.addRequestTag( handler.getRequestTag(path, method, headers, parameterMap, new Keys(matchKeys, injectTags)))); - return true; + return context; } @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object obj, - ModelAndView modelAndView) { + public ExecuteContext after(ExecuteContext context) { + ThreadLocalUtils.removeRequestData(); + ThreadLocalUtils.removeRequestTag(); + return context; } @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object obj, Exception ex) { + public ExecuteContext onThrow(ExecuteContext context) { + ThreadLocalUtils.removeRequestData(); ThreadLocalUtils.removeRequestTag(); + return context; } - private Map> getHeaders(HttpServletRequest request) { + private Map> getHeaders(Object request) { Map> headers = new HashMap<>(); - Enumeration enumeration = request.getHeaderNames(); + Enumeration enumeration = getHeaderNames.apply(request); while (enumeration.hasMoreElements()) { String key = (String) enumeration.nextElement(); - headers.put(key, enumeration2List(request.getHeaders(key))); + headers.put(key, enumeration2List(getHeaders.apply(request, key))); } return headers; } @@ -110,4 +127,30 @@ private List enumeration2List(Enumeration enumeration) { } return collection; } + + private void initFunction() { + boolean canLoadLowVersion = canLoadLowVersion(); + if (canLoadLowVersion) { + getParameterMap = obj -> ((HttpServletRequest) obj).getParameterMap(); + getRequestUri = obj -> ((HttpServletRequest) obj).getRequestURI(); + getMethod = obj -> ((HttpServletRequest) obj).getMethod(); + getHeaderNames = obj -> ((HttpServletRequest) obj).getHeaderNames(); + getHeaders = (obj, key) -> ((HttpServletRequest) obj).getHeaders(key); + } else { + getParameterMap = SpringRouterUtils::getParameterMap; + getRequestUri = SpringRouterUtils::getRequestUri; + getMethod = SpringRouterUtils::getMethod; + getHeaderNames = SpringRouterUtils::getHeaderNames; + getHeaders = SpringRouterUtils::getHeaders; + } + } + + private boolean canLoadLowVersion() { + try { + Class.forName(HttpServletRequest.class.getCanonicalName()); + } catch (NoClassDefFoundError | ClassNotFoundException error) { + return false; + } + return true; + } } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptor.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptor.java index 6356078b82..339faa32fe 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptor.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptor.java @@ -53,6 +53,7 @@ public ExecuteContext before(ExecuteContext context) { if (argument instanceof InstanceInfo) { InstanceInfo instanceInfo = (InstanceInfo) argument; AppCache.INSTANCE.setAppName(instanceInfo.getAppName()); + configService.init(RouterConstant.SPRING_CACHE_NAME, AppCache.INSTANCE.getAppName()); SpringRouterUtils.putMetaData(instanceInfo.getMetadata(), routerConfig); } return context; @@ -60,7 +61,6 @@ public ExecuteContext before(ExecuteContext context) { @Override public ExecuteContext after(ExecuteContext context) { - configService.init(RouterConstant.SPRING_CACHE_NAME, AppCache.INSTANCE.getAppName()); return context; } } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/HandlerExecutionChainInterceptor.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/HandlerExecutionChainInterceptor.java deleted file mode 100644 index fbf989f871..0000000000 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/HandlerExecutionChainInterceptor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. 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.interceptor; - -import io.sermant.core.plugin.agent.entity.ExecuteContext; -import io.sermant.core.plugin.agent.interceptor.AbstractInterceptor; -import io.sermant.core.utils.ClassUtils; - -import org.springframework.web.servlet.HandlerExecutionChain; -import org.springframework.web.servlet.HandlerInterceptor; - -/** - * Add an injection interceptor by intercepting and inject a spring web interceptor - * - * @author provenceee - * @since 2022-07-12 - */ -public class HandlerExecutionChainInterceptor extends AbstractInterceptor { - private static final String ROUTE_HANDLER_CLASS_NAME - = "io.sermant.router.spring.interceptor.RouteHandlerInterceptor"; - - private volatile HandlerInterceptor handlerInterceptor; - - @Override - public ExecuteContext before(ExecuteContext context) { - Object object = context.getObject(); - if (object instanceof HandlerExecutionChain) { - HandlerExecutionChain chain = (HandlerExecutionChain) object; - chain.addInterceptor(getInterceptor()); - } - return context; - } - - @Override - public ExecuteContext after(ExecuteContext context) { - return context; - } - - private HandlerInterceptor getInterceptor() { - if (handlerInterceptor == null) { - synchronized (HandlerExecutionChainInterceptor.class) { - if (handlerInterceptor == null) { - ClassUtils.defineClass(ROUTE_HANDLER_CLASS_NAME, getClass().getClassLoader()); - handlerInterceptor = new RouteHandlerInterceptor(); - } - } - } - return handlerInterceptor; - } -} diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptor.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptor.java index 00ce7e9291..e5aea998bd 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptor.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptor.java @@ -66,6 +66,7 @@ public ExecuteContext before(ExecuteContext context) { serviceRegistration.getClass().getDeclaredMethod("getRegistration")) .invoke(serviceRegistration); AppCache.INSTANCE.setAppName(registration.getServiceId()); + configService.init(RouterConstant.SPRING_CACHE_NAME, AppCache.INSTANCE.getAppName()); SpringRouterUtils.putMetaData(registration.getMetadata(), routerConfig); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException ex) { LOGGER.log(Level.WARNING, "Can not get the registration.", ex); @@ -76,7 +77,6 @@ public ExecuteContext before(ExecuteContext context) { @Override public ExecuteContext after(ExecuteContext context) { - configService.init(RouterConstant.SPRING_CACHE_NAME, AppCache.INSTANCE.getAppName()); return context; } } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/utils/SpringRouterUtils.java b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/utils/SpringRouterUtils.java index 781d530223..12ed986836 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/utils/SpringRouterUtils.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/java/io/sermant/router/spring/utils/SpringRouterUtils.java @@ -28,6 +28,7 @@ import org.springframework.cloud.client.DefaultServiceInstance; +import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.Map; @@ -50,6 +51,57 @@ public class SpringRouterUtils { private SpringRouterUtils() { } + /** + * get http parameter + * + * @param obj HttpServletRequest + * @return parameter + */ + public static Map getParameterMap(Object obj) { + return (Map) ReflectUtils.invokeWithNoneParameter(obj, "getParameterMap"); + } + + /** + * get http uri + * + * @param obj HttpServletRequest + * @return uri + */ + public static String getRequestUri(Object obj) { + return ReflectUtils.invokeWithNoneParameterAndReturnString(obj, "getRequestURI"); + } + + /** + * get http method + * + * @param obj HttpServletRequest + * @return method + */ + public static String getMethod(Object obj) { + return ReflectUtils.invokeWithNoneParameterAndReturnString(obj, "getMethod"); + } + + /** + * get http header keys + * + * @param obj HttpServletRequest + * @return key + */ + public static Enumeration getHeaderNames(Object obj) { + return (Enumeration) ReflectUtils.invokeWithNoneParameter(obj, "getHeaderNames"); + } + + /** + * get http header value + * + * @param obj HttpServletRequest + * @param key header key + * @return header value + */ + public static Enumeration getHeaders(Object obj, String key) { + return (Enumeration) ReflectUtils.invokeWithParameter(obj, "getHeaders", key, String.class); + } + /** * get SpringCloud ServiceInstance By XdsServiceInstance * diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer b/sermant-plugins/sermant-router/spring-router-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer index 1cb06edb97..b1e298460b 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer @@ -4,7 +4,7 @@ io.sermant.router.spring.declarer.ClientHttpRequestDeclarer io.sermant.router.spring.declarer.DiscoveryManagerDeclarer io.sermant.router.spring.declarer.EurekaHttpClientDeclarer io.sermant.router.spring.declarer.FeignClientDeclarer -io.sermant.router.spring.declarer.HandlerExecutionChainDeclarer +io.sermant.router.spring.declarer.DispatcherServletDeclarer io.sermant.router.spring.declarer.HystrixActionDeclarer io.sermant.router.spring.declarer.LoadBalancerClientFilterDeclarer io.sermant.router.spring.declarer.NopInstanceFilterDeclarer diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptorTest.java b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptorTest.java index 6ca2ad271d..a9dde066d4 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptorTest.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DiscoveryManagerInterceptorTest.java @@ -104,15 +104,6 @@ public void testBefore() { context.getArguments()[0] = new TestObject(null); interceptor.before(context); Assert.assertEquals("foo", AppCache.INSTANCE.getAppName()); - } - - /** - * Test the after method - */ - @Test - public void testAfter() { - AppCache.INSTANCE.setAppName("foo"); - interceptor.after(context); Assert.assertEquals(RouterConstant.SPRING_CACHE_NAME, configService.getCacheName()); Assert.assertEquals("foo", configService.getServiceName()); } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/RouteHandlerInterceptorTest.java b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DispatcherServletInterceptorTest.java similarity index 63% rename from sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/RouteHandlerInterceptorTest.java rename to sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DispatcherServletInterceptorTest.java index fefecd802f..82329c2923 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/RouteHandlerInterceptorTest.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/DispatcherServletInterceptorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (C) 2022-2024 Huawei Technologies Co., Ltd. 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. @@ -16,7 +16,9 @@ package io.sermant.router.spring.interceptor; +import io.sermant.core.plugin.agent.entity.ExecuteContext; import io.sermant.core.service.ServiceManager; +import io.sermant.router.common.request.RequestData; import io.sermant.router.common.request.RequestTag; import io.sermant.router.common.utils.ThreadLocalUtils; import io.sermant.router.spring.BaseTransmitConfigTest; @@ -31,25 +33,28 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; import java.util.Collections; import java.util.List; import java.util.Map; /** - * Test RouteHandlerInterceptor + * Test DispatcherServletInterceptor * * @author provenceee * @since 2022-09-07 */ -public class RouteHandlerInterceptorTest extends BaseTransmitConfigTest { - private final RouteHandlerInterceptor interceptor; +public class DispatcherServletInterceptorTest extends BaseTransmitConfigTest { + private final DispatcherServletInterceptor interceptor; private static TestSpringConfigService configService; private static MockedStatic mockServiceManager; + private final ExecuteContext context; + + private final MockHttpServletRequest request; + /** * Perform mock before the UT is executed */ @@ -69,8 +74,12 @@ public static void after() { mockServiceManager.close(); } - public RouteHandlerInterceptorTest() { - interceptor = new RouteHandlerInterceptor(); + public DispatcherServletInterceptorTest() throws NoSuchMethodException { + interceptor = new DispatcherServletInterceptor(); + request = new MockHttpServletRequest(); + Object[] arguments = new Object[]{request}; + context = ExecuteContext.forMemberMethod(new Object(), String.class.getMethod("trim"), arguments, null, + null); } /** @@ -83,27 +92,24 @@ public void clear() { } /** - * Test the preHandle method + * Test the before method */ @Test - public void testPreHandle() { - MockHttpServletRequest request = new MockHttpServletRequest(); + public void testBefore() { request.addHeader("bar", "bar1"); request.addHeader("foo", "foo1"); request.addHeader("foo2", "foo2"); - MockHttpServletResponse response = new MockHttpServletResponse(); - Object obj = new Object(); // The test keys are all empty configService.setReturnEmptyWhenGetMatchTags(true); configService.setReturnEmptyWhenGetMatchKeys(true); - interceptor.preHandle(request, response, obj); + interceptor.before(context); Assert.assertNull(ThreadLocalUtils.getRequestTag()); // Test the preHandle method, getMatchKeys is not empty configService.setReturnEmptyWhenGetMatchTags(true); configService.setReturnEmptyWhenGetMatchKeys(false); - interceptor.preHandle(request, response, obj); + interceptor.before(context); RequestTag requestTag = ThreadLocalUtils.getRequestTag(); Map> header = requestTag.getTag(); Assert.assertNotNull(header); @@ -113,15 +119,32 @@ public void testPreHandle() { } /** - * After testing is completed, verify if the thread variables are released + * Test the after method, verify if the thread variables are released + */ + @Test + public void testAfter() { + ThreadLocalUtils.addRequestTag(Collections.singletonMap("bar", Collections.singletonList("foo"))); + ThreadLocalUtils.setRequestData(new RequestData(Collections.emptyMap(), "", "")); + Assert.assertNotNull(ThreadLocalUtils.getRequestTag()); + + // Test the after method, verify if thread variables are released + interceptor.after(context); + Assert.assertNull(ThreadLocalUtils.getRequestTag()); + Assert.assertNull(ThreadLocalUtils.getRequestData()); + } + + /** + * Test the onThrow method, verify if the thread variables are released */ @Test - public void testAfterCompletion() { + public void testOnThrow() { ThreadLocalUtils.addRequestTag(Collections.singletonMap("bar", Collections.singletonList("foo"))); + ThreadLocalUtils.setRequestData(new RequestData(Collections.emptyMap(), "", "")); Assert.assertNotNull(ThreadLocalUtils.getRequestTag()); - // Test afterCompletion to verify if thread variables are released - interceptor.afterCompletion(new MockHttpServletRequest(), new MockHttpServletResponse(), new Object(), null); + // Test the onThrow method, verify if thread variables are released + interceptor.onThrow(context); Assert.assertNull(ThreadLocalUtils.getRequestTag()); + Assert.assertNull(ThreadLocalUtils.getRequestData()); } } diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptorTest.java b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptorTest.java index d08bfcf774..f4233fffda 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptorTest.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/EurekaHttpClientInterceptorTest.java @@ -111,13 +111,6 @@ public void testBefore() { Assert.assertEquals(routerConfig.getRouterVersion(), metadata.get("version")); Assert.assertEquals("bar1", metadata.get("bar")); Assert.assertEquals("foo2", metadata.get("foo")); - } - - /** - * Test the after method - */ - @Test - public void testAfter() { AppCache.INSTANCE.setAppName("FOO"); interceptor.after(context); Assert.assertEquals(RouterConstant.SPRING_CACHE_NAME, configService.getCacheName()); diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/HandlerExecutionChainInterceptorTest.java b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/HandlerExecutionChainInterceptorTest.java deleted file mode 100644 index 76b272b139..0000000000 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/HandlerExecutionChainInterceptorTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. 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.interceptor; - -import io.sermant.core.plugin.agent.entity.ExecuteContext; -import io.sermant.core.service.ServiceManager; -import io.sermant.router.spring.service.SpringConfigService; - -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.springframework.web.servlet.HandlerExecutionChain; - -import java.util.Collections; -import java.util.Set; - -/** - * Test HandlerExecutionChainInterceptor - * - * @author provenceee - * @since 2022-09-07 - */ -public class HandlerExecutionChainInterceptorTest { - private final HandlerExecutionChainInterceptor interceptor; - - private final ExecuteContext context; - - private static MockedStatic mockServiceManager; - - /** - * Perform mock before the UT is executed - */ - @BeforeClass - public static void before() { - mockServiceManager = Mockito.mockStatic(ServiceManager.class); - mockServiceManager.when(() -> ServiceManager.getService(SpringConfigService.class)).thenReturn( - new SpringConfigService() { - @Override - public void init(String cacheName, String serviceName) { - } - - @Override - public Set getMatchKeys() { - return Collections.emptySet(); - } - - @Override - public Set getInjectTags() { - return Collections.emptySet(); - } - }); - } - - /** - * Release the mock object after the UT is executed - */ - @AfterClass - public static void after() { - mockServiceManager.close(); - } - - public HandlerExecutionChainInterceptorTest() { - interceptor = new HandlerExecutionChainInterceptor(); - context = ExecuteContext.forMemberMethod(new HandlerExecutionChain(new Object()), null, null, null, null); - } - - /** - * Test the before method - */ - @Test - public void testBefore() { - interceptor.before(context); - HandlerExecutionChain chain = (HandlerExecutionChain) context.getObject(); - Assert.assertNotNull(chain.getInterceptors()); - Assert.assertEquals(1, chain.getInterceptors().length); - Assert.assertEquals(RouteHandlerInterceptor.class, chain.getInterceptors()[0].getClass()); - } -} diff --git a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptorTest.java b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptorTest.java index d1f27f0883..b511151747 100644 --- a/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptorTest.java +++ b/sermant-plugins/sermant-router/spring-router-plugin/src/test/java/io/sermant/router/spring/interceptor/ServiceRegistryInterceptorTest.java @@ -105,13 +105,6 @@ public void testBefore() { Assert.assertEquals(routerConfig.getRouterVersion(), metadata.get("version")); Assert.assertEquals("bar1", metadata.get("bar")); Assert.assertEquals("foo2", metadata.get("foo")); - } - - /** - * Test the after method - */ - @Test - public void testAfter() { AppCache.INSTANCE.setAppName("foo"); interceptor.after(context); Assert.assertEquals(RouterConstant.SPRING_CACHE_NAME, configService.getCacheName()); diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/declarers/SpringWebHandlerDeclarer.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/declarers/DispatcherServletDeclarer.java similarity index 76% rename from sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/declarers/SpringWebHandlerDeclarer.java rename to sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/declarers/DispatcherServletDeclarer.java index 9ea6785093..ad7d9d8bb0 100644 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/declarers/SpringWebHandlerDeclarer.java +++ b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/declarers/DispatcherServletDeclarer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (C) 2022-2024 Huawei Technologies Co., Ltd. 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. @@ -21,7 +21,7 @@ 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.registry.grace.interceptors.SpringWebHandlerInterceptor; +import io.sermant.registry.grace.interceptors.DispatcherServletInterceptor; /** * Inject interceptors by intercepting them @@ -29,16 +29,16 @@ * @author zhouss * @since 2022-05-23 */ -public class SpringWebHandlerDeclarer extends AbstractPluginDeclarer { +public class DispatcherServletDeclarer extends AbstractPluginDeclarer { /** * The fully qualified name of the enhanced class */ - private static final String ENHANCE_CLASS = "org.springframework.web.servlet.HandlerExecutionChain"; + private static final String ENHANCE_CLASS = "org.springframework.web.servlet.DispatcherServlet"; /** * The fully qualified name of the interception class */ - private static final String INTERCEPT_CLASS = SpringWebHandlerInterceptor.class.getCanonicalName(); + private static final String INTERCEPT_CLASS = DispatcherServletInterceptor.class.getCanonicalName(); @Override public ClassMatcher getClassMatcher() { @@ -48,7 +48,7 @@ public ClassMatcher getClassMatcher() { @Override public InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader) { return new InterceptDeclarer[]{ - InterceptDeclarer.build(MethodMatcher.nameEquals("applyPreHandle"), INTERCEPT_CLASS) + InterceptDeclarer.build(MethodMatcher.nameEquals("doService"), INTERCEPT_CLASS) }; } } diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/DispatcherServletInterceptor.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/DispatcherServletInterceptor.java new file mode 100644 index 0000000000..fffb1e68e6 --- /dev/null +++ b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/DispatcherServletInterceptor.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/* + * Based on org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeServer.java + * from the Apache Dubbo project. + */ + +package io.sermant.registry.grace.interceptors; + +import io.sermant.core.plugin.agent.entity.ExecuteContext; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.service.PluginServiceManager; +import io.sermant.core.utils.ReflectUtils; +import io.sermant.core.utils.StringUtils; +import io.sermant.registry.config.GraceConfig; +import io.sermant.registry.config.grace.GraceConstants; +import io.sermant.registry.config.grace.GraceContext; +import io.sermant.registry.config.grace.GraceShutDownManager; +import io.sermant.registry.context.RegisterContext; +import io.sermant.registry.context.RegisterContext.ClientInfo; +import io.sermant.registry.services.GraceService; + +import java.util.function.BiFunction; +import java.util.function.Function; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Spring Web request interceptors + * + * @author zhouss + * @since 2022-05-23 + */ +public class DispatcherServletInterceptor extends GraceSwitchInterceptor { + private final GraceService graceService; + + private final GraceConfig graceConfig; + + private Consumer addHeader; + + private Function getServerPort; + + private Function getRemoteAddr; + + private BiFunction getHeader; + + /** + * Constructor + */ + public DispatcherServletInterceptor() { + graceService = PluginServiceManager.getPluginService(GraceService.class); + graceConfig = PluginConfigManager.getPluginConfig(GraceConfig.class); + initFunction(); + } + + @Override + public ExecuteContext doBefore(ExecuteContext context) { + if (GraceContext.INSTANCE.getStartWarmUpTime() == 0) { + GraceContext.INSTANCE.setStartWarmUpTime(System.currentTimeMillis()); + } + Object[] arguments = context.getArguments(); + Object request = arguments[0]; + Object response = arguments[1]; + addGraceAddress(request); + final GraceShutDownManager graceShutDownManager = GraceContext.INSTANCE.getGraceShutDownManager(); + graceShutDownManager.increaseRequestCount(); + if (graceShutDownManager.isShutDown() && graceConfig.isEnableGraceShutdown()) { + // It has been marked as closed, and the number of incoming requests has been counted + final ClientInfo clientInfo = RegisterContext.INSTANCE.getClientInfo(); + addHeader.accept(response, GraceConstants.MARK_SHUTDOWN_SERVICE_ENDPOINT, + buildEndpoint(clientInfo.getIp(), clientInfo.getPort())); + addHeader.accept(response, GraceConstants.MARK_SHUTDOWN_SERVICE_ENDPOINT, + buildEndpoint(clientInfo.getHost(), clientInfo.getPort())); + addHeader.accept(response, GraceConstants.MARK_SHUTDOWN_SERVICE_NAME, clientInfo.getServiceName()); + } + return context; + } + + @Override + public ExecuteContext doAfter(ExecuteContext context) { + GraceContext.INSTANCE.getGraceShutDownManager().decreaseRequestCount(); + return context; + } + + @Override + public ExecuteContext doThrow(ExecuteContext context) { + GraceContext.INSTANCE.getGraceShutDownManager().decreaseRequestCount(); + return context; + } + + private void addGraceAddress(Object request) { + if (graceConfig.isEnableSpring() && graceConfig.isEnableGraceShutdown() && graceConfig.isEnableOfflineNotify() + && GraceConstants.GRACE_OFFLINE_SOURCE_VALUE + .equals(getHeader.apply(request, GraceConstants.GRACE_OFFLINE_SOURCE_KEY))) { + String address = getHeader.apply(request, GraceConstants.SERMANT_GRACE_ADDRESS); + if (StringUtils.isBlank(address)) { + address = getRemoteAddr.apply(request) + ":" + getServerPort.apply(request); + } + graceService.addAddress(address); + } + } + + private void addHeader(Object httpServletResponse, String key, String value) { + ReflectUtils.invokeMethod(httpServletResponse, "addHeader", new Class[]{String.class, String.class}, + new Object[]{key, value}); + } + + private int getServerPort(Object httpServletRequest) { + return (int) ReflectUtils.invokeMethodWithNoneParameter(httpServletRequest, "getServerPort").orElse(0); + } + + private String getRemoteAddr(Object httpServletRequest) { + return getString(httpServletRequest, "getRemoteAddr"); + } + + private String getHeader(Object httpServletRequest, String key) { + return (String) ReflectUtils.invokeMethod(httpServletRequest, "getHeader", new Class[]{String.class}, + new Object[]{key}).orElse(null); + } + + private String getString(Object object, String method) { + return (String) ReflectUtils.invokeMethodWithNoneParameter(object, method).orElse(null); + } + + private void initFunction() { + boolean canLoadLowVersion = canLoadLowVersion(); + if (canLoadLowVersion) { + addHeader = (obj, key, value) -> ((HttpServletResponse) obj).addHeader(key, value); + getServerPort = obj -> ((HttpServletRequest) obj).getServerPort(); + getRemoteAddr = obj -> ((HttpServletRequest) obj).getRemoteAddr(); + getHeader = (obj, key) -> ((HttpServletRequest) obj).getHeader(key); + } else { + addHeader = this::addHeader; + getServerPort = this::getServerPort; + getRemoteAddr = this::getRemoteAddr; + getHeader = this::getHeader; + } + } + + private boolean canLoadLowVersion() { + try { + Class.forName(HttpServletRequest.class.getCanonicalName()); + } catch (NoClassDefFoundError | ClassNotFoundException error) { + return false; + } + return true; + } + + /** + * mapping + * + * @param parameter1 + * @param parameter2 + * @param parameter3 + * @author proveceee + * @since 2024-11-15 + */ + @FunctionalInterface + private interface Consumer { + void accept(T t, R r, U u); + } +} diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/GraceSwitchInterceptor.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/GraceSwitchInterceptor.java index 20e89021c7..11ebc67c6a 100644 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/GraceSwitchInterceptor.java +++ b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/GraceSwitchInterceptor.java @@ -74,7 +74,7 @@ protected boolean isEnabled() { * @return endpoint */ protected String buildEndpoint(String host, int port) { - return String.format(Locale.ENGLISH, "%s:%s", host, port); + return host + ":" + port; } /** diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/SpringWebHandlerInterceptor.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/SpringWebHandlerInterceptor.java deleted file mode 100644 index d264a995c9..0000000000 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/grace/interceptors/SpringWebHandlerInterceptor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. 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.registry.grace.interceptors; - -import io.sermant.core.plugin.agent.entity.ExecuteContext; -import io.sermant.registry.inject.grace.SpringRequestInterceptor; - -import org.springframework.web.servlet.HandlerExecutionChain; -import org.springframework.web.servlet.HandlerInterceptor; - -/** - * Inject the request interceptor - * - * @author zhouss - * @since 2022-05-23 - */ -public class SpringWebHandlerInterceptor extends GraceSwitchInterceptor { - private HandlerInterceptor handlerInterceptor; - - @Override - protected ExecuteContext doBefore(ExecuteContext context) { - final Object object = context.getObject(); - if (object instanceof HandlerExecutionChain) { - HandlerExecutionChain chain = (HandlerExecutionChain) object; - chain.addInterceptor(getInterceptor()); - } - return context; - } - - private HandlerInterceptor getInterceptor() { - if (handlerInterceptor == null) { - handlerInterceptor = new SpringRequestInterceptor(); - } - return handlerInterceptor; - } -} diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/inject/grace/SpringRequestInterceptor.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/inject/grace/SpringRequestInterceptor.java deleted file mode 100644 index 401174a268..0000000000 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/java/io/sermant/registry/inject/grace/SpringRequestInterceptor.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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. - */ - -/* - * Based on org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeServer.java - * from the Apache Dubbo project. - */ - -package io.sermant.registry.inject.grace; - -import io.sermant.core.plugin.config.PluginConfigManager; -import io.sermant.core.plugin.service.PluginServiceManager; -import io.sermant.core.utils.StringUtils; -import io.sermant.registry.config.GraceConfig; -import io.sermant.registry.config.grace.GraceConstants; -import io.sermant.registry.config.grace.GraceContext; -import io.sermant.registry.config.grace.GraceShutDownManager; -import io.sermant.registry.context.RegisterContext; -import io.sermant.registry.context.RegisterContext.ClientInfo; -import io.sermant.registry.grace.interceptors.SpringWebHandlerInterceptor; -import io.sermant.registry.services.GraceService; - -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * Spring Web request pre-interceptors, dynamically joined by interceptors, are added with each request, - * {@link SpringWebHandlerInterceptor} - * - * @author zhouss - * @since 2022-05-23 - */ -public class SpringRequestInterceptor implements HandlerInterceptor { - private final GraceService graceService; - - private final GraceConfig graceConfig; - - /** - * Constructor - */ - public SpringRequestInterceptor() { - graceService = PluginServiceManager.getPluginService(GraceService.class); - graceConfig = PluginConfigManager.getPluginConfig(GraceConfig.class); - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - if (GraceContext.INSTANCE.getStartWarmUpTime() == 0) { - GraceContext.INSTANCE.setStartWarmUpTime(System.currentTimeMillis()); - } - addGraceAddress(request); - final GraceShutDownManager graceShutDownManager = GraceContext.INSTANCE.getGraceShutDownManager(); - graceShutDownManager.increaseRequestCount(); - if (graceShutDownManager.isShutDown() && graceConfig.isEnableGraceShutdown()) { - // It has been marked as closed, and the number of incoming requests has been counted - final ClientInfo clientInfo = RegisterContext.INSTANCE.getClientInfo(); - response.addHeader(GraceConstants.MARK_SHUTDOWN_SERVICE_ENDPOINT, - buildEndpoint(clientInfo.getIp(), clientInfo.getPort())); - response.addHeader(GraceConstants.MARK_SHUTDOWN_SERVICE_ENDPOINT, - buildEndpoint(clientInfo.getHost(), clientInfo.getPort())); - response.addHeader(GraceConstants.MARK_SHUTDOWN_SERVICE_NAME, clientInfo.getServiceName()); - } - return true; - } - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) { - } - - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, - Exception ex) { - GraceContext.INSTANCE.getGraceShutDownManager().decreaseRequestCount(); - } - - private String buildEndpoint(String host, int port) { - return host + ":" + port; - } - - private void addGraceAddress(HttpServletRequest request) { - if (graceConfig.isEnableSpring() && graceConfig.isEnableGraceShutdown() && graceConfig.isEnableOfflineNotify() - && GraceConstants.GRACE_OFFLINE_SOURCE_VALUE - .equals(request.getHeader(GraceConstants.GRACE_OFFLINE_SOURCE_KEY))) { - String address = request.getHeader(GraceConstants.SERMANT_GRACE_ADDRESS); - if (StringUtils.isBlank(address)) { - address = request.getRemoteAddr() + ":" + request.getServerPort(); - } - graceService.addAddress(address); - } - } -} diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer index d7af3eef33..8bc2d2c8ee 100644 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer +++ b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.agent.declarer.PluginDeclarer @@ -36,7 +36,7 @@ io.sermant.registry.declarers.health.HealthIndicatorDeclarer io.sermant.registry.grace.declarers.RegistryDelayDeclarer io.sermant.registry.grace.declarers.SpringRibbonWarmUpDeclarer io.sermant.registry.grace.declarers.SpringLoadbalancerWarmUpDeclarer -io.sermant.registry.grace.declarers.SpringWebHandlerDeclarer +io.sermant.registry.grace.declarers.DispatcherServletDeclarer io.sermant.registry.grace.declarers.SpringRibbonBalancerDeclarer io.sermant.registry.grace.declarers.SpringCacheManagerDeclarer io.sermant.registry.grace.declarers.SpringLoadbalancerFeignResponseDeclarer diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/inject/grace/SpringRequestInterceptorTest.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/grace/interceptors/DispatcherServletInterceptorTest.java similarity index 81% rename from sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/inject/grace/SpringRequestInterceptorTest.java rename to sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/grace/interceptors/DispatcherServletInterceptorTest.java index c082508ed2..0dc46b589d 100644 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/inject/grace/SpringRequestInterceptorTest.java +++ b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/grace/interceptors/DispatcherServletInterceptorTest.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (C) 2022-2024 Huawei Technologies Co., Ltd. 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 + * 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, @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.sermant.registry.inject.grace; +package io.sermant.registry.grace.interceptors; +import io.sermant.core.plugin.agent.entity.ExecuteContext; import io.sermant.core.plugin.config.PluginConfigManager; import io.sermant.core.plugin.service.PluginServiceManager; import io.sermant.registry.config.GraceConfig; @@ -44,7 +45,7 @@ * @author zhouss * @since 2022-09-06 */ -public class SpringRequestInterceptorTest { +public class DispatcherServletInterceptorTest { private final String testAddress = "localhost:8099"; private final List addresses = new ArrayList<>(); @@ -63,7 +64,9 @@ public void addAddress(String address) { } }; - private SpringRequestInterceptor interceptor; + private ExecuteContext executeContext; + + private DispatcherServletInterceptor interceptor; @Mock private HttpServletRequest request; @@ -76,7 +79,7 @@ public void addAddress(String address) { private MockedStatic pluginConfigManagerMockedStatic; @Before - public void setUp() { + public void setUp() throws NoSuchMethodException { MockitoAnnotations.openMocks(this); pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); pluginConfigManagerMockedStatic = Mockito.mockStatic(PluginConfigManager.class); @@ -91,7 +94,10 @@ public void setUp() { .thenReturn(GraceConstants.GRACE_OFFLINE_SOURCE_VALUE); Mockito.when(request.getHeader(GraceConstants.SERMANT_GRACE_ADDRESS)) .thenReturn(testAddress); - interceptor = new SpringRequestInterceptor(); + interceptor = new DispatcherServletInterceptor(); + Object[] arguments = new Object[]{request, response}; + executeContext = ExecuteContext.forMemberMethod(new Object(), String.class.getMethod("trim"), arguments, null, + null); } @After @@ -101,9 +107,9 @@ public void tearDown() throws Exception { } @Test - public void preHandle() { + public void before() { GraceContext.INSTANCE.getGraceShutDownManager().setShutDown(true); - interceptor.preHandle(request, response, new Object()); + interceptor.doBefore(executeContext); Assert.assertTrue(GraceContext.INSTANCE.getStartWarmUpTime() > 0); Assert.assertTrue(addresses.contains(testAddress)); Assert.assertTrue(GraceContext.INSTANCE.getGraceShutDownManager().getRequestCount() > 0); @@ -114,13 +120,13 @@ public void preHandle() { } @Test - public void postHandle() { - interceptor.postHandle(request, response, new Object(), null); + public void after() { + interceptor.doAfter(executeContext); } @Test - public void afterCompletion() { - interceptor.afterCompletion(request, response, new Object(), null); + public void doThrow() { + interceptor.doThrow(executeContext); Assert.assertTrue(GraceContext.INSTANCE.getGraceShutDownManager().getRequestCount() < 0); GraceContext.INSTANCE.getGraceShutDownManager().increaseRequestCount(); } diff --git a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/grace/interceptors/SpringWebHandlerInterceptorTest.java b/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/grace/interceptors/SpringWebHandlerInterceptorTest.java deleted file mode 100644 index 0b090b6a52..0000000000 --- a/sermant-plugins/sermant-service-registry/spring-cloud-registry-plugin/src/test/java/io/sermant/registry/grace/interceptors/SpringWebHandlerInterceptorTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. 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.registry.grace.interceptors; - -import io.sermant.core.plugin.agent.entity.ExecuteContext; -import io.sermant.core.plugin.config.PluginConfigManager; -import io.sermant.core.plugin.service.PluginServiceManager; -import io.sermant.registry.config.GraceConfig; -import io.sermant.registry.config.RegisterConfig; -import io.sermant.registry.services.GraceService; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.springframework.web.servlet.HandlerExecutionChain; - -import java.util.Objects; - -/** - * Add an interceptor test - * - * @author zhouss - * @since 2022-06-30 - */ -public class SpringWebHandlerInterceptorTest { - private final GraceConfig graceConfig = new GraceConfig(); - - /** - * PluginConfigManager mock object - */ - public MockedStatic pluginConfigManagerMockedStatic; - - private MockedStatic pluginServiceManagerMockedStatic; - - /** - * Initialize - */ - @Before - public void init() { - graceConfig.setEnableSpring(true); - pluginConfigManagerMockedStatic = Mockito.mockStatic(PluginConfigManager.class); - pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(GraceConfig.class)) - .thenReturn(graceConfig); - pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(RegisterConfig.class)) - .thenReturn(new RegisterConfig()); - pluginServiceManagerMockedStatic = Mockito.mockStatic(PluginServiceManager.class); - pluginServiceManagerMockedStatic.when(() -> PluginServiceManager.getPluginService(GraceService.class)) - .thenReturn(new GraceService() { - @Override - public void shutdown() { - - } - - @Override - public void addAddress(String address) { - - } - }); - } - - @After - public void tearDown() { - pluginConfigManagerMockedStatic.close(); - pluginServiceManagerMockedStatic.close(); - } - - /** - * Test add interceptors - */ - @Test - public void testAddInterceptor() { - final HandlerExecutionChain chain = new HandlerExecutionChain(new Object()); - final ExecuteContext executeContext = ExecuteContext.forMemberMethod(chain, null, null, null, null); - final SpringWebHandlerInterceptor springWebHandlerInterceptor = new SpringWebHandlerInterceptor(); - springWebHandlerInterceptor.doBefore(executeContext); - Assert.assertTrue(Objects.requireNonNull(chain.getInterceptors()).length > 0); - } -}