soul网关mysql8

编程入门 行业动态 更新时间:2024-10-24 10:27:20

soul<a href=https://www.elefans.com/category/jswz/34/1769301.html style=网关mysql8"/>

soul网关mysql8

Soul网关源码阅读番外篇(一) HTTP参数请求错误

共同作者:石立 萧 *

简介

在Soul网关2.2.1版本源码阅读中,遇到了HTTP请求加上参数返回404的错误,此篇文章基于此进行探索

Bug复现

相关环境配置

首先把代码拉下来,然后切换到2.2.1版本,命令大致如下:

# 加速拉取

git clone .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,大致如下:

org.dromara

soul-spring-boot-starter-client-springmvc

2.2.1

启动soul-test --> soul-test-http --> SoulTestHttpApplication

请求复现

访问管理界面: http://localhost:9095/ ,查看插件列表 --> divide ,表现正常

{

"timestamp": "2021-01-18T02:18:19.557+0000",

"path": "/",

"status": 404,

"error": "Not Found",

"message": null,

"requestId": "84752141"

}

{

"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 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 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 upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());

if (CollectionUtils.isEmpty(upstreamList)) {

LOGGER.error("divide upstream configuration error:{}", rule.toString());

Object error = SoulResultWarp.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)) {

LOGGER.error("divide has no upstream");

Object error = SoulResultWarp.error(SoulResultEnum.CANNOT_FIND_URL.getCode(), SoulResultEnum.CANNOT_FIND_URL.getMsg(), null);

return WebFluxResultUtils.result(exchange, error);

}

//设置一下 http url : http://192.168.101.104:8187

String domain = buildDomain(divideUpstream);

// 在这设置realURL,进去看看这个函数

String realURL = buildRealURL(domain, soulContext, exchange);

// 放入exchange中

exchange.getAttributes().put(Constants.HTTP_URL, realURL);

exchange.getAttributes().put(Constants.HTTP_TIME_OUT, ruleHandle.getTimeout());

return chain.execute(exchange);

}

private String buildRealURL(final String domain, final SoulContext soulContext, final ServerWebExchange exchange) {

String path = domain;

// 在这取url,但通过debug发现,它确实是null

final String rewriteURI = (String) exchange.getAttributes().get(Constants.REWRITE_URI);

if (StringUtils.isNoneBlank(rewriteURI)) {

path = path + rewriteURI;

} else {

// 然后又进到这进行取,发现也是null

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;

}

在上面的分析中,发现取出来的都是null,而且没有看到url的设置之类的操作,divide竟然也是单纯的取值

URL设置探索

那我们需要继续探索url的是怎么设置进去的,通过上面的分析,目前有两者设置url的方式,如下面两段代码:

final String rewriteURI = (String) exchange.getAttributes().get(Constants.REWRITE_URI);

final String realUrl = soulContext.getRealUrl();

exchange.getAttributes().get(Constants.REWRITE_URI) 方式探索

我们类比响应的设置方式,可以得到第一种URL设置的方式大致如下:

exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());

// 可以得到放Constants.REWRITE_URI的大致代码如下:

exchange.getAttributes().put(Constants.REWRITE_URI

然后使用全局搜索:ctrl+shift+r ,exchange.getAttributes().put(Constants.REWRITE_URI

# RewritePlugin

protected Mono doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {

String handle = rule.getHandle();

final RewriteHandle rewriteHandle = GsonUtils.getInstance().fromJson(handle, RewriteHandle.class);

if (Objects.isNull(rewriteHandle) || StringUtils.isBlank(rewriteHandle.getRewriteURI())) {

log.error("uri rewrite rule can not configuration:{}", handle);

return chain.execute(exchange);

}

exchange.getAttributes().put(Constants.REWRITE_URI, rewriteHandle.getRewriteURI());

return chain.execute(exchange);

}

搜索到唯一一处有这个代码的类: RewritePlugin ,然后我们打断点,然后并不能进入这个逻辑,查看控制台,它是关闭的。那就先放着,看第二种设置方式

soulContext.getRealUrl() 的设置探索

运用类比,我们可以猜测设置的代码应该是: soulContext.setRealUrl

我们进行搜索,也成功的找到了唯一的一处代码,在类 DefaultSoulContextBuilder 中,大致如下:

# DefaultSoulContextBuilder

private void setSoulContextByHttp(final SoulContext soulContext, final String path) {

String contextPath = "/";

String[] splitList = StringUtils.split(path, "/");

if (splitList.length != 0) {

contextPath = contextPath.concat(splitList[0]);

}

String realUrl = path.substring(contextPath.length());

soulContext.setContextPath(contextPath);

soulContext.setModule(contextPath);

soulContext.setMethod(realUrl);

// 设置url

soulContext.setRealUrl(realUrl);

}

我们在这个函数上打上断点,然而非常不幸的是,也没有进入。瞬间头上???????,这是怎么肥事啊,都没设置

不抛弃不放弃,咋继续。看到realURL是从path来的,我们继续往上追求其来源,发现调用的是同一个类的下面这个函数 transform ,再上一层是 build

# DefaultSoulContextBuilder

private SoulContext transform(final ServerHttpRequest request, final MetaData metaData) {

final String appKey = request.getHeaders().getFirst(Constants.APP_KEY);

final String sign = request.getHeaders().getFirst(Constants.SIGN);

final String timestamp = request.getHeaders().getFirst(Constants.TIMESTAMP);

SoulContext soulContext = new SoulContext();

String path = request.getURI().getPath();

soulContext.setPath(path);

if (Objects.nonNull(metaData) && metaData.getEnabled()) {

if (RpcTypeEnum.SPRING_CLOUD.getName().equals(metaData.getRpcType())) {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(metaData.getRpcType());

} else {

setSoulContextByDubbo(soulContext, metaData);

}

} else {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(RpcTypeEnum.HTTP.getName());

}

soulContext.setAppKey(appKey);

soulContext.setSign(sign);

soulContext.setTimestamp(timestamp);

soulContext.setStartDateTime(LocalDateTime.now());

Optional.ofNullable(request.getMethod()).ifPresent(httpMethod -> soulContext.setHttpMethod(httpMethod.name()));

return soulContext;

}

public SoulContext build(final ServerWebExchange exchange) {

final ServerHttpRequest request = exchange.getRequest();

String path = request.getURI().getPath();

MetaData metaData = MetaDataCache.getInstance().obtain(path);

if (Objects.nonNull(metaData) && metaData.getEnabled()) {

exchange.getAttributes().put(Constants.META_DATA, metaData);

}

return transform(request, metaData);

}

在build函数上打上断点,感谢老天,成功进入,通过调用栈发现,竟然是熟悉的 GlobalPlugin 进行调用的

# GlobalPlugin

public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {

final ServerHttpRequest request = exchange.getRequest();

final HttpHeaders headers = request.getHeaders();

final String upgrade = headers.getFirst("Upgrade");

SoulContext soulContext;

if (StringUtils.isBlank(upgrade) || !"websocket".equals(upgrade)) {

soulContext = builder.build(exchange);

} else {

final MultiValueMap queryParams = request.getQueryParams();

soulContext = transformMap(queryParams);

}

exchange.getAttributes().put(Constants.CONTEXT, soulContext);

return chain.execute(exchange);

}

在下面的函数打上端口,逐步debug。在下面注释的地方可以看到:我们的是HTTP请求,但竟然走到Dubbo的逻辑里面去,这非常的不对劲

private SoulContext transform(final ServerHttpRequest request, final MetaData metaData) {

// http://127.0.0.1:9195/http/order/findById?id=1111

final String appKey = request.getHeaders().getFirst(Constants.APP_KEY);

final String sign = request.getHeaders().getFirst(Constants.SIGN);

final String timestamp = request.getHeaders().getFirst(Constants.TIMESTAMP);

SoulContext soulContext = new SoulContext();

String path = request.getURI().getPath();

soulContext.setPath(path);

// 下面这个就神了,判断直接进到了setSoulContextByDubbo

if (Objects.nonNull(metaData) && metaData.getEnabled()) {

if (RpcTypeEnum.SPRING_CLOUD.getName().equals(metaData.getRpcType())) {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(metaData.getRpcType());

} else {

// 应该是进到HTTP的,估计就这出错了

setSoulContextByDubbo(soulContext, metaData);

}

} else {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(RpcTypeEnum.HTTP.getName());

}

soulContext.setAppKey(appKey);

soulContext.setSign(sign);

soulContext.setTimestamp(timestamp);

soulContext.setStartDateTime(LocalDateTime.now());

Optional.ofNullable(request.getMethod()).ifPresent(httpMethod -> soulContext.setHttpMethod(httpMethod.name()));

return soulContext;

}

我们使用下面的diff工具,看看最新版本的代码和目前版本有什么区别:

通过上图我们可以发现,最新版本中进行了更严谨的判断,并将默认的请求类型设置为了HTTP,这样再新版本代码中,就能走HTTP的处理逻辑

我们将代码修改一下,将HTTP设置为默认处理,代码大致如下:

private SoulContext transform(final ServerHttpRequest request, final MetaData metaData) {

final String appKey = request.getHeaders().getFirst(Constants.APP_KEY);

final String sign = request.getHeaders().getFirst(Constants.SIGN);

final String timestamp = request.getHeaders().getFirst(Constants.TIMESTAMP);

SoulContext soulContext = new SoulContext();

String path = request.getURI().getPath();

soulContext.setPath(path);

if (Objects.nonNull(metaData) && metaData.getEnabled()) {

if (RpcTypeEnum.SPRING_CLOUD.getName().equals(metaData.getRpcType())) {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(metaData.getRpcType());

} else if (RpcTypeEnum.DUBBO.getName().equals(metaData.getRpcType())) {

setSoulContextByDubbo(soulContext, metaData);

} else {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(RpcTypeEnum.HTTP.getName());

}

} else {

setSoulContextByHttp(soulContext, path);

soulContext.setRpcType(RpcTypeEnum.HTTP.getName());

}

soulContext.setAppKey(appKey);

soulContext.setSign(sign);

soulContext.setTimestamp(timestamp);

soulContext.setStartDateTime(LocalDateTime.now());

Optional.ofNullable(request.getMethod()).ifPresent(httpMethod -> soulContext.setHttpMethod(httpMethod.name()));

return soulContext;

}

{

"id": "1111",

"name": "hello world findById"

}

到这,我们成功的定位并修复了这个错误(虽然没有啥用,但开心啊)

总结

本篇文章中对Soul网关2.2.1版本中HTTP请求出现404的错误进行了详细的分析

通过上面的分析可以看出,在2.2.1中,不是Spring cloud的HTTP请求,都会发生错误,这个bug还是有点厉害的

还认识到了GlobalPlugin这个插件的重要作用,不仅设置了类型,还设置了真实的后端服务器路径,可以说这个插件很核心。rewrite插件也有设置路径这个功能

又有了新的认识,更新下我们请求处理图:

Soul网关源码分析文章列表

Github

掘金

更多推荐

soul网关mysql8

本文发布于:2024-02-07 00:40:43,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1752048.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:网关   soul

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!