运行 soul-examlpes-http 实例遇到的问题

前置条件

  1. 从github下载代码网关后端项目soul、网关前端项目soul-dashboard
  2. 本地安装好mysql、zookeeper并启动
  3. 前端项目编译需要nodejs,npm
  4. 启动soul-admin 管理端
  5. 启动soul-bootstrap 网关
  6. 启动 soul-examlpes-http 实例项目
  7. 运行前端项目,打开浏览器输入 http://localhost:8000/

测试项目

  1. 测试实例项目返回正常
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"}
  1. 测试通过网关访问实例项目
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插件的正常使用。
不然给用户的感觉是会莫名其妙的问题。

posted @ 2021-04-22 16:51  zendwang  阅读(106)  评论(0编辑  收藏  举报