Soul网关源码阅读番外篇(一) HTTP参数请求错误
- 共同作者:石立 萧 *
简介
在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索
Bug复现
相关环境配置
首先把代码拉下来,然后切换到2.2.1版本,命令大致如下:
# 加速拉取
git clone https://github.com.cnpmjs.org/lw1243925457/soul.git
# 切换到2.2.1版本
git fetch origin 2.2.1:2.2.1
git checkout 2.2.1
如果之前运行过Soul网关的,需要清理下数据库,这里删除原来的soul数据库,让2.2.1版本自己重新建立一个
# 使用docker启动mysql
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:latest
# 重启,需要删除soul数据库,然后让程序自己重建
docker restart mysql
# 使用命令登录,删除原来的数据库
docker exec -ti mysql mysql -u root -p
> drop database soul;
Soul——Admin启动
修改Soul-admin模块下的配置文件:soul-admin --> application-local.yml
修改mysql用户和密码: root root
修改链接配置:jdbc:mysql://localhost:3306/soul?useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&&useSSL=false
启动soul-admin --> SoulAdminBootstrap
如果出现SelectorTypeEnum相关的错误,请切换到jdk8
启动Soul-Bootstrap
启动soul-bootstrap --> SoulBootstrapApplication
启动HTTP test
首先右键soul-test根目录下的pom.xml,选择 add as maven project,导入工程
可能会出现依赖错误,将其版本替换为2.2.1,大致如下:
<dependency>
<groupId>org.dromara</groupId>
<artifactId>soul-spring-boot-starter-client-springmvc</artifactId>
<version>2.2.1</version>
</dependency>
启动soul-test --> soul-test-http --> SoulTestHttpApplication
请求复现
访问管理界面: http://localhost:9095/ ,查看插件列表 --> divide ,表现正常
访问问题链接: http://localhost:9195/http/order/findById?id=1 ,可以看到出现了404
{
"timestamp": "2021-01-18T02:18:19.557+0000",
"path": "/",
"status": 404,
"error": "Not Found",
"message": null,
"requestId": "84752141"
}
直接访问: http://localhost:8187/order/findById?id=11 ,正常的
{
"id": "11",
"name": "hello world findById"
}
OK,到这问题基本复现,下面开始debug
源码Debug
查看日志进行切入
根据老哥的提示,我们也看到了这个问题请求的相关日志,大致如下
o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
o.d.soul.plugin.base.AbstractSoulPlugin : divide rule success match ,rule name :/http/order/findById
o.d.s.plugin.httpclient.WebClientPlugin : you request,The resulting urlPath is :http://192.168.101.104:8187?id=1111
最后一句urlpath非常的诡异,完整路径不对。我们就直接看下这个类: WebClientPlugin
# WebClientPlugin
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
assert soulContext != null;
# 在这里debug看到取出来的路径是: http://192.168.101.104:8187?id=1111
String urlPath = exchange.getAttribute(Constants.HTTP_URL);
if (StringUtils.isEmpty(urlPath)) {
Object error = SoulResultWarp.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);
log.info("you request,The resulting urlPath is :{}", urlPath);
HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
return handleRequestBody(requestBodySpec, exchange, timeout, chain);
}
在上面这个类中,可以看到就是单纯取路径,我们需要跟踪这个路径的来源
Divide查看
在前面几篇分析中,我们知道divide plugin 是进行路由配置,并写入真实路径到exchange中的,我们去 DividePlugin 看看
# DividePlugin
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.