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); } 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) 的值不为空, 则将 contextMappingHandle 的 realUrl 的值设置到 SoulContext 实例的 realUrl 字段当中, 结束 buildContextPath 方法。
以 contextMappingHandle 中的 contextPath 字段信息分割 SoulContext 中的请求路径信息,将不包含 contextMappingHandle 中的 contextPath 字段信息的路径信息 设置到 SoulContext 实例的 realUrl 字段当中, 结束 buildContextPath 方法。
结束当前插件的处理,继续插件链中下一个插件的逻辑调用。
skip 方法是 SoulPlugin 接口中定义的方法, 用来标识一个插件链的处理过程中是否可以跳过一个插件的处理, 在 soul 网关插件链的执行过程中调用,可以看到 ContextPathMappingPlugin 的 skip 方法的实现: 如果 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 , RewritePlugin 的 doExecute 方法的处理流程如下:
如果插件处理信息 rewriteHandle 不存在或者 rewriteHandle 中的 rewriteURI 信息为空(按照相应的类型去判断, 对象是否为 null, 字符串是否为空) , 则结束当前插件的处理, 继续插件链中下一个插件的处理。
如果网关请求上下文 SoulContext 中的请求路径信息没有以 contextMappingHandle 中的 contextPath 开头 (页面上维护 ContextPath 的时候会约束以 / 开头 ), 以结果为请求异常 结束该次请求的调用。
rewriteHandle 中的 rewriteURI 字段的值放入到 ServerWebExchange 的参数 Map 中
结束当前插件的处理,继续插件链中下一个插件的逻辑调用。
RewritePlugin 的 skip 方法和 ContextPathMappingPlugin 的 skip 方法处理逻辑一样: 如果 SoulContext 中 RpcType 是 dubbo, 则在网关请求的插件链处理过程中跳过该插件的处理。
总结 今天学习的还是 soul 网关插件链处理中前置请求的插件的源码, 属于 Web 请求中常见的请求路径处理功能。