Skip to content

Commit

Permalink
Merge pull request #865 from justforstudy-A/urlconn_springboot_registry
Browse files Browse the repository at this point in the history
【feature】add support for HttpUrlConnection;add new Retry policy for same instance
  • Loading branch information
robotLJW authored Oct 21, 2022
2 parents 76ebdd3 + 6257dc4 commit a0f23a3
Show file tree
Hide file tree
Showing 20 changed files with 798 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ public void setUp() {
.thenReturn(new YamlConverterImpl());

pluginConfigManagerMockedStatic = Mockito.mockStatic(PluginConfigManager.class);
final DynamicConfiguration configuration = new DynamicConfiguration();
configuration.setEnableOriginConfigCenter(false);
pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(DynamicConfiguration.class))
.thenReturn(new DynamicConfiguration());
.thenReturn(configuration);

ConfigHolder.INSTANCE.getConfigSources()
.removeIf(configSource -> configSource.getClass() == OriginConfigDisableSource.class);
Expand All @@ -72,7 +74,7 @@ public void test() {
try {
final ZookeeperLocatorInterceptor zookeeperLocatorInterceptor = new ZookeeperLocatorInterceptor();
final ExecuteContext context = zookeeperLocatorInterceptor.doBefore(buildContext());
Assert.assertFalse(context.isSkip());
Assert.assertTrue(context.isSkip());
ConfigHolder.INSTANCE.getConfigSources().add(new OriginConfigDisableSource("test"));
final ExecuteContext closeContext = zookeeperLocatorInterceptor.doBefore(buildContext());
Assert.assertTrue(closeContext.isSkip());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,21 @@ public class LbConfig implements PluginConfig {
*/
private int maxRetry = LbConstants.DEFAULT_MAX_RETRY;

/**
* 最大相同实例的重试次数
*/
private int maxSameRetry = LbConstants.DEFAULT_MAX_SAME_RETRY;

/**
* 重试等待时间, 默认一秒
*/
private long retryWaitMs = LbConstants.DEFAULT_RETRY_WAIT_MS;

/**
* 重试策略, 当前支持两种"轮询(RoundRobin)"与"先重试上一次失败的实例(SameInstance){@link LbConfig#maxSameRetry}"
*/
private String retryPolicy = "RoundRobin";

/**
* 重试场景, 针对{@link java.net.SocketTimeoutException}: connect timed out是否需要重试, 默认开启
*/
Expand Down Expand Up @@ -162,6 +172,22 @@ public class LbConfig implements PluginConfig {
*/
private long instanceStatTimeWindowMs = LbConstants.DEFAULT_INSTANCE_STATE_TIME_WINDOW_MS;

public String getRetryPolicy() {
return retryPolicy;
}

public void setRetryPolicy(String retryPolicy) {
this.retryPolicy = retryPolicy;
}

public int getMaxSameRetry() {
return maxSameRetry;
}

public void setMaxSameRetry(int maxSameRetry) {
this.maxSameRetry = maxSameRetry;
}

public boolean isEnableSocketReadTimeoutRetry() {
return enableSocketReadTimeoutRetry;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public class LbConstants {
*/
public static final int DEFAULT_MAX_RETRY = 3;

/**
* 最大相同实例的重试次数
*/
public static final int DEFAULT_MAX_SAME_RETRY = 3;

/**
* 重试等待时间, 默认一秒
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.huawei.discovery.declarers.httpconnection;

import com.huawei.discovery.interceptors.httpconnection.HttpUrlConnectionConnectInterceptor;
import com.huawei.discovery.interceptors.httpconnection.HttpUrlConnectionDisconnectCodeInterceptor;

import com.huaweicloud.sermant.core.plugin.agent.declarer.AbstractPluginDeclarer;
import com.huaweicloud.sermant.core.plugin.agent.declarer.InterceptDeclarer;
import com.huaweicloud.sermant.core.plugin.agent.matcher.ClassMatcher;
import com.huaweicloud.sermant.core.plugin.agent.matcher.MethodMatcher;

/**
* 针对HttpUrlConnection连接拦截
*
* @author zhouss
* @since 2022-10-20
*/
public class HttpUrlConnectionConnectDeclarer extends AbstractPluginDeclarer {
private static final String INTERCEPT_CLASS = HttpUrlConnectionConnectInterceptor.class.getCanonicalName();

private static final String INTERCEPT_DISCONNECT_CLASS =
HttpUrlConnectionDisconnectCodeInterceptor.class.getCanonicalName();

private static final String METHOD_NAME = "connect";

private static final String METHOD_DISCONNECT_NAME = "disconnect";

@Override
public ClassMatcher getClassMatcher() {
return ClassMatcher.isExtendedFrom("java.net.HttpURLConnection");
}

@Override
public InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader) {
return new InterceptDeclarer[]{
InterceptDeclarer.build(MethodMatcher.nameEquals(METHOD_NAME), INTERCEPT_CLASS),
InterceptDeclarer.build(MethodMatcher.nameEquals(METHOD_DISCONNECT_NAME), INTERCEPT_DISCONNECT_CLASS)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.huawei.discovery.declarers.httpconnection;

import com.huawei.discovery.interceptors.httpconnection.HttpUrlConnectionResponseCodeInterceptor;

import com.huaweicloud.sermant.core.plugin.agent.declarer.AbstractPluginDeclarer;
import com.huaweicloud.sermant.core.plugin.agent.declarer.InterceptDeclarer;
import com.huaweicloud.sermant.core.plugin.agent.matcher.ClassMatcher;
import com.huaweicloud.sermant.core.plugin.agent.matcher.MethodMatcher;

/**
* 针对HttpUrlConnection连接拦截
*
* @author zhouss
* @since 2022-10-20
*/
public class HttpUrlConnectionResponseCodeDeclarer extends AbstractPluginDeclarer {
private static final String INTERCEPT_CLASS = HttpUrlConnectionResponseCodeInterceptor.class.getCanonicalName();

private static final String METHOD_NAME = "getResponseCode";

@Override
public ClassMatcher getClassMatcher() {
return ClassMatcher.nameEquals("java.net.HttpURLConnection");
}

@Override
public InterceptDeclarer[] getInterceptDeclarers(ClassLoader classLoader) {
return new InterceptDeclarer[] {
InterceptDeclarer.build(MethodMatcher.nameEquals(METHOD_NAME), INTERCEPT_CLASS)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.huawei.discovery.interceptors.httpconnection;

import com.huawei.discovery.interceptors.MarkInterceptor;
import com.huawei.discovery.retry.InvokerContext;
import com.huawei.discovery.service.InvokerService;
import com.huawei.discovery.utils.HttpConnectionUtils;
import com.huawei.discovery.utils.HttpConnectionUtils.HttpConnectionContext;
import com.huawei.discovery.utils.HttpConstants;
import com.huawei.discovery.utils.PlugEffectWhiteBlackUtils;
import com.huawei.discovery.utils.RequestInterceptorUtils;

import com.huaweicloud.sermant.core.common.LoggerFactory;
import com.huaweicloud.sermant.core.plugin.agent.entity.ExecuteContext;
import com.huaweicloud.sermant.core.plugin.service.PluginServiceManager;
import com.huaweicloud.sermant.core.utils.ReflectUtils;

import java.net.URL;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* 拦截HttpUrlConnection#connect方法, 检查SocketTimeoutException: connect timed out
*
* @author zhouss
* @since 2022-10-20
*/
public class HttpUrlConnectionConnectInterceptor extends MarkInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger();

@Override
protected ExecuteContext doBefore(ExecuteContext context) throws Exception {
final InvokerService invokerService = PluginServiceManager.getPluginService(InvokerService.class);
final Optional<URL> rawUrl = getUrl(context.getObject());
if (!rawUrl.isPresent()) {
return context;
}
final URL url = rawUrl.get();
final String fullUrl = url.toString();
Map<String, String> urlInfo = RequestInterceptorUtils.recovertUrl(fullUrl);
if (!PlugEffectWhiteBlackUtils.isAllowRun(url.getHost(), urlInfo.get(HttpConstants.HTTP_URI_HOST),
false)) {
return context;
}
HttpConnectionUtils.save(new HttpConnectionContext(urlInfo, url));
RequestInterceptorUtils.printRequestLog("HttpURLConnection", urlInfo);
invokerService.invoke(
buildInvokerFunc(context, url, urlInfo),
ex -> ex,
urlInfo.get(HttpConstants.HTTP_URI_HOST))
.ifPresent(obj -> {
if (obj instanceof Exception) {
LOGGER.log(Level.SEVERE, "request is error, uri is " + fullUrl, (Exception) obj);
context.setThrowableOut((Exception) obj);
return;
}
context.skip(obj);
});
return context;
}

private Optional<URL> getUrl(Object target) {
final Optional<Object> url = ReflectUtils.getFieldValue(target, "url");
if (url.isPresent() && url.get() instanceof URL) {
return Optional.of((URL) url.get());
}
return Optional.empty();
}

private Function<InvokerContext, Object> buildInvokerFunc(ExecuteContext context, URL url,
Map<String, String> urlInfo) {
return invokerContext -> {
tryReleaseConnection(context.getObject());
final String path = urlInfo.get(HttpConstants.HTTP_URI_PATH);
final Optional<URL> newUrl = RequestInterceptorUtils.rebuildUrlForHttpConnection(url,
invokerContext.getServiceInstance(), path);
newUrl.ifPresent(value -> ReflectUtils.setFieldValue(context.getObject(), "url", value));
return RequestInterceptorUtils.buildFunc(context, invokerContext).get();
};
}

private void tryReleaseConnection(Object target) {
final Optional<Object> connected = ReflectUtils.getFieldValue(target, "connected");
if (!connected.isPresent() || connected.get() instanceof Boolean) {
return;
}
final boolean isConnected = (boolean) connected.get();
if (isConnected) {
// 释放连接
LOGGER.fine("Release Http url connection when read timed out for retry!");
ReflectUtils.invokeMethod(target, "disconnect", null, null);
}
}

@Override
public ExecuteContext after(ExecuteContext context) throws Exception {
return context;
}

@Override
public ExecuteContext onThrow(ExecuteContext context) throws Exception {
return context;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2022-2022 Huawei Technologies Co., Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.huawei.discovery.interceptors.httpconnection;

import com.huawei.discovery.utils.HttpConnectionUtils;

import com.huaweicloud.sermant.core.plugin.agent.entity.ExecuteContext;
import com.huaweicloud.sermant.core.plugin.agent.interceptor.Interceptor;

/**
* 该拦截点主要在最终阶段清理连接
*
* @author zhouss
* @since 2022-10-20
*/
public class HttpUrlConnectionDisconnectCodeInterceptor implements Interceptor {
@Override
public ExecuteContext before(ExecuteContext context) throws Exception {
return context;
}

@Override
public ExecuteContext after(ExecuteContext context) throws Exception {
HttpConnectionUtils.remove();
return context;
}

@Override
public ExecuteContext onThrow(ExecuteContext context) throws Exception {
return context;
}
}
Loading

0 comments on commit a0f23a3

Please sign in to comment.