Skip to content

Commit

Permalink
Add FeignRequestInterceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
chenzhiguo committed May 8, 2024
1 parent 41b7668 commit 8e6aff5
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ protected Object invokeWithRetry(O invocation, MethodContext ctx) {
Response response = retrySupplier.get();
return response == null ? null : response.getResponse();
}

}

/**
Expand Down
9 changes: 9 additions & 0 deletions joylive-package/src/main/assembly/config/microservice.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions joylive-plugin/joylive-router/joylive-router-springcloud3/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<spring.boot.version>2.7.18</spring.boot.version>
<spring.version>5.3.31</spring.version>
<servlet.api.version>4.0.4</servlet.api.version>
<openfeign.version>11.10</openfeign.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -68,5 +69,17 @@
<version>${servlet.api.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<version>${spring.cloud.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${openfeign.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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<OutboundFilter> 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)
)
};
}
}
Original file line number Diff line number Diff line change
@@ -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<FeignOutboundRequest> {

public FeignRequestInterceptor(InvocationContext context, List<OutboundFilter> filters) {
super(context, filters);
}

/**
* Enhanced logic before method execution<br>
* <p>
*
* @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<FeignOutboundRequest> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Request> {

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 <K, V> Map<K, List<V>> convertToListMap(Map<K, ? extends Collection<V>> map) {
return map.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> new ArrayList<>(e.getValue())
));
}

protected Map<String, List<Cookie>> parseCookie(Collection<String> headers) {
Map<String, List<Cookie>> result = new HashMap<>();
if (headers == null || headers.isEmpty()) {
return result;
}
for (String header : headers) {
if (header != null && !header.isEmpty()) {
String[] values = header.split(";");
List<HttpCookie> 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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Response> {

public FeignOutboundResponse(Response response, Throwable throwable) {
super(response, throwable);
}

@Override
public String getCode() {
return response == null ? "500" : String.valueOf(response.status());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
com.jd.live.agent.plugin.router.springcloud.v3.definition.ReactorLoadBalancerDefinition
com.jd.live.agent.plugin.router.springcloud.v3.definition.FeignRequestDefinition

0 comments on commit 8e6aff5

Please sign in to comment.