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 11a701fc..4664a2dc 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 @@ -24,12 +24,20 @@ import com.jd.live.agent.governance.invoke.OutboundInvocation; import com.jd.live.agent.governance.invoke.OutboundInvocation.HttpOutboundInvocation; import com.jd.live.agent.governance.invoke.filter.*; +import com.jd.live.agent.governance.invoke.retry.Retrier; +import com.jd.live.agent.governance.invoke.retry.RetrierFactory; +import com.jd.live.agent.governance.policy.service.ServicePolicy; +import com.jd.live.agent.governance.policy.service.retry.RetryPolicy; import com.jd.live.agent.governance.request.HttpRequest.HttpInboundRequest; import com.jd.live.agent.governance.request.HttpRequest.HttpOutboundRequest; import com.jd.live.agent.governance.request.ServiceRequest.InboundRequest; import com.jd.live.agent.governance.request.ServiceRequest.OutboundRequest; +import com.jd.live.agent.governance.response.Response; +import java.lang.reflect.Method; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * AbstractInterceptor is the base class for all interceptors within the framework. @@ -117,15 +125,18 @@ public static abstract class AbstractOutboundInterceptor retrierFactories; + /** * Constructs a new AbstractOutboundInterceptor with the given InvocationContext and outbound filters. * - * @param context the InvocationContext for the current invocation - * @param filters a list of OutboundFilter instances to be applied to the outbound request + * @param context the InvocationContext for the current invocation + * @param filters a list of OutboundFilter instances to be applied to the outbound request */ - public AbstractOutboundInterceptor(InvocationContext context, List filters) { + public AbstractOutboundInterceptor(InvocationContext context, List filters, Map retrierFactories) { super(context); this.outboundFilters = filters == null ? new OutboundFilter[0] : filters.toArray(new OutboundFilter[0]); + this.retrierFactories = retrierFactories; } /** @@ -157,6 +168,30 @@ protected O process(R request) { */ protected abstract void process(O invocation); + /** + * Create a retry supplier + * + * @param target The object on which the method is called. + * @param method The method being called. + * @param allArguments All parameters of the method. + * @param result The result of the method call. + * @return Returns a supplier for retry logic. + */ + protected abstract Supplier createRetrySupplier(Object target, Method method, Object[] allArguments, Object result); + + protected Response tryRetry(O invocation, Response response, Supplier retrySupplier) { + ServicePolicy servicePolicy = invocation == null ? null : invocation.getServiceMetadata().getServicePolicy(); + RetryPolicy retryPolicy = servicePolicy == null ? null : servicePolicy.getRetryPolicy(); + if (retryPolicy != null && retrierFactories != null) { + RetrierFactory retrierFactory = retrierFactories.get(retryPolicy.getType()); + Retrier retrier = retrierFactory == null ? null : retrierFactory.get(retryPolicy); + if (retrier != null && retrier.isRetryable(response)) { + return retrier.execute(retrySupplier); + } + } + return null; + } + } /** @@ -176,8 +211,8 @@ public static abstract class AbstractRouteInterceptor filters) { super(context); @@ -199,8 +234,8 @@ protected O routing(R request) { /** * Initiates the routing process for the outbound request by creating an OutboundInvocation and providing a list of candidate endpoints. * - * @param request the outbound request to be routed - * @param instances a list of Endpoint instances to be considered during routing + * @param request the outbound request to be routed + * @param instances a list of Endpoint instances to be considered during routing * @return the resulting OutboundInvocation after initiating the routing process */ protected O routing(R request, List instances) { @@ -230,8 +265,8 @@ protected O routing(R request, List instances) { * Creates a new OutboundInvocation for the given outbound request and initializes it with a list of Endpoint instances. * This method can be overridden by concrete subclasses if additional initialization is required. * - * @param request the outbound request for which to create an OutboundInvocation - * @param instances a list of Endpoint instances to be associated with the OutboundInvocation + * @param request the outbound request for which to create an OutboundInvocation + * @param instances a list of Endpoint instances to be associated with the OutboundInvocation * @return a new OutboundInvocation instance with the specified Endpoint instances */ protected O createOutlet(R request, List instances) { @@ -252,8 +287,8 @@ public static abstract class AbstractHttpInboundInterceptor filters) { super(context, filters); @@ -283,11 +318,11 @@ public static abstract class AbstractHttpOutboundInterceptor filters) { - super(context, filters); + public AbstractHttpOutboundInterceptor(InvocationContext context, List filters, Map retrierFactories) { + super(context, filters, retrierFactories); } @Override @@ -368,9 +403,9 @@ public static abstract class AbstractGatewayInterceptor inboundFilters, List routeFilters) { super(context); diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/retry/Retrier.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/retry/Retrier.java index c59413b3..09593407 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/retry/Retrier.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/invoke/retry/Retrier.java @@ -16,6 +16,7 @@ package com.jd.live.agent.governance.invoke.retry; import com.jd.live.agent.governance.policy.service.retry.RetryPolicy; +import com.jd.live.agent.governance.response.Response; import java.util.function.Supplier; @@ -26,6 +27,14 @@ */ public interface Retrier { + /** + * Determine whether to retry + * + * @param response Response + * @return true: retry, false: no need to retry + */ + boolean isRetryable(Response response); + /** * Execute retry logic * @@ -33,7 +42,7 @@ public interface Retrier { * @param Response type * @return Response */ - T execute(Supplier supplier); + T execute(Supplier supplier); /** * Get failover policy diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/PolicyId.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/PolicyId.java index fb57ffe2..974c3534 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/PolicyId.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/PolicyId.java @@ -120,7 +120,9 @@ protected Map supplementTag() { /** * Supplements the existing tag map with additional key-value pairs. * - *

This method takes a variable number of string arguments representing key-value pairs of tags to be added to the current tag map. It creates a new map containing all the entries of the original tag map, if it exists, and then adds the new key-value pairs to it.

+ *

This method takes a variable number of string arguments representing key-value pairs of tags to be added to the current tag map. + * It creates a new map containing all the entries of the original tag map, if it exists, + * and then adds the new key-value pairs to it.

* *

If the number of key-value pairs provided is not an even number, the last provided value will be ignored.

* diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/retry/RetryPolicy.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/retry/RetryPolicy.java index a246c1d6..54a0f410 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/retry/RetryPolicy.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/policy/service/retry/RetryPolicy.java @@ -49,6 +49,11 @@ public class RetryPolicy extends PolicyId implements PolicyInheritWithId retryableStatusCodes = new HashSet<>(Arrays.asList(500, 502, 503)); + private Set retryableStatusCodes = new HashSet<>(Arrays.asList("500", "502", "503")); /** * A collection of retryable exception class names. @@ -82,6 +87,9 @@ public void supplement(RetryPolicy source) { if (source == null) { return; } + if (type == null) { + type = source.type; + } if (retry == null) { retry = source.retry; } diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/request/HttpRequest.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/request/HttpRequest.java index eb15de2f..b969f52b 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/request/HttpRequest.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/request/HttpRequest.java @@ -148,7 +148,6 @@ interface HttpInboundRequest extends HttpRequest, InboundRequest { * This interface represents HTTP requests that are sent by a service. *

* - * @author Zhiguo.Chen * @since 1.0.0 */ interface HttpOutboundRequest extends HttpRequest, OutboundRequest { diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractHttpResponse.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractHttpResponse.java new file mode 100644 index 00000000..ad6a906f --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractHttpResponse.java @@ -0,0 +1,281 @@ +/* + * 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.governance.response; + +import com.jd.live.agent.core.util.cache.LazyObject; +import com.jd.live.agent.governance.request.Cookie; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * AbstractHttpResponse + * + * @since 1.0.0 + */ +public abstract class AbstractHttpResponse extends AbstractServiceResponse implements HttpResponse { + + /** + * Key for the "Host" header in HTTP requests. + */ + protected static final String HEAD_HOST_KEY = "Host"; + + /** + * Key for the "serviceGroup" header in HTTP requests. + */ + protected static final String HEAD_GROUP_KEY = "serviceGroup"; + + /** + * Lazily evaluated, parsed cookies from the HTTP request. + */ + protected LazyObject>> cookies; + + /** + * Lazily evaluated, parsed query parameters from the HTTP request URL. + */ + protected LazyObject>> queries; + + /** + * Lazily evaluated HTTP headers from the request. + */ + protected LazyObject>> headers; + + /** + * The URI of the HTTP request. + */ + protected URI uri; + + /** + * Lazily evaluated port number of the request URI. + */ + protected LazyObject port; + + /** + * Lazily evaluated host of the request URI. + */ + protected LazyObject host; + + /** + * Lazily evaluated scheme of the request URI. + */ + protected LazyObject schema; + + /** + * Constructs an instance of {@code AbstractHttpResponse} with the original response object. + * + * @param response The original response object. + * @param throwable The original exception. + */ + public AbstractHttpResponse(T response, Throwable throwable) { + super(response, throwable); + port = new LazyObject<>(this::parsePort); + host = new LazyObject<>(this::parseHost); + schema = new LazyObject<>(this::parseScheme); + } + + @Override + public URI getURI() { + return uri; + } + + @Override + public String getSchema() { + String result = schema.get(); + return result == null || result.isEmpty() ? null : result; + } + + @Override + public int getPort() { + return port.get(); + } + + @Override + public String getHost() { + String result = host.get(); + return result == null || result.isEmpty() ? null : result; + } + + @Override + public String getPath() { + return uri.getPath(); + } + + @Override + public Map> getHeaders() { + return headers.get(); + } + + @Override + public String getHeader(String key) { + if (key == null) return null; + List values = headers.get().get(key); + return values == null || values.isEmpty() ? null : values.get(0); + } + + @Override + public String getQuery(String key) { + if (key == null) return null; + List values = queries.get().get(key); + return values == null || values.isEmpty() ? null : values.get(0); + } + + @Override + public Map> getQueries() { + return queries.get(); + } + + @Override + public Map> getCookies() { + return cookies.get(); + } + + @Override + public String getCookie(String key) { + if (key == null) return null; + List values = cookies.get().get(key); + return values == null || values.isEmpty() ? null : values.get(0).getValue(); + } + + /** + * Parses the port number from the request URI. + * + * @return The port number, or a default value if it cannot be parsed from the URI. + */ + protected int parsePort() { + int result = uri.getPort(); + if (result < 0) { + result = parsePortByHeader(); + } + return result; + } + + /** + * Parses the port number from the "Host" header when it is not directly available from the URI. + * + * @return The port number parsed from the header, or -1 if it cannot be determined. + */ + protected int parsePortByHeader() { + int result = -1; + String header = getHeader(HEAD_HOST_KEY); + if (header != null) { + char ch; + for (int i = header.length() - 1; i > 0; i--) { + ch = header.charAt(i); + if (ch == ':') { + try { + result = Integer.parseInt(header.substring(i + 1)); + } catch (NumberFormatException ignore) { + } + break; + } else if (!Character.isDigit(ch)) { + break; + } + } + } + return result; + } + + /** + * Parses the host from the request URI. + * + * @return The host, or a default value if it cannot be parsed from the URI. + */ + protected String parseHost() { + String result = uri.getHost(); + if (result == null) { + result = parseHostByHeader(); + } + return result; + } + + /** + * Parses the host from the "Host" header when it is not directly available from the URI. + * + * @return The host parsed from the header, or null if it cannot be determined. + */ + protected String parseHostByHeader() { + String result = null; + String header = getHeader(HEAD_HOST_KEY); + if (header != null) { + char ch; + for (int i = header.length() - 1; i > 0; i--) { + ch = header.charAt(i); + if (ch == ':') { + result = header.substring(0, i); + break; + } else if (!Character.isDigit(ch)) { + break; + } + } + } + return result; + } + + /** + * Parses the scheme (protocol) from the request URI. + * + * @return The scheme, or null if it cannot be determined. + */ + protected String parseScheme() { + return uri.getScheme(); + } + + /** + * Parses query parameters from a query string. + * + * @param query The query string from the URI. + * @return A map of query parameter names to their values. + */ + protected Map> parseQuery(String query) { + Map> result = new HashMap<>(); + if (query == null || query.isEmpty()) { + return result; + } + int idx; + String key, value; + String[] pairs = query.split("&"); + for (String pair : pairs) { + idx = pair.indexOf('='); + if (idx > 0) { + key = pair.substring(0, idx); + value = pair.substring(idx + 1); + if (!value.isEmpty()) { + try { + // Assuming encoding is UTF-8. If not, change the encoding accordingly. + key = URLDecoder.decode(key, "UTF-8"); + value = URLDecoder.decode(value, "UTF-8"); + result.computeIfAbsent(key, k -> new ArrayList<>()).add(value); + } catch (UnsupportedEncodingException ignored) { + } + } + } + } + return result; + } + + public abstract static class AbstractHttpOutboundResponse extends AbstractHttpResponse + implements HttpResponse.HttpOutboundResponse { + + public AbstractHttpOutboundResponse(T response, Throwable throwable) { + super(response, throwable); + } + } +} diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractRpcResponse.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractRpcResponse.java new file mode 100644 index 00000000..7dcc9404 --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractRpcResponse.java @@ -0,0 +1,36 @@ +/* + * 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.governance.response; + +/** + * AbstractRpcResponse + * + * @since 1.0.0 + */ +public abstract class AbstractRpcResponse extends AbstractServiceResponse implements RpcResponse { + + public AbstractRpcResponse(T response, Throwable throwable) { + super(response, throwable); + } + + public abstract static class AbstractRpcOutboundResponse extends AbstractRpcResponse + implements RpcResponse.RpcOutboundResponse { + + public AbstractRpcOutboundResponse(T response, Throwable throwable) { + super(response, throwable); + } + } +} diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractServiceResponse.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractServiceResponse.java new file mode 100644 index 00000000..5f0f5758 --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/AbstractServiceResponse.java @@ -0,0 +1,36 @@ +/* + * 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.governance.response; + +import lombok.Getter; + +/** + * AbstractServiceResponse + * + * @since 1.0.0 + */ +@Getter +public abstract class AbstractServiceResponse implements ServiceResponse { + + protected final T response; + + private final Throwable throwable; + + public AbstractServiceResponse(T response, Throwable throwable) { + this.response = response; + this.throwable = throwable; + } +} diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/HttpResponse.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/HttpResponse.java new file mode 100644 index 00000000..d577d211 --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/HttpResponse.java @@ -0,0 +1,137 @@ +/* + * 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.governance.response; + +import com.jd.live.agent.governance.request.Cookie; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * HttpResponse + * + * @since 1.0.0 + */ +public interface HttpResponse extends ServiceResponse { + + /** + * Returns the URI of the request. + * + * @return The URI object representing the request's URI. + */ + URI getURI(); + + /** + * Returns the schema of the request, such as "http" or "https". + * + * @return The schema as a string. + */ + String getSchema(); + + /** + * Returns the port number of the request. + * + * @return The port number. + */ + int getPort(); + + /** + * Returns the host name of the request. + * + * @return The host name as a string. + */ + String getHost(); + + /** + * Returns the path of the request. + * + * @return The path as a string. + */ + String getPath(); + + /** + * Returns all headers of the request as a map. + * + * @return A map of header names to their respective list of values. + */ + Map> getHeaders(); + + /** + * Returns the values of a specific header. + * + * @param key The name of the header. + * @return A list of values for the specified header, or null if the header does not exist. + */ + default List getHeaders(String key) { + Map> result = getHeaders(); + return result == null || key == null ? null : result.get(key); + } + + /** + * Returns the first value of a specific header. + * + * @param key The name of the header. + * @return The first value of the specified header, or null if the header does not exist or has no values. + */ + default String getHeader(String key) { + List headers = getHeaders(key); + return headers == null || headers.isEmpty() ? null : headers.get(0); + } + + /** + * Returns all query parameters of the request as a map. + * + * @return A map of query parameter names to their respective list of values. + */ + Map> getQueries(); + + /** + * Returns the value of a specific query parameter. + * + * @param key The name of the query parameter. + * @return The value of the specified query parameter, or null if it does not exist. + */ + String getQuery(String key); + + /** + * Returns all cookies of the request as a map. + * + * @return A map of cookie names to their respective list of cookies. + */ + Map> getCookies(); + + /** + * Returns the value of a specific cookie. + * + * @param key The name of the cookie. + * @return The value of the specified cookie, or null if it does not exist. + */ + String getCookie(String key); + + /** + * Defines an interface for outbound HTTP response. + *

+ * This interface represents HTTP response that are received from a service. + *

+ * + * @since 1.0.0 + */ + interface HttpOutboundResponse extends HttpResponse, ServiceResponse.OutboundResponse { + + } + +} diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/Response.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/Response.java index 942af385..3701219f 100644 --- a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/Response.java +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/Response.java @@ -23,4 +23,25 @@ * @since 1.0.0 */ public interface Response extends Serializable { + + /** + * Response Code + * + * @return status code + */ + String getCode(); + + /** + * Abnormal response + * + * @return Exception + */ + Throwable getThrowable(); + + /** + * Origin response + * + * @return response + */ + Object getResponse(); } diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/RpcResponse.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/RpcResponse.java new file mode 100644 index 00000000..7a4d0985 --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/RpcResponse.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.governance.response; + +/** + * RpcResponse + * + * @since 1.0.0 + */ +public interface RpcResponse extends ServiceResponse { + + /** + * Defines an interface for outbound RPC response. + *

+ * This interface represents RPC response that are received from a service. + *

+ * + * @since 1.0.0 + */ + interface RpcOutboundResponse extends RpcResponse, OutboundResponse { + + } + +} diff --git a/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/ServiceResponse.java b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/ServiceResponse.java new file mode 100644 index 00000000..20c867c1 --- /dev/null +++ b/joylive-core/joylive-governance-api/src/main/java/com/jd/live/agent/governance/response/ServiceResponse.java @@ -0,0 +1,36 @@ +/* + * 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.governance.response; + +/** + * ServiceResponse + * + * @since 1.0.0 + */ +public interface ServiceResponse extends Response { + + /** + * Defines an interface for outbound service response. + *

+ * This interface represents the response received from another service or component from the current service。 + *

+ * + * @since 1.0.0 + */ + interface OutboundResponse extends ServiceResponse { + + } +} diff --git a/joylive-implement/joylive-bytekit/joylive-bytekit-bytebuddy/src/main/java/com/jd/live/agent/implement/bytekit/bytebuddy/plugin/PluginTransformer.java b/joylive-implement/joylive-bytekit/joylive-bytekit-bytebuddy/src/main/java/com/jd/live/agent/implement/bytekit/bytebuddy/plugin/PluginTransformer.java index 34a8c28a..ee609a58 100644 --- a/joylive-implement/joylive-bytekit/joylive-bytekit-bytebuddy/src/main/java/com/jd/live/agent/implement/bytekit/bytebuddy/plugin/PluginTransformer.java +++ b/joylive-implement/joylive-bytekit/joylive-bytekit-bytebuddy/src/main/java/com/jd/live/agent/implement/bytekit/bytebuddy/plugin/PluginTransformer.java @@ -287,7 +287,7 @@ public DynamicType.Builder transform(@NeverNull DynamicType.Builder builde * Retrieves a list of interceptor instances applicable to a given method, based on a set of interceptor definitions. * Each interceptor definition includes a matcher that determines whether the interceptor should be applied to the method. * - * @param methodDesc The description of the method for which interceptors are being retrieved. + * @param methodDesc The description of the method for which interceptors are being retrieved. * @param interceptors A list of interceptor definitions to evaluate against the method. * @return A list of {@link Interceptor} instances that match the method according to their respective definitions. */ @@ -308,17 +308,17 @@ private List getInterceptors(MethodDescription.InDefinedShape metho * to insert advice around the method execution. The advice is defined in a specified class and is selected * based on a unique key. * - * @param builder The builder used to create or modify the class that contains the method. - * @param methodDesc The description of the method to be enhanced. - * @param classLoader The class loader of the class being modified. + * @param builder The builder used to create or modify the class that contains the method. + * @param methodDesc The description of the method to be enhanced. + * @param classLoader The class loader of the class being modified. * @param interceptors A list of interceptors to apply to the method. - * @param templateCls The class that contains the advice to be applied to the method. - * @param adviceKey A unique key identifying the specific advice to use. + * @param templateCls The class that contains the advice to be applied to the method. + * @param adviceKey A unique key identifying the specific advice to use. * @return A {@link DynamicType.Builder} instance representing the modified class. * @throws InvocationTargetException if an exception occurs while invoking the advice method. - * @throws IllegalAccessException if the advice method or field is not accessible. - * @throws NoSuchMethodException if the advice method does not exist. - * @throws NoSuchFieldException if a required field by the advice does not exist. + * @throws IllegalAccessException if the advice method or field is not accessible. + * @throws NoSuchMethodException if the advice method does not exist. + * @throws NoSuchFieldException if a required field by the advice does not exist. */ protected DynamicType.Builder enhanceMethod(DynamicType.Builder builder, MethodDescription.InDefinedShape methodDesc, diff --git a/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrier.java b/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrier.java index 52cf1c35..e9f64b51 100644 --- a/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrier.java +++ b/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrier.java @@ -17,6 +17,7 @@ import com.jd.live.agent.governance.invoke.retry.Retrier; import com.jd.live.agent.governance.policy.service.retry.RetryPolicy; +import com.jd.live.agent.governance.response.Response; import io.github.resilience4j.retry.Retry; import io.github.resilience4j.retry.RetryConfig; import io.github.resilience4j.retry.RetryRegistry; @@ -40,8 +41,12 @@ public Resilience4jRetrier(RetryPolicy policy) { RetryConfig config = RetryConfig.custom() .maxAttempts(policy.getRetry()) .waitDuration(Duration.ofMillis(policy.getTimeoutInMilliseconds())) - // TODO - // .retryOnResult(response -> response.getStatus() == 500) + .retryOnResult(response -> { + if (policy.getRetryableStatusCodes() != null) { + return policy.getRetryableStatusCodes().contains(((Response) response).getCode()); + } + return false; + }) .retryOnException(throwable -> policy.getExceptionClassNames() != null && policy.getExceptionClassNames().contains(throwable.getClass().getCanonicalName())) .failAfterMaxAttempts(true) @@ -54,7 +59,23 @@ public Resilience4jRetrier(RetryPolicy policy) { * {@inheritDoc} */ @Override - public T execute(Supplier supplier) { + public boolean isRetryable(Response response) { + boolean retryable = false; + if (policy.getRetryableStatusCodes() != null) { + retryable = policy.getRetryableStatusCodes().contains(response.getCode()); + } + if (!retryable && response.getThrowable() != null) { + retryable = response.getThrowable() != null + && policy.getExceptionClassNames().contains(response.getThrowable().getClass().getCanonicalName()); + } + return retryable; + } + + /** + * {@inheritDoc} + */ + @Override + public T execute(Supplier supplier) { return retry.executeSupplier(supplier); } diff --git a/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrierFactory.java b/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrierFactory.java index 9ae2b5e4..62a3fb16 100644 --- a/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrierFactory.java +++ b/joylive-implement/joylive-flowcontrol/joylive-flowcontrol-resilience4j/src/main/java/com/jd/live/agent/implement/flowcontrol/resilience4j/retry/Resilience4jRetrierFactory.java @@ -15,6 +15,7 @@ */ package com.jd.live.agent.implement.flowcontrol.resilience4j.retry; +import com.jd.live.agent.core.extension.annotation.Extension; import com.jd.live.agent.governance.invoke.retry.Retrier; import com.jd.live.agent.governance.invoke.retry.RetrierFactory; import com.jd.live.agent.governance.policy.service.retry.RetryPolicy; @@ -24,6 +25,7 @@ * * @since 1.0.0 */ +@Extension(value = "Resilience4j") public class Resilience4jRetrierFactory implements RetrierFactory { @Override diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/definition/MonitorFilterDefinition.java b/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/definition/MonitorFilterDefinition.java index 76c8c738..47fe4bd6 100644 --- a/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/definition/MonitorFilterDefinition.java +++ b/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/definition/MonitorFilterDefinition.java @@ -29,9 +29,11 @@ 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.governance.invoke.retry.RetrierFactory; import com.jd.live.agent.plugin.router.dubbo.v2_6.interceptor.MonitorFilterInterceptor; import java.util.List; +import java.util.Map; import static com.jd.live.agent.plugin.router.dubbo.v2_6.definition.ExceptionFilterDefinition.ARGUMENT_INVOKE; @@ -55,13 +57,17 @@ public class MonitorFilterDefinition extends PluginDefinitionAdapter { @InjectLoader(ResourcerType.PLUGIN) private List filters; + @Inject + @InjectLoader(ResourcerType.CORE_IMPL) + private Map retrierFactories; + public MonitorFilterDefinition() { this.matcher = () -> MatcherBuilder.named(TYPE_MONITOR_FILTER); this.interceptors = new InterceptorDefinition[]{ new InterceptorDefinitionAdapter( MatcherBuilder.named(METHOD_INVOKE). and(MatcherBuilder.arguments(ARGUMENT_INVOKE)), - () -> new MonitorFilterInterceptor(context, filters) + () -> new MonitorFilterInterceptor(context, filters, retrierFactories) ) }; } diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/interceptor/MonitorFilterInterceptor.java b/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/interceptor/MonitorFilterInterceptor.java index 5ebca53e..bfe26680 100644 --- a/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/interceptor/MonitorFilterInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/interceptor/MonitorFilterInterceptor.java @@ -23,10 +23,16 @@ import com.jd.live.agent.governance.invoke.InvocationContext; import com.jd.live.agent.governance.invoke.filter.OutboundFilter; import com.jd.live.agent.governance.invoke.filter.OutboundFilterChain; +import com.jd.live.agent.governance.invoke.retry.RetrierFactory; +import com.jd.live.agent.governance.response.Response; import com.jd.live.agent.plugin.router.dubbo.v2_6.request.DubboRequest.DubboOutboundRequest; import com.jd.live.agent.plugin.router.dubbo.v2_6.request.invoke.DubboInvocation.DubboOutboundInvocation; +import com.jd.live.agent.plugin.router.dubbo.v2_6.response.DubboResponse; +import java.lang.reflect.Method; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * MonitorFilterInterceptor @@ -34,8 +40,8 @@ public class MonitorFilterInterceptor extends AbstractOutboundInterceptor { - public MonitorFilterInterceptor(InvocationContext context, List filters) { - super(context, filters); + public MonitorFilterInterceptor(InvocationContext context, List filters, Map retrierFactories) { + super(context, filters, retrierFactories); } /** @@ -49,13 +55,28 @@ public MonitorFilterInterceptor(InvocationContext context, List public void onEnter(ExecutableContext ctx) { MethodContext mc = (MethodContext) ctx; Invocation invocation = (Invocation) mc.getArguments()[1]; + DubboOutboundInvocation outboundInvocation = null; try { - process(new DubboOutboundRequest(invocation)); + outboundInvocation = process(new DubboOutboundRequest(invocation)); } catch (RejectException e) { Result result = new RpcResult(new RpcException(RpcException.FORBIDDEN_EXCEPTION, e.getMessage())); mc.setResult(result); mc.setSkip(true); } + final Supplier retrySupplier = createRetrySupplier(mc.getTarget(), mc.getMethod(), mc.getArguments(), mc.getResult()); + Response result = null; + Throwable ex = null; + try { + result = retrySupplier.get(); + } catch (Throwable throwable) { + ex = throwable; + } + Response tryResult = tryRetry(outboundInvocation, new DubboResponse.DubboOutboundResponse((Result) result, ex), retrySupplier); + if (tryResult != null) { + result = tryResult; + } + mc.setResult(result == null ? null : result.getResponse()); + mc.setSkip(true); } @Override @@ -63,6 +84,23 @@ protected void process(DubboOutboundInvocation invocation) { new OutboundFilterChain.Chain(outboundFilters).filter(invocation); } + @Override + protected Supplier createRetrySupplier(Object target, Method method, Object[] allArguments, Object result) { + return () -> { + Response response = null; + method.setAccessible(true); + try { + Object r = method.invoke(target, allArguments); + response = new DubboResponse.DubboOutboundResponse((Result) r, null); + } catch (IllegalAccessException ignored) { + // ignored + } catch (Throwable throwable) { + response = new DubboResponse.DubboOutboundResponse((Result) result, throwable); + } + return response; + }; + } + @Override protected DubboOutboundInvocation createOutlet(DubboOutboundRequest request) { return new DubboOutboundInvocation(request, context); diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/response/DubboResponse.java b/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/response/DubboResponse.java new file mode 100644 index 00000000..d669f65f --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-dubbo2.6/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_6/response/DubboResponse.java @@ -0,0 +1,39 @@ +/* + * 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.dubbo.v2_6.response; + +import com.alibaba.dubbo.rpc.Result; +import com.jd.live.agent.governance.response.AbstractRpcResponse.AbstractRpcOutboundResponse; + +/** + * DubboResponse + * + * @since 1.0.0 + */ +public interface DubboResponse { + + class DubboOutboundResponse extends AbstractRpcOutboundResponse implements DubboResponse { + + public DubboOutboundResponse(Result response, Throwable throwable) { + super(response, throwable); + } + + @Override + public String getCode() { + return null; + } + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/definition/MonitorFilterDefinition.java b/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/definition/MonitorFilterDefinition.java index 505cbf43..35234d20 100644 --- a/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/definition/MonitorFilterDefinition.java +++ b/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/definition/MonitorFilterDefinition.java @@ -29,9 +29,11 @@ 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.governance.invoke.retry.RetrierFactory; import com.jd.live.agent.plugin.router.dubbo.v2_7.interceptor.MonitorFilterInterceptor; import java.util.List; +import java.util.Map; import static com.jd.live.agent.plugin.router.dubbo.v2_7.definition.ClassLoaderFilterDefinition.ARGUMENT_INVOKE; @@ -56,6 +58,9 @@ public class MonitorFilterDefinition extends PluginDefinitionAdapter { @InjectLoader(ResourcerType.PLUGIN) private List filters; + @Inject + @InjectLoader(ResourcerType.CORE_IMPL) + private Map retrierFactories; public MonitorFilterDefinition() { this.matcher = () -> MatcherBuilder.named(TYPE_MONITOR_FILTER); @@ -63,7 +68,7 @@ public MonitorFilterDefinition() { new InterceptorDefinitionAdapter( MatcherBuilder.named(METHOD_INVOKE). and(MatcherBuilder.arguments(ARGUMENT_INVOKE)), - () -> new MonitorFilterInterceptor(context, filters) + () -> new MonitorFilterInterceptor(context, filters, retrierFactories) ) }; } diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/interceptor/MonitorFilterInterceptor.java b/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/interceptor/MonitorFilterInterceptor.java index ac345a07..c13a3edb 100644 --- a/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/interceptor/MonitorFilterInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/interceptor/MonitorFilterInterceptor.java @@ -22,12 +22,18 @@ import com.jd.live.agent.governance.invoke.InvocationContext; import com.jd.live.agent.governance.invoke.filter.OutboundFilter; import com.jd.live.agent.governance.invoke.filter.OutboundFilterChain; +import com.jd.live.agent.governance.invoke.retry.RetrierFactory; +import com.jd.live.agent.governance.response.Response; import com.jd.live.agent.plugin.router.dubbo.v2_7.request.DubboRequest.DubboOutboundRequest; import com.jd.live.agent.plugin.router.dubbo.v2_7.request.invoke.DubboInvocation.DubboOutboundInvocation; +import com.jd.live.agent.plugin.router.dubbo.v2_7.response.DubboResponse; import org.apache.dubbo.monitor.support.MonitorFilter; import org.apache.dubbo.rpc.*; +import java.lang.reflect.Method; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * MonitorFilterInterceptor @@ -35,8 +41,8 @@ public class MonitorFilterInterceptor extends AbstractOutboundInterceptor { - public MonitorFilterInterceptor(InvocationContext context, List filters) { - super(context, filters); + public MonitorFilterInterceptor(InvocationContext context, List filters, Map retrierFactories) { + super(context, filters, retrierFactories); } /** @@ -51,13 +57,28 @@ public void onEnter(ExecutableContext ctx) { MethodContext mc = (MethodContext) ctx; Object[] arguments = mc.getArguments(); Invocation invocation = (Invocation) arguments[1]; + DubboOutboundInvocation outboundInvocation = null; try { - process(new DubboOutboundRequest(invocation)); + outboundInvocation = process(new DubboOutboundRequest(invocation)); } catch (RejectException e) { Result result = new AppResponse(new RpcException(RpcException.FORBIDDEN_EXCEPTION, e.getMessage())); mc.setResult(result); mc.setSkip(true); } + final Supplier retrySupplier = createRetrySupplier(mc.getTarget(), mc.getMethod(), mc.getArguments(), mc.getResult()); + Response result = null; + Throwable ex = null; + try { + result = retrySupplier.get(); + } catch (Throwable throwable) { + ex = throwable; + } + Response tryResult = tryRetry(outboundInvocation, new DubboResponse.DubboOutboundResponse((Result) result, ex), retrySupplier); + if (tryResult != null) { + result = tryResult; + } + mc.setResult(result == null ? null : result.getResponse()); + mc.setSkip(true); } @Override @@ -69,4 +90,21 @@ protected void process(DubboOutboundInvocation invocation) { protected DubboOutboundInvocation createOutlet(DubboOutboundRequest request) { return new DubboOutboundInvocation(request, context); } + + @Override + protected Supplier createRetrySupplier(Object target, Method method, Object[] allArguments, Object result) { + return () -> { + Response response = null; + method.setAccessible(true); + try { + Object r = method.invoke(target, allArguments); + response = new DubboResponse.DubboOutboundResponse((Result) r, null); + } catch (IllegalAccessException ignored) { + // ignored + } catch (Throwable throwable) { + response = new DubboResponse.DubboOutboundResponse((Result) result, throwable); + } + return response; + }; + } } diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/response/DubboResponse.java b/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/response/DubboResponse.java new file mode 100644 index 00000000..8231034e --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-dubbo2.7/src/main/java/com/jd/live/agent/plugin/router/dubbo/v2_7/response/DubboResponse.java @@ -0,0 +1,39 @@ +/* + * 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.dubbo.v2_7.response; + +import com.jd.live.agent.governance.response.AbstractRpcResponse.AbstractRpcOutboundResponse; +import org.apache.dubbo.rpc.Result; + +/** + * DubboResponse + * + * @since 1.0.0 + */ +public interface DubboResponse { + + class DubboOutboundResponse extends AbstractRpcOutboundResponse implements DubboResponse { + + public DubboOutboundResponse(Result response, Throwable throwable) { + super(response, throwable); + } + + @Override + public String getCode() { + return null; + } + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/definition/ConsumerClassLoaderFilterDefinition.java b/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/definition/ConsumerClassLoaderFilterDefinition.java index f61e56e2..ea699238 100644 --- a/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/definition/ConsumerClassLoaderFilterDefinition.java +++ b/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/definition/ConsumerClassLoaderFilterDefinition.java @@ -29,9 +29,11 @@ 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.governance.invoke.retry.RetrierFactory; import com.jd.live.agent.plugin.router.dubbo.v3.interceptor.ConsumerClassLoaderFilterInterceptor; import java.util.List; +import java.util.Map; import static com.jd.live.agent.plugin.router.dubbo.v3.definition.ClassLoaderFilterDefinition.ARGUMENT_INVOKE; @@ -54,13 +56,17 @@ public class ConsumerClassLoaderFilterDefinition extends PluginDefinitionAdapter @InjectLoader(ResourcerType.PLUGIN) private List filters; + @Inject + @InjectLoader(ResourcerType.CORE_IMPL) + private Map retrierFactories; + public ConsumerClassLoaderFilterDefinition() { this.matcher = () -> MatcherBuilder.named(TYPE_CONSUMER_CLASSLOADER_FILTER); this.interceptors = new InterceptorDefinition[]{ new InterceptorDefinitionAdapter( MatcherBuilder.named(METHOD_INVOKE). and(MatcherBuilder.arguments(ARGUMENT_INVOKE)), - () -> new ConsumerClassLoaderFilterInterceptor(context, filters) + () -> new ConsumerClassLoaderFilterInterceptor(context, filters, retrierFactories) ) }; } diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/interceptor/ConsumerClassLoaderFilterInterceptor.java b/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/interceptor/ConsumerClassLoaderFilterInterceptor.java index 161f145b..7660edc4 100644 --- a/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/interceptor/ConsumerClassLoaderFilterInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/interceptor/ConsumerClassLoaderFilterInterceptor.java @@ -22,12 +22,18 @@ import com.jd.live.agent.governance.invoke.InvocationContext; import com.jd.live.agent.governance.invoke.filter.OutboundFilter; import com.jd.live.agent.governance.invoke.filter.OutboundFilterChain; +import com.jd.live.agent.governance.invoke.retry.RetrierFactory; +import com.jd.live.agent.governance.response.Response; import com.jd.live.agent.plugin.router.dubbo.v3.request.DubboRequest.DubboOutboundRequest; import com.jd.live.agent.plugin.router.dubbo.v3.request.invoke.DubboInvocation.DubboOutboundInvocation; +import com.jd.live.agent.plugin.router.dubbo.v3.response.DubboResponse; import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.cluster.filter.support.ConsumerClassLoaderFilter; +import java.lang.reflect.Method; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * ConsumerClassLoaderFilterInterceptor @@ -35,8 +41,8 @@ public class ConsumerClassLoaderFilterInterceptor extends AbstractOutboundInterceptor { - public ConsumerClassLoaderFilterInterceptor(InvocationContext context, List filters) { - super(context, filters); + public ConsumerClassLoaderFilterInterceptor(InvocationContext context, List filters, Map retrierFactories) { + super(context, filters, retrierFactories); } /** @@ -51,13 +57,28 @@ public void onEnter(ExecutableContext ctx) { MethodContext mc = (MethodContext) ctx; Object[] arguments = mc.getArguments(); Invocation invocation = (Invocation) arguments[1]; + DubboOutboundInvocation outboundInvocation = null; try { - process(new DubboOutboundRequest(invocation)); + outboundInvocation = process(new DubboOutboundRequest(invocation)); } catch (RejectException e) { Result result = new AppResponse(new RpcException(RpcException.FORBIDDEN_EXCEPTION, e.getMessage())); mc.setResult(result); mc.setSkip(true); } + final Supplier retrySupplier = createRetrySupplier(mc.getTarget(), mc.getMethod(), mc.getArguments(), mc.getResult()); + Response result = null; + Throwable ex = null; + try { + result = retrySupplier.get(); + } catch (Throwable throwable) { + ex = throwable; + } + Response tryResult = tryRetry(outboundInvocation, new DubboResponse.DubboOutboundResponse((Result) result, ex), retrySupplier); + if (tryResult != null) { + result = tryResult; + } + mc.setResult(result == null ? null : result.getResponse()); + mc.setSkip(true); } @Override @@ -69,4 +90,21 @@ protected void process(DubboOutboundInvocation invocation) { protected DubboOutboundInvocation createOutlet(DubboOutboundRequest request) { return new DubboOutboundInvocation(request, context); } + + @Override + protected Supplier createRetrySupplier(Object target, Method method, Object[] allArguments, Object result) { + return () -> { + Response response = null; + method.setAccessible(true); + try { + Object r = method.invoke(target, allArguments); + response = new DubboResponse.DubboOutboundResponse((Result) r, null); + } catch (IllegalAccessException ignored) { + // ignored + } catch (Throwable throwable) { + response = new DubboResponse.DubboOutboundResponse((Result) result, throwable); + } + return response; + }; + } } diff --git a/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/response/DubboResponse.java b/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/response/DubboResponse.java new file mode 100644 index 00000000..3cc90b4c --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-dubbo3/src/main/java/com/jd/live/agent/plugin/router/dubbo/v3/response/DubboResponse.java @@ -0,0 +1,39 @@ +/* + * 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.dubbo.v3.response; + +import com.jd.live.agent.governance.response.AbstractRpcResponse.AbstractRpcOutboundResponse; +import org.apache.dubbo.rpc.Result; + +/** + * DubboResponse + * + * @since 1.0.0 + */ +public interface DubboResponse { + + class DubboOutboundResponse extends AbstractRpcOutboundResponse implements DubboResponse { + + public DubboOutboundResponse(Result response, Throwable throwable) { + super(response, throwable); + } + + @Override + public String getCode() { + return null; + } + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/definition/ConsumerInvokerDefinition.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/definition/ConsumerInvokerDefinition.java index b4c6b9a0..0d0d69a4 100644 --- a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/definition/ConsumerInvokerDefinition.java +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/definition/ConsumerInvokerDefinition.java @@ -29,9 +29,11 @@ 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.governance.invoke.retry.RetrierFactory; import com.jd.live.agent.plugin.router.sofarpc.interceptor.ConsumerInvokerInterceptor; import java.util.List; +import java.util.Map; @Injectable @Extension(value = "ConsumerInvokerDefinition_v2.7") @@ -57,6 +59,9 @@ public class ConsumerInvokerDefinition extends PluginDefinitionAdapter { @InjectLoader(ResourcerType.PLUGIN) private List filters; + @Inject + @InjectLoader(ResourcerType.CORE_IMPL) + private Map retrierFactories; public ConsumerInvokerDefinition() { this.matcher = () -> MatcherBuilder.named(TYPE_CONSUMER_INVOKER); @@ -64,7 +69,7 @@ public ConsumerInvokerDefinition() { new InterceptorDefinitionAdapter( MatcherBuilder.named(METHOD_INVOKE). and(MatcherBuilder.arguments(ARGUMENT_INVOKE)), - () -> new ConsumerInvokerInterceptor(context, filters) + () -> new ConsumerInvokerInterceptor(context, filters, retrierFactories) ) }; } diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ConsumerInvokerInterceptor.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ConsumerInvokerInterceptor.java index b13ebd95..60c35b73 100644 --- a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ConsumerInvokerInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ConsumerInvokerInterceptor.java @@ -25,19 +25,25 @@ import com.jd.live.agent.governance.invoke.InvocationContext; import com.jd.live.agent.governance.invoke.filter.OutboundFilter; import com.jd.live.agent.governance.invoke.filter.OutboundFilterChain; +import com.jd.live.agent.governance.invoke.retry.RetrierFactory; +import com.jd.live.agent.governance.response.Response; import com.jd.live.agent.plugin.router.sofarpc.request.SofaRpcRequest.SofaRpcOutboundRequest; -import com.jd.live.agent.plugin.router.sofarpc.request.invoke.DubboInvocation; +import com.jd.live.agent.plugin.router.sofarpc.request.invoke.SofaRpcInvocation; +import com.jd.live.agent.plugin.router.sofarpc.response.SofaRpcResponse; +import java.lang.reflect.Method; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * ConsumerInvokerInterceptor */ public class ConsumerInvokerInterceptor extends - AbstractOutboundInterceptor { + AbstractOutboundInterceptor { - public ConsumerInvokerInterceptor(InvocationContext context, List filters) { - super(context, filters); + public ConsumerInvokerInterceptor(InvocationContext context, List filters, Map retrierFactories) { + super(context, filters, retrierFactories); } /** @@ -51,23 +57,55 @@ public ConsumerInvokerInterceptor(InvocationContext context, List retrySupplier = createRetrySupplier(mc.getTarget(), mc.getMethod(), mc.getArguments(), mc.getResult()); + Response result = null; + Throwable ex = null; + try { + result = retrySupplier.get(); + } catch (Throwable throwable) { + ex = throwable; + } + Response tryResult = tryRetry(outboundInvocation, result, retrySupplier); + if (tryResult != null) { + result = tryResult; + } + mc.setResult(result == null ? null : result.getResponse()); + mc.setSkip(true); } @Override - protected void process(DubboInvocation.DubboOutboundInvocation invocation) { + protected void process(SofaRpcInvocation.SofaRpcOutboundInvocation invocation) { new OutboundFilterChain.Chain(outboundFilters).filter(invocation); } @Override - protected DubboInvocation.DubboOutboundInvocation createOutlet(SofaRpcOutboundRequest request) { - return new DubboInvocation.DubboOutboundInvocation(request, context); + protected SofaRpcInvocation.SofaRpcOutboundInvocation createOutlet(SofaRpcOutboundRequest request) { + return new SofaRpcInvocation.SofaRpcOutboundInvocation(request, context); + } + + @Override + protected Supplier createRetrySupplier(Object target, Method method, Object[] allArguments, Object result) { + return () -> { + Response response = null; + method.setAccessible(true); + try { + Object r = method.invoke(target, allArguments); + response = new SofaRpcResponse.SofaRpcOutboundResponse((SofaResponse) r, null); + } catch (IllegalAccessException ignored) { + // ignored + } catch (Throwable throwable) { + response = new SofaRpcResponse.SofaRpcOutboundResponse((SofaResponse) result, throwable); + } + return response; + }; } } diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ProviderInvokerInterceptor.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ProviderInvokerInterceptor.java index 4916dd98..396987fa 100644 --- a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ProviderInvokerInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/ProviderInvokerInterceptor.java @@ -26,7 +26,7 @@ import com.jd.live.agent.governance.invoke.filter.InboundFilter; import com.jd.live.agent.governance.invoke.filter.InboundFilterChain; import com.jd.live.agent.plugin.router.sofarpc.request.SofaRpcRequest.SofaRpcInboundRequest; -import com.jd.live.agent.plugin.router.sofarpc.request.invoke.DubboInvocation; +import com.jd.live.agent.plugin.router.sofarpc.request.invoke.SofaRpcInvocation; import java.util.List; @@ -34,7 +34,7 @@ * ProviderInvokerInterceptor */ public class ProviderInvokerInterceptor extends - AbstractInboundInterceptor { + AbstractInboundInterceptor { public ProviderInvokerInterceptor(InvocationContext context, List filters) { super(context, filters); @@ -62,13 +62,13 @@ public void onEnter(ExecutableContext ctx) { } @Override - protected void process(DubboInvocation.DubboInboundInvocation invocation) { + protected void process(SofaRpcInvocation.SofaRpcInboundInvocation invocation) { new InboundFilterChain.Chain(inboundFilters).filter(invocation); } @Override - protected DubboInvocation.DubboInboundInvocation createInlet(SofaRpcInboundRequest request) { - return new DubboInvocation.DubboInboundInvocation(request, context); + protected SofaRpcInvocation.SofaRpcInboundInvocation createInlet(SofaRpcInboundRequest request) { + return new SofaRpcInvocation.SofaRpcInboundInvocation(request, context); } } diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/RouterChainInterceptor.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/RouterChainInterceptor.java index c9eb621d..56b32d22 100644 --- a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/RouterChainInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/interceptor/RouterChainInterceptor.java @@ -28,7 +28,7 @@ import com.jd.live.agent.governance.invoke.filter.RouteFilterChain; import com.jd.live.agent.plugin.router.sofarpc.instance.SofaRpcEndpoint; import com.jd.live.agent.plugin.router.sofarpc.request.SofaRpcRequest.SofaRpcOutboundRequest; -import com.jd.live.agent.plugin.router.sofarpc.request.invoke.DubboInvocation.DubboOutboundInvocation; +import com.jd.live.agent.plugin.router.sofarpc.request.invoke.SofaRpcInvocation.SofaRpcOutboundInvocation; import java.util.List; import java.util.stream.Collectors; @@ -38,7 +38,7 @@ /** * RouterChainInterceptor */ -public class RouterChainInterceptor extends AbstractRouteInterceptor { +public class RouterChainInterceptor extends AbstractRouteInterceptor { public RouterChainInterceptor(InvocationContext context, List filters) { super(context, filters); @@ -62,7 +62,7 @@ public void onSuccess(ExecutableContext ctx) { SofaRequest request = (SofaRequest) arguments[0]; SofaRpcOutboundRequest outboundRequest = new SofaRpcOutboundRequest(request); try { - DubboOutboundInvocation outboundInvocation = routing(outboundRequest, instances); + SofaRpcOutboundInvocation outboundInvocation = routing(outboundRequest, instances); List endpoints = (List) outboundInvocation.getEndpoints(); mc.setResult(endpoints.stream().map(SofaRpcEndpoint::getProvider).collect(Collectors.toList())); } catch (RejectException e) { @@ -71,12 +71,12 @@ public void onSuccess(ExecutableContext ctx) { } @Override - protected void routing(DubboOutboundInvocation invocation) { + protected void routing(SofaRpcOutboundInvocation invocation) { new RouteFilterChain.Chain(routeFilters).filter(invocation); } @Override - protected DubboOutboundInvocation createOutlet(SofaRpcOutboundRequest request) { - return new DubboOutboundInvocation(request, context); + protected SofaRpcOutboundInvocation createOutlet(SofaRpcOutboundRequest request) { + return new SofaRpcOutboundInvocation(request, context); } } diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/SofaRpcRequest.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/SofaRpcRequest.java index 800d7525..ad158078 100644 --- a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/SofaRpcRequest.java +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/SofaRpcRequest.java @@ -19,6 +19,11 @@ import com.jd.live.agent.governance.request.AbstractRpcRequest.AbstractRpcInboundRequest; import com.jd.live.agent.governance.request.AbstractRpcRequest.AbstractRpcOutboundRequest; +/** + * SofaRpcRequest + * + * @since 1.0.0 + */ public interface SofaRpcRequest { class SofaRpcInboundRequest extends AbstractRpcInboundRequest implements SofaRpcRequest { diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/invoke/DubboInvocation.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/invoke/SofaRpcInvocation.java similarity index 73% rename from joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/invoke/DubboInvocation.java rename to joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/invoke/SofaRpcInvocation.java index bb0a9284..d7062c4c 100644 --- a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/invoke/DubboInvocation.java +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/request/invoke/SofaRpcInvocation.java @@ -21,18 +21,18 @@ import com.jd.live.agent.plugin.router.sofarpc.request.SofaRpcRequest.SofaRpcInboundRequest; import com.jd.live.agent.plugin.router.sofarpc.request.SofaRpcRequest.SofaRpcOutboundRequest; -public interface DubboInvocation { +public interface SofaRpcInvocation { - class DubboInboundInvocation extends RpcInboundInvocation implements DubboInvocation { + class SofaRpcInboundInvocation extends RpcInboundInvocation implements SofaRpcInvocation { - public DubboInboundInvocation(SofaRpcInboundRequest request, InvocationContext context) { + public SofaRpcInboundInvocation(SofaRpcInboundRequest request, InvocationContext context) { super(request, context); } } - class DubboOutboundInvocation extends RpcOutboundInvocation implements DubboInvocation { + class SofaRpcOutboundInvocation extends RpcOutboundInvocation implements SofaRpcInvocation { - public DubboOutboundInvocation(SofaRpcOutboundRequest request, InvocationContext context) { + public SofaRpcOutboundInvocation(SofaRpcOutboundRequest request, InvocationContext context) { super(request, context); } } diff --git a/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/response/SofaRpcResponse.java b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/response/SofaRpcResponse.java new file mode 100644 index 00000000..95d437b8 --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-sofarpc/src/main/java/com/jd/live/agent/plugin/router/sofarpc/response/SofaRpcResponse.java @@ -0,0 +1,39 @@ +/* + * 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.sofarpc.response; + +import com.alipay.sofa.rpc.core.response.SofaResponse; +import com.jd.live.agent.governance.response.AbstractRpcResponse; + +/** + * SofaRpcResponse + * + * @since 1.0.0 + */ +public interface SofaRpcResponse { + + class SofaRpcOutboundResponse extends AbstractRpcResponse.AbstractRpcOutboundResponse implements SofaRpcResponse { + + public SofaRpcOutboundResponse(SofaResponse response, Throwable throwable) { + super(response, throwable); + } + + @Override + public String getCode() { + return null; + } + } +} diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/InterceptingClientHttpRequestDefinition.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/InterceptingClientHttpRequestDefinition.java index 69e37ad5..cfb48bae 100644 --- a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/InterceptingClientHttpRequestDefinition.java +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/definition/InterceptingClientHttpRequestDefinition.java @@ -29,12 +29,14 @@ 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.governance.invoke.retry.RetrierFactory; import com.jd.live.agent.plugin.router.springcloud.v3.interceptor.InterceptingClientHttpRequestInterceptor; import java.util.List; +import java.util.Map; /** - * InterceptingClientHttpRequestPluginDefinition + * InterceptingClientHttpRequestDefinition * * @author Zhiguo.Chen * @since 1.0.0 @@ -63,13 +65,17 @@ public class InterceptingClientHttpRequestDefinition extends PluginDefinitionAda @InjectLoader(ResourcerType.PLUGIN) private List filters; + @Inject + @InjectLoader(ResourcerType.CORE_IMPL) + private Map retrierFactories; + public InterceptingClientHttpRequestDefinition() { this.matcher = () -> MatcherBuilder.named(TYPE_INTERCEPTING_CLIENT_HTTP_REQUEST); this.interceptors = new InterceptorDefinition[]{ new InterceptorDefinitionAdapter( MatcherBuilder.named(METHOD_EXECUTE_INTERNAL). and(MatcherBuilder.arguments(ARGUMENT_EXECUTE_INTERNAL)), - () -> new InterceptingClientHttpRequestInterceptor(context, filters) + () -> new InterceptingClientHttpRequestInterceptor(context, filters, retrierFactories) ) }; } diff --git a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/InterceptingClientHttpRequestInterceptor.java b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/InterceptingClientHttpRequestInterceptor.java index 13540e5f..7efef6dc 100644 --- a/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/InterceptingClientHttpRequestInterceptor.java +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/interceptor/InterceptingClientHttpRequestInterceptor.java @@ -22,14 +22,22 @@ 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; import com.jd.live.agent.governance.invoke.filter.OutboundFilter; +import com.jd.live.agent.governance.invoke.retry.RetrierFactory; +import com.jd.live.agent.governance.response.Response; import com.jd.live.agent.plugin.router.springcloud.v3.request.ReactiveOutboundRequest; +import com.jd.live.agent.plugin.router.springcloud.v3.response.ClientHttpOutboundResponse; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.HttpClientErrorException; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; +import java.util.function.Supplier; /** * InterceptingClientHttpRequestInterceptor @@ -39,8 +47,8 @@ */ public class InterceptingClientHttpRequestInterceptor extends AbstractHttpOutboundInterceptor { - public InterceptingClientHttpRequestInterceptor(InvocationContext context, List filters) { - super(context, filters); + public InterceptingClientHttpRequestInterceptor(InvocationContext context, List filters, Map retrierFactories) { + super(context, filters, retrierFactories); } /** @@ -54,8 +62,9 @@ public InterceptingClientHttpRequestInterceptor(InvocationContext context, List< public void onEnter(ExecutableContext ctx) { MethodContext mc = (MethodContext) ctx; HttpRequest request = (HttpRequest) mc.getTarget(); + OutboundInvocation.HttpOutboundInvocation outboundInvocation = null; try { - process(new ReactiveOutboundRequest(request, RequestContext.getAttribute(Carrier.ATTRIBUTE_SERVICE_ID))); + outboundInvocation = process(new ReactiveOutboundRequest(request, RequestContext.getAttribute(Carrier.ATTRIBUTE_SERVICE_ID))); } catch (RejectException e) { mc.setThrowable(HttpClientErrorException.create( e.getMessage(), @@ -65,10 +74,47 @@ public void onEnter(ExecutableContext ctx) { null, StandardCharsets.UTF_8)); } + final Supplier retrySupplier = createRetrySupplier(mc.getTarget(), mc.getMethod(), mc.getArguments(), mc.getResult()); + Response result = null; + Throwable ex = null; + try { + result = retrySupplier.get(); + } catch (Throwable throwable) { + ex = throwable; + } + Response tryResult = tryRetry(outboundInvocation, result, retrySupplier); + if (tryResult != null) { + result = tryResult; + } + mc.setResult(result == null ? null : result.getResponse()); + mc.setSkip(true); } + /** + * {@inheritDoc} + */ @Override public void onExit(ExecutableContext ctx) { RequestContext.remove(); } + + /** + * {@inheritDoc} + */ + @Override + protected final Supplier createRetrySupplier(Object target, Method method, Object[] allArguments, Object result) { + return () -> { + Response response = null; + method.setAccessible(true); + try { + Object r = method.invoke(target, allArguments); + response = new ClientHttpOutboundResponse((ClientHttpResponse) r, null); + } catch (IllegalAccessException ignored) { + // ignored + } catch (Throwable throwable) { + response = new ClientHttpOutboundResponse((ClientHttpResponse) result, throwable); + } + return response; + }; + } } 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 new file mode 100644 index 00000000..4972c212 --- /dev/null +++ b/joylive-plugin/joylive-router/joylive-router-springcloud3/src/main/java/com/jd/live/agent/plugin/router/springcloud/v3/response/ClientHttpOutboundResponse.java @@ -0,0 +1,29 @@ +package com.jd.live.agent.plugin.router.springcloud.v3.response; + +import com.jd.live.agent.governance.response.AbstractHttpResponse.AbstractHttpOutboundResponse; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; +import java.io.UncheckedIOException; + +/** + * ClientHttpOutboundResponse + * + * @since 1.0.0 + */ +public class ClientHttpOutboundResponse extends AbstractHttpOutboundResponse { + + public ClientHttpOutboundResponse(ClientHttpResponse response, Throwable throwable) { + super(response, throwable); + } + + @Override + public String getCode() { + try { + return response == null ? "500" : String.valueOf(response.getStatusCode().value()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +}