Soul 网关源码学习(16) - Soul 网关插件 ContextPathMapping, Rewrite

ContextPathMappingPlugin 源码分析

源码分析

ContextPathMappingPlugin 源码来看, ContextPathMappingPlugin 最终会将 Soul 网关中的请求 API 的 ContextPath 设置到 SoulContext 中。ContextPathMappingPlugin 继承自 AbstractSoulPlugin,
和之前继承自 AbstractPlugin 的插件实现的逻辑一样, ContextPathMappingPlugin 的主要实现的逻辑在 doExecute 方法中:

@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);
}
//check the context path illegal
if (!soulContext.getPath().startsWith(contextMappingHandle.getContextPath())) {
Object error = SoulResultWrap.error(SoulResultEnum.CONTEXT_PATH_ERROR.getCode(), SoulResultEnum.CONTEXT_PATH_ERROR.getMsg(), null);
return WebFluxResultUtils.result(exchange, error);
}
this.buildContextPath(soulContext, contextMappingHandle);
return chain.execute(exchange);
}

@Override
public int getOrder() {
return PluginEnum.CONTEXTPATH_MAPPING.getCode();
}

@Override
public String named() {
return PluginEnum.CONTEXTPATH_MAPPING.getName();
}

@Override
public Boolean skip(final ServerWebExchange exchange) {
final SoulContext body = exchange.getAttribute(Constants.CONTEXT);
return Objects.equals(Objects.requireNonNull(body).getRpcType(), RpcTypeEnum.DUBBO.getName());
}

private void buildContextPath(final SoulContext context, final ContextMappingHandle handle) {
context.setContextPath(handle.getContextPath());
if (!StringUtils.isBlank(handle.getRealUrl())) {
log.info("context path mappingPlugin replaced old :{} , real:{}", context.getRealUrl(), handle.getRealUrl());
context.setRealUrl(handle.getRealUrl());
return;
}
Optional<String> optional = Arrays.stream(context.getPath()
.split(handle.getContextPath()))
.reduce((first, last) -> last);
optional.ifPresent(context::setRealUrl);
}

doExecute 方法的入参中, selector 和 rule 都是根据 soul 网关中接口路由匹配之后的选择器和规则信息, rule 规则中包含了路径决策之后的插件处理信息 PluginHandle, 是一个 JSON 字符串, 所以在 doExecute
方法中我们能看到有转换逻辑将字符串信息 rule.getHandle() 转换为 ContextMappingHandle.class 对象, ContextMappingHandle 对象的定义如下:

public class ContextMappingHandle {

private String realUrl;

private String contextPath;
}

doExecute 方法的逻辑中:

  • 如果插件处理信息 contextMappingHandle 不存在或者 contextMappingHandle 中的 contextPath 信息为空(按照相应的类型去判断, 对象是否为 null, 字符串是否为空), 则结束当前插件的处理, 继续插件链中下一个插件的处理。
  • 如果网关请求上下文 SoulContext 中的请求路径信息没有以 contextMappingHandle 中的 contextPath 开头 (页面上维护 ContextPath 的时候会约束以 / 开头), 以结果为请求异常结束该次请求的调用。
  • 执行构造请求 ContextPath 方法 buildContextPath:
    • contextMappingHandle 设置到 SoulContext 中。
    • 如果 contextMappingHandle 中请求真实的 url (realUrl) 的值不为空, 则将 contextMappingHandlerealUrl 的值设置到 SoulContext 实例的 realUrl 字段当中, 结束 buildContextPath 方法。
    • contextMappingHandle 中的 contextPath 字段信息分割 SoulContext 中的请求路径信息,将不包含 contextMappingHandle 中的 contextPath 字段信息的路径信息设置到 SoulContext 实例的 realUrl 字段当中, 结束 buildContextPath 方法。
  • 结束当前插件的处理,继续插件链中下一个插件的逻辑调用。

skip 方法是 SoulPlugin 接口中定义的方法, 用来标识一个插件链的处理过程中是否可以跳过一个插件的处理, 在 soul 网关插件链的执行过程中调用,可以看到 ContextPathMappingPluginskip 方法的实现: 如果 SoulContext 中 RpcType 是 dubbo,
则在网关请求的插件链处理过程中跳过该插件的处理。

总结

我理解 Soul 网关中的 ContextPathMappingPlugin 定义了一种隔离 API 接口的一种粒度,按照定义好的插件处理信息提供了一种转换 soul 网关请求中 ContextPath 的能力。

ReWritePlugin 源码分析

源码分析

RewritePlugin 继承自 AbstractSoulPlugin,
和之前继承自 AbstractPlugin 的插件实现的逻辑一样, ReWritePlugin 的主要实现的逻辑在 doExecute 方法中:

@Override
protected Mono<Void> 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);
}

@Override
public Boolean skip(final ServerWebExchange exchange) {
final SoulContext body = exchange.getAttribute(Constants.CONTEXT);
return Objects.equals(Objects.requireNonNull(body).getRpcType(), RpcTypeEnum.DUBBO.getName());
}

@Override
public String named() {
return PluginEnum.REWRITE.getName();
}

@Override
public int getOrder() {
return PluginEnum.REWRITE.getCode();
}

doExecute 方法的入参中, selector 和 rule 都是根据 soul 网关中接口路由匹配之后的选择器和规则信息, rule 规则中包含了路径决策之后的插件处理信息 PluginHandle, 是一个 JSON 字符串, 所以在 doExecute
方法中我们能看到有转换逻辑将字符串信息 rule.getHandle() 转换为 RewriteHandle.class 对象, RewriteHandle 对象的定义如下:

@Data
public class RewriteHandle {

private String rewriteURI;
}

rewriteURI 标记改写过后的请求 URI, RewritePlugindoExecute 方法的处理流程如下:

  • 如果插件处理信息 rewriteHandle 不存在或者 rewriteHandle 中的 rewriteURI 信息为空(按照相应的类型去判断, 对象是否为 null, 字符串是否为空), 则结束当前插件的处理, 继续插件链中下一个插件的处理。
  • 如果网关请求上下文 SoulContext 中的请求路径信息没有以 contextMappingHandle 中的 contextPath 开头 (页面上维护 ContextPath 的时候会约束以 / 开头), 以结果为请求异常结束该次请求的调用。
  • rewriteHandle 中的 rewriteURI 字段的值放入到 ServerWebExchange 的参数 Map 中
  • 结束当前插件的处理,继续插件链中下一个插件的逻辑调用。

RewritePluginskip 方法和 ContextPathMappingPluginskip 方法处理逻辑一样: 如果 SoulContext 中 RpcType 是 dubbo,
则在网关请求的插件链处理过程中跳过该插件的处理。

总结

今天学习的还是 soul 网关插件链处理中前置请求的插件的源码, 属于 Web 请求中常见的请求路径处理功能。

文章作者: David Liu
文章链接: https://davidliu.now.sh/2021/02/01/soul_plugin_source_discoveryIII/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 David Liu's Blog