Param-Mapping插件源码分析
开始前,可以参考 这篇文章 运行shenyu网关
#
正文先看一下这个插件的结构,如下图。
猜测:handler是用来做数据同步的;strategy中文意思是策略,可能是对各种请求体做了适配,应该是这个插件的重点;ParamMappingPlugin
应该是 ShenyuPlugin
的实现。
首先,看一下 ParamMappingPlugin
,里面主要是对 doExecute
方法的重写。
public Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) { ... // paramMappingHandle判断是否为空 // 根据首部行中的contentType确定请求体类型 HttpHeaders headers = exchange.getRequest().getHeaders(); MediaType contentType = headers.getContentType(); // * return match(contentType).apply(exchange, chain, paramMappingHandle);}
match方法是根据contentType返回对应的
Operator
private Operator match(final MediaType mediaType) { if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) { return operatorMap.get(MediaType.APPLICATION_JSON.toString()); } else if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) { return operatorMap.get(MediaType.APPLICATION_FORM_URLENCODED.toString()); } else { return operatorMap.get(Constants.DEFAULT); }}
从match方法的代码可以看出,目前有
DefaultOperator
、FormDataOperator
、JsonOperator
三种,支持x-www-form-urlencoded
和json
两种格式的请求体。
那么我们就来看一下上面三种Operator究竟是怎么样的吧。
#
一、DefaultOperator虚晃一枪,它的apply方法只是继续执行插件链,并没有实质功能。当请求体没有匹配到Operator时,就会通过 DefaultOperator
跳过。
#
二、FormDataOperator这个类是用来处理 x-www-form-urlencoded
格式的请求体的。
主要是看apply方法,但是这个apply方法长得有点奇怪。
public Mono<Void> apply(final ServerWebExchange exchange, final ShenyuPluginChain shenyuPluginChain, final ParamMappingHandle paramMappingHandle) { return exchange.getFormData() .switchIfEmpty(Mono.defer(() -> Mono.just(new LinkedMultiValueMap<>()))) .flatMap(multiValueMap -> { ... });}
省略号中的代码是对请求体的处理,如下。
// 判空if (Objects.isNull(multiValueMap) || multiValueMap.isEmpty()) { return shenyuPluginChain.execute(exchange);}// 将form-data转化成jsonString original = GsonUtils.getInstance().toJson(multiValueMap);LOG.info("get from data success data:{}", original);// *修改请求体*String modify = operation(original, paramMappingHandle);if (StringUtils.isEmpty(modify)) { return shenyuPluginChain.execute(exchange);}...// 将修改后的json,转换成LinkedMultiValueMap。注意一下这一行,后面会提到!LinkedMultiValueMap<String, String> modifyMap = GsonUtils.getInstance().toLinkedMultiValueMap(modify);...final BodyInserter bodyInserter = BodyInserters.fromValue(modifyMap);...// 修改exchange中的请求体,然后继续执行插件链return bodyInserter.insert(cachedBodyOutputMessage, new BodyInserterContext()) .then(Mono.defer(() -> shenyuPluginChain.execute(exchange.mutate() .request(new ModifyServerHttpRequestDecorator(httpHeaders, exchange.getRequest(), cachedBodyOutputMessage)) .build()) )).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> release(cachedBodyOutputMessage, throwable));
PS: 省略的部分是设置请求头等操作。
上面比较重要的应该是打星的修改请求体,也就是 operation
方法的调用。这里因为参数类型的原因,会先调用 Operator
接口的默认方法(而不是 FormDataOperator
重写的)。
default String operation(final String jsonValue, final ParamMappingHandle paramMappingHandle) { DocumentContext context = JsonPath.parse(jsonValue); // 调用重写的operation方法,添加addParameterKey operation(context, paramMappingHandle); // 对设置的replacedParameterKey进行替换 if (!CollectionUtils.isEmpty(paramMappingHandle.getReplaceParameterKeys())) { paramMappingHandle.getReplaceParameterKeys().forEach(info -> { context.renameKey(info.getPath(), info.getKey(), info.getValue()); }); } // 对设置的removeParameterKey进行删除 if (!CollectionUtils.isEmpty(paramMappingHandle.getRemoveParameterKeys())) { paramMappingHandle.getRemoveParameterKeys().forEach(info -> { context.delete(info); }); } return context.jsonString();}
梳理下来可以发现,这里引入的json工具JsonPath使得请求体的加工变得简单、清晰很多。
另外,我们可以注意到 FormDataOperator
重写了 operation(DocumentContext, ParamMappingHandle)
方法。
为什么要重写呢? 接口中有对应处理addParameterKey的默认方法啊。
// Operator接口中的默认方法default void operation(final DocumentContext context, final ParamMappingHandle paramMappingHandle) { if (!CollectionUtils.isEmpty(paramMappingHandle.getAddParameterKeys())) { paramMappingHandle.getAddParameterKeys().forEach(info -> { context.put(info.getPath(), info.getKey(), info.getValue()); //不同之处 }); }}
// FormDataOperator重写的方法@Overridepublic void operation(final DocumentContext context, final ParamMappingHandle paramMappingHandle) { if (!CollectionUtils.isEmpty(paramMappingHandle.getAddParameterKeys())) { paramMappingHandle.getAddParameterKeys().forEach(info -> { context.put(info.getPath(), info.getKey(), Arrays.asList(info.getValue())); }); }}
实际上,在 FormDataOperator#apply
中有这么一行(前面有提到):LinkedMultiValueMap<String, String> modifyMap = GsonUtils.getInstance().toLinkedMultiValueMap(modify);
这一行是将修改后的json转换成 LinkedMultiValueMap
,GsonUtils#toLinkedMultiValueMap
如下。
public LinkedMultiValueMap<String, String> toLinkedMultiValueMap(final String json) { return GSON.fromJson(json, new TypeToken<LinkedMultiValueMap<String, String>>() { }.getType());}
而 LinkedMultiValueMap
类中的属性 targetMap
定义为:private final Map<K, List<V>> targetMap
因此,json字符串中的value必须是列表形式的,不然Gson就会抛出转换错误的异常,这也就是为什么 FormDataOperator
要重写operator方法。
那么为什么要用 LinkedMultiValueMap
呢?
回到 FormDataOperator#apply
方法的第一行 exchange.getFormData
。而SpringMVC中,DefaultServerWebExchange#getFormData
的返回值类型就是 Mono<MultiValueMap<String, String>>
,而 LinkedMultiValueMap
是 MultiValueMap
的子类。并且,getFormData
方法就是针对 x-www-form-urlencoded
格式的请求体的。
#
三、JsonOperator显然,这个类是用来处理Json格式的请求体的。
public Mono<Void> apply(final ServerWebExchange exchange, final ShenyuPluginChain shenyuPluginChain, final ParamMappingHandle paramMappingHandle) { ServerRequest serverRequest = ServerRequest.create(exchange, MESSAGE_READERS); Mono<String> mono = serverRequest.bodyToMono(String.class).switchIfEmpty(Mono.defer(() -> Mono.just(""))).flatMap(originalBody -> { LOG.info("get body data success data:{}", originalBody); // 调用默认的operation方法修改请求体 String modify = operation(originalBody, paramMappingHandle); return Mono.just(modify); }); BodyInserter bodyInserter = BodyInserters.fromPublisher(mono, String.class); ... //处理首部行 CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); // 修改exchange中的请求体,然后继续执行插件链 return bodyInserter.insert(outputMessage, new BodyInserterContext()) .then(Mono.defer(() -> { ServerHttpRequestDecorator decorator = new ModifyServerHttpRequestDecorator(headers, exchange.getRequest(), outputMessage); return shenyuPluginChain.execute(exchange.mutate().request(decorator).build()); })).onErrorResume((Function<Throwable, Mono<Void>>) throwable -> release(outputMessage, throwable));}
JsonOperator
的处理流程与 FormDataOperator
大致类似。
#
总结最后,用一张图来简单总结一下。