diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/interceptor/AbstractInterceptor.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/interceptor/AbstractInterceptor.java index dbf14a9e..504a576c 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/interceptor/AbstractInterceptor.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/interceptor/AbstractInterceptor.java @@ -227,7 +227,6 @@ protected Object invokeWithRetry(O invocation, MethodContext ctx) { Response response = retrySupplier.get(); return response == null ? null : response.getResponse(); } - } /** diff --git a/joylive-package/src/main/assembly/config/microservice.json b/joylive-package/src/main/assembly/config/microservice.json index 6ac3ac7b..b7d89032 100644 --- a/joylive-package/src/main/assembly/config/microservice.json +++ b/joylive-package/src/main/assembly/config/microservice.json @@ -91,6 +91,15 @@ "loadBalancePolicy": { "policyType": "ROUND_ROBIN" }, + "retryPolicy": { + "type": "Resilience4j", + "retry": 10, + "retryInterval": 1000, + "timeout": 5000, + "retryStatuses": [500, 502], + "retryExceptions": ["java.lang.NullPointException"], + "version": 1704038400000 + }, "rateLimitPolicies": [ { "name": "limit-rule-1", diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/pom.xml b/joylive-plugin/joylive-router/joylive-router-springcloud3/pom.xml index 82d70cc2..7db7e345 100644 --- a/joylive-plugin/joylive-router/joylive-router-springcloud3/pom.xml +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/pom.xml @@ -17,6 +17,7 @@ 2.7.18 5.3.31 4.0.4 + 11.10 @@ -68,5 +69,17 @@ ${servlet.api.version} provided + + org.springframework.cloud + spring-cloud-openfeign-core + ${spring.cloud.version} + provided + + + io.github.openfeign + feign-core + ${openfeign.version} + provided + \ No newline at end of file diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/FeignRequestDefinition.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/FeignRequestDefinition.java new file mode 100644 index 00000000..3fc1c726 --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/FeignRequestDefinition.java @@ -0,0 +1,75 @@ +/* + * Copyright © ${year} ${owner} (${email}) + * + * 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 com.jd.live.agent.plugin.router.springcloud.v3.definition; + +import com.jd.live.agent.bootstrap.classloader.ResourcerType; +import com.jd.live.agent.core.bytekit.matcher.MatcherBuilder; +import com.jd.live.agent.core.extension.annotation.ConditionalOnClass; +import com.jd.live.agent.core.extension.annotation.ConditionalOnProperty; +import com.jd.live.agent.core.extension.annotation.Extension; +import com.jd.live.agent.core.inject.annotation.Inject; +import com.jd.live.agent.core.inject.annotation.InjectLoader; +import com.jd.live.agent.core.inject.annotation.Injectable; +import com.jd.live.agent.core.plugin.definition.InterceptorDefinition; +import com.jd.live.agent.core.plugin.definition.InterceptorDefinitionAdapter; +import com.jd.live.agent.core.plugin.definition.PluginDefinitionAdapter; +import com.jd.live.agent.governance.config.GovernanceConfig; +import com.jd.live.agent.governance.invoke.InvocationContext; +import com.jd.live.agent.governance.invoke.filter.OutboundFilter; +import com.jd.live.agent.plugin.router.springcloud.v3.interceptor.FeignRequestInterceptor; + +import java.util.List; + +/** + * FeignRequestDefinition + * + * @since 1.0.0 + */ +@Injectable +@Extension(value = "InterceptingClientHttpRequestPluginDefinition_v3") +@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_ENABLED, matchIfMissing = true) +@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_SPRING_ENABLED, matchIfMissing = true) +@ConditionalOnProperty(value = GovernanceConfig.CONFIG_REGISTRY_ENABLED, matchIfMissing = true) +@ConditionalOnProperty(value = GovernanceConfig.CONFIG_TRANSMISSION_ENABLED, matchIfMissing = true) +@ConditionalOnClass(FeignRequestDefinition.TYPE_FEIGN_CLIENT_CLASS) +public class FeignRequestDefinition extends PluginDefinitionAdapter { + + public static final String TYPE_FEIGN_CLIENT_CLASS = "feign.Client"; + + private static final String METHOD_EXECUTE_INTERNAL = "execute"; + + private static final String[] ARGUMENT_EXECUTE = new String[]{ + "feign.Request", "feign.Request.Options" + }; + + @Inject(InvocationContext.COMPONENT_INVOCATION_CONTEXT) + private InvocationContext context; + + @Inject + @InjectLoader(ResourcerType.PLUGIN) + private List filters; + + public FeignRequestDefinition() { + this.matcher = () -> MatcherBuilder.isImplement(TYPE_FEIGN_CLIENT_CLASS); + this.interceptors = new InterceptorDefinition[]{ + new InterceptorDefinitionAdapter( + MatcherBuilder.named(METHOD_EXECUTE_INTERNAL). + and(MatcherBuilder.arguments(ARGUMENT_EXECUTE)), + () -> new FeignRequestInterceptor(context, filters) + ) + }; + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/FeignRequestInterceptor.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/FeignRequestInterceptor.java new file mode 100644 index 00000000..7cc1b423 --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/FeignRequestInterceptor.java @@ -0,0 +1,81 @@ +/* + * Copyright © ${year} ${owner} (${email}) + * + * 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 com.jd.live.agent.plugin.router.springcloud.v3.interceptor; + +import com.jd.live.agent.bootstrap.bytekit.context.ExecutableContext; +import com.jd.live.agent.bootstrap.bytekit.context.MethodContext; +import com.jd.live.agent.bootstrap.exception.RejectException; +import com.jd.live.agent.governance.context.RequestContext; +import com.jd.live.agent.governance.context.bag.Carrier; +import com.jd.live.agent.governance.interceptor.AbstractInterceptor.AbstractHttpOutboundInterceptor; +import com.jd.live.agent.governance.invoke.InvocationContext; +import com.jd.live.agent.governance.invoke.OutboundInvocation.HttpOutboundInvocation; +import com.jd.live.agent.governance.invoke.filter.OutboundFilter; +import com.jd.live.agent.governance.response.Response; +import com.jd.live.agent.plugin.router.springcloud.v3.request.FeignOutboundRequest; +import com.jd.live.agent.plugin.router.springcloud.v3.response.FeignOutboundResponse; +import feign.Request; +import org.springframework.http.HttpStatus; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * FeignRequestInterceptor + * + * @since 1.0.0 + */ +public class FeignRequestInterceptor extends AbstractHttpOutboundInterceptor { + + public FeignRequestInterceptor(InvocationContext context, List filters) { + super(context, filters); + } + + /** + * Enhanced logic before method execution
+ *

+ * + * @param ctx ExecutableContext + * @see feign.Client#execute(Request, Request.Options) + */ + @Override + public void onEnter(ExecutableContext ctx) { + MethodContext mc = (MethodContext) ctx; + Request request = (Request) mc.getArguments()[0]; + HttpOutboundInvocation invocation; + try { + invocation = process(new FeignOutboundRequest(request, RequestContext.getAttribute(Carrier.ATTRIBUTE_SERVICE_ID))); + mc.setResult(invokeWithRetry(invocation, mc)); + } catch (RejectException e) { + mc.setResult(feign.Response.builder().status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value()) + .body(e.getMessage().getBytes(StandardCharsets.UTF_8)).build()); + } + mc.setSkip(true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onExit(ExecutableContext ctx) { + RequestContext.remove(); + } + + @Override + protected Response createResponse(Object result, Throwable throwable) { + return new FeignOutboundResponse((feign.Response) result, throwable); + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/request/FeignOutboundRequest.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/request/FeignOutboundRequest.java new file mode 100644 index 00000000..9d07a52a --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/request/FeignOutboundRequest.java @@ -0,0 +1,84 @@ +/* + * Copyright © ${year} ${owner} (${email}) + * + * 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 com.jd.live.agent.plugin.router.springcloud.v3.request; + +import com.jd.live.agent.core.util.cache.LazyObject; +import com.jd.live.agent.governance.request.AbstractHttpRequest; +import com.jd.live.agent.governance.request.Cookie; +import com.jd.live.agent.governance.request.HttpMethod; +import feign.Request; + +import java.net.HttpCookie; +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +/** + * FeignOutboundRequest + * + * @since 1.0.0 + */ +public class FeignOutboundRequest extends AbstractHttpRequest.AbstractHttpOutboundRequest { + + private static final String COOKIE_HEADER = "Cookie"; + + private final String serviceId; + + public FeignOutboundRequest(Request request, String serviceId) { + super(request); + this.serviceId = serviceId; + this.uri = URI.create(request.url()); + this.queries = new LazyObject<>(() -> parseQuery(request.requestTemplate().queryLine())); + this.headers = new LazyObject<>(convertToListMap(request.headers())); + this.cookies = new LazyObject<>(() -> parseCookie(request.headers().get(COOKIE_HEADER))); + } + + public static Map> convertToListMap(Map> map) { + return map.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> new ArrayList<>(e.getValue()) + )); + } + + protected Map> parseCookie(Collection headers) { + Map> result = new HashMap<>(); + if (headers == null || headers.isEmpty()) { + return result; + } + for (String header : headers) { + if (header != null && !header.isEmpty()) { + String[] values = header.split(";"); + List cookies; + for (String cookie : values) { + cookies = HttpCookie.parse(cookie.trim()); + cookies.forEach(c -> result.computeIfAbsent(c.getName(), k -> new ArrayList<>()).add(new Cookie(c.getName(), c.getValue()))); + } + } + } + return result; + } + + @Override + public HttpMethod getHttpMethod() { + Request.HttpMethod method = request.httpMethod(); + try { + return method == null ? null : HttpMethod.valueOf(method.name()); + } catch (IllegalArgumentException ignore) { + return null; + } + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/ClientHttpOutboundResponse.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/ClientHttpOutboundResponse.java index 4972c212..4f3f4b56 100644 --- a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/ClientHttpOutboundResponse.java +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/ClientHttpOutboundResponse.java @@ -1,3 +1,18 @@ +/* + * Copyright © ${year} ${owner} (${email}) + * + * 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 com.jd.live.agent.plugin.router.springcloud.v3.response; import com.jd.live.agent.governance.response.AbstractHttpResponse.AbstractHttpOutboundResponse; diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/FeignOutboundResponse.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/FeignOutboundResponse.java new file mode 100644 index 00000000..73e019af --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/FeignOutboundResponse.java @@ -0,0 +1,37 @@ +/* + * Copyright © ${year} ${owner} (${email}) + * + * 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 com.jd.live.agent.plugin.router.springcloud.v3.response; + +import com.jd.live.agent.governance.response.AbstractHttpResponse.AbstractHttpOutboundResponse; +import feign.Response; + +/** + * FeignOutboundResponse + * + * @since 1.0.0 + */ +public class FeignOutboundResponse extends AbstractHttpOutboundResponse { + + public FeignOutboundResponse(Response response, Throwable throwable) { + super(response, throwable); + } + + @Override + public String getCode() { + return response == null ? "500" : String.valueOf(response.status()); + } + +} diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/resources/META-INF/services/com.jd.live.agent.core.plugin.definition.PluginDefinition b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/resources/META-INF/services/com.jd.live.agent.core.plugin.definition.PluginDefinition index 729c6808..08ecec2c 100644 --- a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/resources/META-INF/services/com.jd.live.agent.core.plugin.definition.PluginDefinition +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/resources/META-INF/services/com.jd.live.agent.core.plugin.definition.PluginDefinition @@ -5,4 +5,5 @@ com.jd.live.agent.plugin.router.springcloud.v3.definition.FilteringWebHandlerDef com.jd.live.agent.plugin.router.springcloud.v3.definition.StickySessionDefinition com.jd.live.agent.plugin.router.springcloud.v3.definition.PreferSameInstanceDefinition com.jd.live.agent.plugin.router.springcloud.v3.definition.ZonePreferenceDefinition -com.jd.live.agent.plugin.router.springcloud.v3.definition.ReactorLoadBalancerDefinition \ No newline at end of file +com.jd.live.agent.plugin.router.springcloud.v3.definition.ReactorLoadBalancerDefinition +com.jd.live.agent.plugin.router.springcloud.v3.definition.FeignRequestDefinition \ No newline at end of file