本篇文章介绍soul对sofa的插件支持,以及sofa服务接入soul网关。对应官方文档可以参考:dromara.org/zh-cn/docs/…
sofa服务接入soul网关步骤
1、启动sole-admin。登陆localhost:9095。
2、启动soul-bootstrap
- pom.xml引入对应jar包,添加对dubbo插件的支持
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-rpc-all</artifactId>
<version>5.7.6</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-plugin-sofa</artifactId>
<version>2.2.1</version>
</dependency>
-
启动zookeeper
查看配置文件 缺省的配置文件在以下目录中
> cd /usr/local/etc/zookeeper/启动服务
> zkServer start -
启动 SoulBootstrapApplication
3、启动soul-examples-sofa
1、 pom.xml新增配置
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-client-sofa</artifactId>
<version>${soul.version}</version>
<exclusions>
<exclusion>
<artifactId>guava</artifactId>
<groupId>com.google.guava</groupId>
</exclusion>
</exclusions>
</dependency>
2、 配置application.xml
soul:
sofa:
adminUrl: http://localhost:9095
contextPath: /sofa
appName: sofa
3、 启动SoulBootstrapApplication
我们看到sole-admin 的插件列表->sofa 中有了对应的sofa初始化数据,系统管理->元数据管理 里面也有了应用名称为sofa的元数据。
4、sofa 插件设置
- 首先在
soul-admin插件管理中,把sofa插件设置为开启。 - 其次在
sofa插件中配置你的注册地址或者其他注册中心的地址.
{"protocol":"zookeeper","register":"127.0.0.1:2181"}
由上节课的知识(juejin.cn/post/691840… SofaServiceBeanPostProcessor 类中,部分关键代码如下:
@Slf4j
public class SofaServiceBeanPostProcessor implements BeanPostProcessor {
private final SofaConfig sofaConfig;
private final ExecutorService executorService;
private final String url;
public SofaServiceBeanPostProcessor(final SofaConfig sofaConfig) {
String contextPath = sofaConfig.getContextPath();
String adminUrl = sofaConfig.getAdminUrl();
if (contextPath == null || "".equals(contextPath)
|| adminUrl == null || "".equals(adminUrl)) {
throw new RuntimeException("sofa client must config the contextPath, adminUrl");
}
this.sofaConfig = sofaConfig;
url = sofaConfig.getAdminUrl() + "/soul-client/sofa-register";
executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof ServiceFactoryBean) {
executorService.execute(() -> handler((ServiceFactoryBean) bean));
}
return bean;
}
private void handler(final ServiceFactoryBean serviceBean) {
Class<?> clazz;
try {
clazz = ((Service) Objects.requireNonNull(serviceBean.getObject())).getTarget().getClass();
} catch (Exception e) {
log.error("failed to get sofa target class");
return;
}
if (ClassUtils.isCglibProxyClass(clazz)) {
String superClassName = clazz.getGenericSuperclass().getTypeName();
try {
clazz = Class.forName(superClassName);
} catch (ClassNotFoundException e) {
log.error(String.format("class not found: %s", superClassName));
return;
}
}
final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
for (Method method : methods) {
SoulSofaClient soulSofaClient = method.getAnnotation(SoulSofaClient.class);
if (Objects.nonNull(soulSofaClient)) {
RegisterUtils.doRegister(buildJsonParams(serviceBean, soulSofaClient, method), url, RpcTypeEnum.SOFA);
}
}
}
private String buildJsonParams(final ServiceFactoryBean serviceBean, final SoulSofaClient soulSofaClient, final Method method) {
String appName = sofaConfig.getAppName();
String path = sofaConfig.getContextPath() + soulSofaClient.path();
String desc = soulSofaClient.desc();
String serviceName = serviceBean.getInterfaceClass().getName();
String configRuleName = soulSofaClient.ruleName();
String ruleName = ("".equals(configRuleName)) ? path : configRuleName;
String methodName = method.getName();
Class<?>[] parameterTypesClazz = method.getParameterTypes();
String parameterTypes = Arrays.stream(parameterTypesClazz).map(Class::getName)
.collect(Collectors.joining(","));
MetaDataDTO metaDataDTO = MetaDataDTO.builder()
.appName(appName)
.serviceName(serviceName)
.methodName(methodName)
.contextPath(sofaConfig.getContextPath())
.path(path)
.ruleName(ruleName)
.pathDesc(desc)
.parameterTypes(parameterTypes)
.rpcType("sofa")
.rpcExt(buildRpcExt(soulSofaClient))
.enabled(soulSofaClient.enabled())
.build();
return OkHttpTools.getInstance().getGson().toJson(metaDataDTO);
}
private String buildRpcExt(final SoulSofaClient soulSofaClient) {
MetaDataDTO.RpcExt build = MetaDataDTO.RpcExt.builder()
.loadbalance(soulSofaClient.loadBalance())
.retries(soulSofaClient.retries())
.timeout(soulSofaClient.timeout())
.build();
return OkHttpTools.getInstance().getGson().toJson(build);
}
}
项目启动时,实例化、依赖注入、初始化完毕时执行会加载postProcessAfterInitialization里面的数据, 我们debug发现,实际情况确是如我们猜测的那样:
SofaServiceBeanPostProcessor 实现了 BeanPostProcessor 和重载了postProcessAfterInitialization方法, BeanPostProcessor 的两个方法如下
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
| 方法 | 说明 |
|---|---|
| postProcessBeforeInitialization | 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务 |
| postProcessAfterInitialization | 实例化、依赖注入、初始化完毕时执行 |
postProcessAfterInitialization 方法会在soul-examples-sofa项目启动时,实例化、依赖注入、初始化完毕时执行里面重载的方法,将加有@SoulSofaClient 的方法都相关数据通过 http:localhost9095/soul-client/sofa-register 注册到 sole-admin 中。
通过上篇文章 和 本篇文章的学习,我们目前掌握了两种项目启动时,实例化、依赖注入、初始化完毕时执行相关业务的方法,
- 实现ApplicationListener 并重载 onApplicationEvent 方法
- 实现 BeanPostProcessor 并重载 postProcessAfterInitialization 方法
验证sofa服务是否正常, 先来个错误演示:
我们请求接口 localhost:9195/soul/findById?id=nihao, 返回错误信息如下
{
"code": -107,
"message": "Can not find selector, please check your configuration!",
"data": null
}
通过代码发现,web请求会执行WebHandler的handle 方法,该方法主要是对http请求做处理并将结果作出响应。我们启动的是sola服务,SoulWebHandler 实现了WebHandler的handle 方法。 所以当我们发起sola网关请求时,sola服务执行到SoulWebHandler的 handle方法, 遍历插件SoulPlugin并进行插件AbstractSoulPlugin 的 execute方法, 发现divide插件开启,就执行对应的插件服务,但是发现没有对应的选择器(或服务),所以报错。 我们把系统管理->插件管理 中除了sofa 的插件全部关闭,以避免此类问题干扰。由此我们得知,调用方法时会遍历插件,如果插件开启,就会找对应的选择器和服务执行,选择器不存在或服务不存在,就会报异常,所以测试哪个插件,只启用该插件就可以了。
部分代码
SoulWebHandler 的
public final class SoulWebHandler implements WebHandler {
private final List<SoulPlugin> plugins;
private final Scheduler scheduler;
/**
* Instantiates a new Soul web handler.
*
* @param plugins the plugins
*/
public SoulWebHandler(final List<SoulPlugin> plugins) {
this.plugins = plugins;
String schedulerType = System.getProperty("soul.scheduler.type", "fixed");
if (Objects.equals(schedulerType, "fixed")) {
int threads = Integer.parseInt(System.getProperty(
"soul.work.threads", "" + Math.max((Runtime.getRuntime().availableProcessors() << 1) + 1, 16)));
scheduler = Schedulers.newParallel("soul-work-threads", threads);
} else {
scheduler = Schedulers.elastic();
}
}
/**
* Handle the web server exchange.
*
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
public Mono<Void> handle(@NonNull final ServerWebExchange exchange) {
MetricsTrackerFacade.getInstance().counterInc(MetricsLabelEnum.REQUEST_TOTAL.getName());
Optional<HistogramMetricsTrackerDelegate> startTimer = MetricsTrackerFacade.getInstance().histogramStartTimer(MetricsLabelEnum.REQUEST_LATENCY.getName());
return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
.doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
}
private static class DefaultSoulPluginChain implements SoulPluginChain {
private int index;
private final List<SoulPlugin> plugins;
/**
* Instantiates a new Default soul plugin chain.
*
* @param plugins the plugins
*/
DefaultSoulPluginChain(final List<SoulPlugin> plugins) {
this.plugins = plugins;
}
/**
* Delegate to the next {@code WebFilter} in the chain.
*
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
@Override
public Mono<Void> execute(final ServerWebExchange exchange) {
return Mono.defer(() -> {
if (this.index < plugins.size()) {
SoulPlugin plugin = plugins.get(this.index++);
Boolean skip = plugin.skip(exchange);
if (skip) {
return this.execute(exchange);
}
return plugin.execute(exchange, this);
}
return Mono.empty();
});
}
}
}
AbstractSoulPlugin:
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if (pluginData != null && pluginData.getEnabled()) {
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
return doExecute(exchange, chain, selectorData, rule);
}
return chain.execute(exchange);
}
@Slf4j
public class DividePlugin extends AbstractSoulPlugin {
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
if (CollectionUtils.isEmpty(upstreamList)) {
log.error("divide upstream configuration error: {}", rule.toString());
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
if (Objects.isNull(divideUpstream)) {
log.error("divide has no upstream");
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// set the http url
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// set the http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
@Override
public String named() {
return PluginEnum.DIVIDE.getName();
}
@Override
public Boolean skip(final ServerWebExchange exchange) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
return !Objects.equals(Objects.requireNonNull(soulContext).getRpcType(), RpcTypeEnum.HTTP.getName());
}
@Override
public int getOrder() {
return PluginEnum.DIVIDE.getCode();
}
private String buildDomain(final DivideUpstream divideUpstream) {
String protocol = divideUpstream.getProtocol();
if (StringUtils.isBlank(protocol)) {
protocol = "http://";
}
return protocol + divideUpstream.getUpstreamUrl().trim();
}
private String buildRealURL(final String domain, final SoulContext soulContext, final ServerWebExchange exchange) {
String path = domain;
final String rewriteURI = (String) exchange.getAttributes().get(Constants.REWRITE_URI);
if (StringUtils.isNoneBlank(rewriteURI)) {
path = path + rewriteURI;
} else {
final String realUrl = soulContext.getRealUrl();
if (StringUtils.isNoneBlank(realUrl)) {
path = path + realUrl;
}
}
String query = exchange.getRequest().getURI().getQuery();
if (StringUtils.isNoneBlank(query)) {
return path + "?" + query;
}
return path;
}
}
接下来继续进行验证,结果又遇到了个问题, 就是把divide 插件关闭时缓存没生效, 打开divide插件重新确认了下,缓存生效了, 这个地方先不做关注。接下来我们重启请求服务,localhost:9195/soul/findById?id=nihao,结果依然报错:
{
"code": -106,
"message": "Can not find url, please check your configuration!",
"data": null
}
我们看代码:
SoulConfiguration 部分初始化代码:
@Configuration
@ComponentScan("org.dromara.soul")
@Import(value = {ErrorHandlerConfiguration.class, SoulExtConfiguration.class, SpringExtConfiguration.class})
@Slf4j
public class SoulConfiguration {
/**
* Init SoulWebHandler.
*
* @param plugins this plugins is All impl SoulPlugin.
* @return {@linkplain SoulWebHandler}
*/
@Bean("webHandler")
public SoulWebHandler soulWebHandler(final ObjectProvider<List<SoulPlugin>> plugins) {
List<SoulPlugin> pluginList = plugins.getIfAvailable(Collections::emptyList);
final List<SoulPlugin> soulPlugins = pluginList.stream()
.sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
soulPlugins.forEach(soulPlugin -> log.info("load plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
return new SoulWebHandler(soulPlugins);
}
}
Soul-bootstrap启动时加载了16个插件,并不是soul-admin里面展示的14个插件。而我们发现soul-admin的插件列表里面展示的14个插件,都实现了AbstractSoulPlugin的doExecute 方法, 而AbstractSoulPlugin又实现了SoulPlugin的execute 方法, execute方法里面校验如果插件存在并且处于开启状态时,会调用该插件的服务doExecute。而不在soul-admin列表里面的插件比如WebClientPlugin直接实现了SoulPlugin的execute并进行请求处理。 所以当我们请求sola服务时,遍历插件,当遍历到WebClientPlugin时直接执行execute方法,得到urlPath为空,直接返回“Can not find url, please check your configuration!”。为什么会执行 WebClientPlugin 的execute 呢,没有被WebClientPlugin的skip 拦截住呢? WebClientPlugin 只能拦截http请求或springCloud 请求, 当sola请求地址不对时,就会变成http请求被WebClientPlugin处理。
下面是部分核心代码及debug截图:
DividePlugin:soul-admin的插件列表里面的插件:
@Slf4j
public class DividePlugin extends AbstractSoulPlugin {
@Override
protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
final List<DivideUpstream> upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
if (CollectionUtils.isEmpty(upstreamList)) {
log.error("divide upstream configuration error: {}", rule.toString());
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
final String ip = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
if (Objects.isNull(divideUpstream)) {
log.error("divide has no upstream");
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
// set the http url
String domain = buildDomain(divideUpstream);
String realURL = buildRealURL(domain, soulContext, exchange);
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
// set the http timeout
exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());
exchange.getAttributes().put(Constants.HTTP_RETRY, ruleHandle.getRetry());
return chain.execute(exchange);
}
@Override
public String named() {
return PluginEnum.DIVIDE.getName();
}
@Override
public Boolean skip(final ServerWebExchange exchange) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
return !Objects.equals(Objects.requireNonNull(soulContext).getRpcType(), RpcTypeEnum.HTTP.getName());
}
@Override
public int getOrder() {
return PluginEnum.DIVIDE.getCode();
}
private String buildDomain(final DivideUpstream divideUpstream) {
String protocol = divideUpstream.getProtocol();
if (StringUtils.isBlank(protocol)) {
protocol = "http://";
}
return protocol + divideUpstream.getUpstreamUrl().trim();
}
private String buildRealURL(final String domain, final SoulContext soulContext, final ServerWebExchange exchange) {
String path = domain;
final String rewriteURI = (String) exchange.getAttributes().get(Constants.REWRITE_URI);
if (StringUtils.isNoneBlank(rewriteURI)) {
path = path + rewriteURI;
} else {
final String realUrl = soulContext.getRealUrl();
if (StringUtils.isNoneBlank(realUrl)) {
path = path + realUrl;
}
}
String query = exchange.getRequest().getURI().getQuery();
if (StringUtils.isNoneBlank(query)) {
return path + "?" + query;
}
return path;
}
}
AbstractSoulPlugin 的execute 对soul-admin上的插件是否开启做了校验:
@RequiredArgsConstructor
@Slf4j
public abstract class AbstractSoulPlugin implements SoulPlugin {
/**
* this is Template Method child has Implement your own logic.
*
* @param exchange exchange the current server exchange {@linkplain ServerWebExchange}
* @param chain chain the current chain {@linkplain ServerWebExchange}
* @param selector selector {@linkplain SelectorData}
* @param rule rule {@linkplain RuleData}
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
protected abstract Mono<Void> doExecute(ServerWebExchange exchange, SoulPluginChain chain, SelectorData selector, RuleData rule);
/**
* Process the Web request and (optionally) delegate to the next
* {@code SoulPlugin} through the given {@link SoulPluginChain}.
*
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next plugin
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
String pluginName = named();
final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
if (pluginData != null && pluginData.getEnabled()) {
final Collection<SelectorData> selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
if (CollectionUtils.isEmpty(selectors)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
final SelectorData selectorData = matchSelector(exchange, selectors);
if (Objects.isNull(selectorData)) {
return handleSelectorIsNull(pluginName, exchange, chain);
}
selectorLog(selectorData, pluginName);
final List<RuleData> rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
if (CollectionUtils.isEmpty(rules)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
RuleData rule;
if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
//get last
rule = rules.get(rules.size() - 1);
} else {
rule = matchRule(exchange, rules);
}
if (Objects.isNull(rule)) {
return handleRuleIsNull(pluginName, exchange, chain);
}
ruleLog(rule, pluginName);
return doExecute(exchange, chain, selectorData, rule);
}
return chain.execute(exchange);
}
private SelectorData matchSelector(final ServerWebExchange exchange, final Collection<SelectorData> selectors) {
return selectors.stream()
.filter(selector -> selector.getEnabled() && filterSelector(selector, exchange))
.findFirst().orElse(null);
}
private Boolean filterSelector(final SelectorData selector, final ServerWebExchange exchange) {
if (selector.getType() == SelectorTypeEnum.CUSTOM_FLOW.getCode()) {
if (CollectionUtils.isEmpty(selector.getConditionList())) {
return false;
}
return MatchStrategyUtils.match(selector.getMatchMode(), selector.getConditionList(), exchange);
}
return true;
}
private RuleData matchRule(final ServerWebExchange exchange, final Collection<RuleData> rules) {
return rules.stream()
.filter(rule -> filterRule(rule, exchange))
.findFirst().orElse(null);
}
private Boolean filterRule(final RuleData ruleData, final ServerWebExchange exchange) {
return ruleData.getEnabled() && MatchStrategyUtils.match(ruleData.getMatchMode(), ruleData.getConditionDataList(), exchange);
}
private Mono<Void> handleSelectorIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
if (PluginEnum.WAF.getName().equals(pluginName)) {
return doExecute(exchange, chain, null, null);
}
return CheckUtils.checkSelector(pluginName, exchange, chain);
}
private Mono<Void> handleRuleIsNull(final String pluginName, final ServerWebExchange exchange, final SoulPluginChain chain) {
if (PluginEnum.WAF.getName().equals(pluginName)) {
return doExecute(exchange, chain, null, null);
}
return CheckUtils.checkRule(pluginName, exchange, chain);
}
private void selectorLog(final SelectorData selectorData, final String pluginName) {
if (selectorData.getLoged()) {
log.info("{} selector success match , selector name :{}", pluginName, selectorData.getName());
}
}
private void ruleLog(final RuleData ruleData, final String pluginName) {
if (ruleData.getLoged()) {
log.info("{} selector success match , selector name :{}", pluginName, ruleData.getName());
}
}
}
WebClientPlugin则直接执行实现SoulPlugin的execute方法:
@Slf4j
public class WebClientPlugin implements SoulPlugin {
private final WebClient webClient;
/**
* Instantiates a new Web client plugin.
*
* @param webClient the web client
*/
public WebClientPlugin(final WebClient webClient) {
this.webClient = webClient;
}
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
String urlPath = exchange.getAttribute(Constants.HTTP_URL);
if (StringUtils.isEmpty(urlPath)) {
Object error = SoulResultWrap.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
long timeout = (long) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_TIME_OUT)).orElse(3000L);
int retryTimes = (int) Optional.ofNullable(exchange.getAttribute(Constants.HTTP_RETRY)).orElse(0);
log.info("The request urlPath is {}, retryTimes is {}", urlPath, retryTimes);
HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
return handleRequestBody(requestBodySpec, exchange, timeout, retryTimes, chain);
}
@Override
public int getOrder() {
return PluginEnum.DIVIDE.getCode() + 1;
}
@Override
public String named() {
return "webClient";
}
@Override
public Boolean skip(final ServerWebExchange exchange) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
return !Objects.equals(RpcTypeEnum.HTTP.getName(), soulContext.getRpcType())
&& !Objects.equals(RpcTypeEnum.SPRING_CLOUD.getName(), soulContext.getRpcType());
}
private MediaType buildMediaType(final ServerWebExchange exchange) {
return MediaType.valueOf(Optional.ofNullable(exchange
.getRequest()
.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE))
.orElse(MediaType.APPLICATION_JSON_VALUE));
}
private Mono<Void> handleRequestBody(final WebClient.RequestBodySpec requestBodySpec,
final ServerWebExchange exchange,
final long timeout,
final int retryTimes,
final SoulPluginChain chain) {
return requestBodySpec.headers(httpHeaders -> {
httpHeaders.addAll(exchange.getRequest().getHeaders());
httpHeaders.remove(HttpHeaders.HOST);
})
.contentType(buildMediaType(exchange))
.body(BodyInserters.fromDataBuffers(exchange.getRequest().getBody()))
.exchange()
.doOnError(e -> log.error(e.getMessage()))
.timeout(Duration.ofMillis(timeout))
.retryWhen(Retry.onlyIf(x -> x.exception() instanceof ConnectTimeoutException)
.retryMax(retryTimes)
.backoff(Backoff.exponential(Duration.ofMillis(200), Duration.ofSeconds(20), 2, true)))
.flatMap(e -> doNext(e, exchange, chain));
}
private Mono<Void> doNext(final ClientResponse res, final ServerWebExchange exchange, final SoulPluginChain chain) {
if (res.statusCode().is2xxSuccessful()) {
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());
} else {
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.ERROR.getName());
}
exchange.getAttributes().put(Constants.CLIENT_RESPONSE_ATTR, res);
return chain.execute(exchange);
}
}
部分debug截图:
我们既然定位到是请求地址不对,换请求地址,继续访问, localhost:9195/sofa/findById?id=nihao:
仍然出错,定位原因,SofaParamResolveService没实现类,报空指针异常,拉最新代码,重新发布,继续访问,成功:
{
"code": 200,
"message": "Access to success!",
"data": {
"name": "hello world Soul Sofa, findById",
"id": "nihao"
}
}
总结
搞死我了,一直在错误到道路上debug。一定要细致,不过这也让我更清楚了代码的逻辑和结构。