运行 soul-examlpes-http 实例遇到的问题
前置条件
- 从github下载代码网关后端项目soul、网关前端项目soul-dashboard
- 本地安装好mysql、zookeeper并启动
- 前端项目编译需要nodejs,npm
- 启动soul-admin 管理端
- 启动soul-bootstrap 网关
- 启动 soul-examlpes-http 实例项目
- 运行前端项目,打开浏览器输入 http://localhost:8000/
测试项目
- 测试实例项目返回正常
http://127.0.0.1:8189/order/path/1/sdsd
zend-deMacBook-Pro:bin zend$ curl http://127.0.0.1:8189/order/path/1/sdsd
{"id":"1","name":"hello world restful: sdsd"}
- 测试通过网关访问实例项目
curl http://localhost:9195/http/order/path/1/sdsd
{"timestamp":"2021-04-22T07:40:29.168+0000","path":"/http/order/path/1/sdsd","status":404,"error":"Not Found","message":null,"requestId":"3f41230f"}
经过观察日志发现
2021-04-22 14:55:39 [soul-work-threads-5] INFO org.dromara.soul.plugin.base.AbstractSoulPlugin - divide rule success match , rule name :/http/order/path/**
2021-04-22 14:55:39 [soul-work-threads-5] INFO org.dromara.soul.plugin.httpclient.WebClientPlugin - The request urlPath is http://127.0.0.1:8189/http/order/path/1/sdsd, retryTimes is 0
实际上正常应该转发到这个后端地址http://127.0.0.1:8189/order/path/1/sdsd
应该是经过DividePlugin插件做负载均衡, 然后是WebClientPlugin插件做http请求调用;
应该是这一块的问题DividePlugin.java
@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);
.....
// set the http url
String domain = buildDomain(divideUpstream);
** String realURL = buildRealURL(domain, soulContext, exchange);**
exchange.getAttributes().put(Constants.HTTP_URL, realURL);
....
}
....
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().getRawQuery();
if (StringUtils.isNoneBlank(query)) {
return path + "?" + query;
}
return path;
}
}
应该是buildRealURL方法这一块的问题,经过调试发现realUrl为/http/order/path/1/sdsd;
再查是哪里设置的realUrl?
在 GlobalPlugin插件中配置的 builder.build(exchange)中配置的soulContext
GlobalPlugin.java
public class GlobalPlugin implements SoulPlugin {
private final SoulContextBuilder builder;
@Override
public Mono<Void> execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
...
SoulContext soulContext;
if (StringUtils.isBlank(upgrade) || !"websocket".equals(upgrade)) {
soulContext = builder.build(exchange);
} else {
final MultiValueMap<String, String> queryParams = request.getQueryParams();
soulContext = transformMap(queryParams);
}
exchange.getAttributes().put(Constants.CONTEXT, soulContext);
return chain.execute(exchange);
}
....
}
最终调用DefaultSoulContextBuilder类中builder方法
public class DefaultSoulContextBuilder implements SoulContextBuilder {
private final Map<String, SoulContextDecorator> decoratorMap;
......
@Override
public SoulContext build(final ServerWebExchange exchange) {
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 Optional.ofNullable(metaData).map(e -> decoratorMap.get(e.getRpcType()))
.orElse(decoratorMap.get(RpcTypeEnum.HTTP.getName()))
.decorator(buildDefault(request), metaData);
}
private SoulContext buildDefault(final ServerHttpRequest request) {
String appKey = request.getHeaders().getFirst(Constants.APP_KEY);
String sign = request.getHeaders().getFirst(Constants.SIGN);
String timestamp = request.getHeaders().getFirst(Constants.TIMESTAMP);
SoulContext soulContext = new SoulContext();
String path = request.getURI().getPath();
soulContext.setPath(path);
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;
}
}
又使用类DivideSoulContextDecorator去完善soulContext里部分属性字段
public class DivideSoulContextDecorator implements SoulContextDecorator {
@Override
public SoulContext decorator(final SoulContext soulContext, final MetaData metaData) {
String path = soulContext.getPath();
soulContext.setMethod(path);
soulContext.setRealUrl(path);
soulContext.setRpcType(RpcTypeEnum.HTTP.getName());
return soulContext;
}
}
realUrl 就是在这里设置的
继续搜索 发现ContextPathMappingPlugin类里也有对realUrl的处理
public class ContextPathMappingPlugin 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 String handle = rule.getHandle();
final ContextMappingHandle contextMappingHandle = GsonUtils.getInstance().fromJson(handle, ContextMappingHandle.class);
if (Objects.isNull(contextMappingHandle) || StringUtils.isBlank(contextMappingHandle.getContextPath())) {
log.error("context path mapping rule configuration is null :{}", rule);
return chain.execute(exchange);
}
this.buildContextPath(soulContext, contextMappingHandle);
return chain.execute(exchange);
}
...
/**
* Build the context path and realUrl.
*
* @param context context
* @param handle handle
*/
private void buildContextPath(final SoulContext context, final ContextMappingHandle handle) {
context.setContextPath(handle.getContextPath());
context.setModule(handle.getContextPath());
if (!StringUtils.isBlank(handle.getRealUrl())) {
log.info("context path mappingPlugin replaced old :{} , real:{}", context.getRealUrl(), handle.getRealUrl());
context.setRealUrl(handle.getRealUrl());
return;
}
String realUrl = context.getPath().substring(handle.getContextPath().length());
context.setRealUrl(realUrl);
}
}
去官网了解一下这个插件发现
soul网关在对目标服务调用的时候,还容许用户使用 context_path 插件来重写请求路径的contextPath,
还要求用divide插件时必须要开启context_path插件;
再去管理台查一下这个插件是否开启,发现被我手抖给关闭了,
开启该插件后,再去试试看,一切正常。
我觉得用户体验不好,关闭context_path插件要提醒一下 用户,关闭了会影响divide插件的正常使用。
不然给用户的感觉是会莫名其妙的问题。