gateway修改响应报文,也不是一件容易的事,我们来看下如何简单地来修改返回报文
一、官方示例
先看看官方示例,如何修改一个响应报文
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri))
.build();
}
这种方式在定义Route的地方写,不太灵活。这种方式本质上就是调用下面的代码,实现的返回报文重写
public <T, R> GatewayFilterSpec modifyResponseBody(Class<T> inClass, Class<R> outClass,
RewriteFunction<T, R> rewriteFunction) {
return filter(getBean(ModifyResponseBodyGatewayFilterFactory.class)
.apply(c -> c.setRewriteFunction(inClass, outClass, rewriteFunction)));
}
其中最关键的就是ModifyResponseBodyGatewayFilterFactory了!
其中重写的逻辑,就是RewriteFunction了。里面就是我们重写返回报文的逻辑了。
重写报文的核心实现就是下面的代码了ModifyResponseBodyGatewayFilterFactory.writeWith,可以看出来,好像也不简单啊,如果我们想要重写返回报文,是可以参考下面的代码。
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Class inClass = config.getInClass();
Class outClass = config.getOutClass();
String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
// explicitly add it in this way instead of
// 'httpHeaders.setContentType(originalResponseContentType)'
// this will prevent exception in case of using non-standard media
// types like "Content-Type: image"
httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType);
ClientResponse clientResponse = prepareClientResponse(body, httpHeaders);
// TODO: flux or mono
Mono modifiedBody = extractBody(exchange, clientResponse, inClass)
.flatMap(originalBody -> config.getRewriteFunction().apply(exchange, originalBody))
.switchIfEmpty(Mono.defer(() -> (Mono) config.getRewriteFunction().apply(exchange, null)));
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,
exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
Mono<DataBuffer> messageBody = writeBody(getDelegate(), outputMessage, outClass);
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)
|| headers.containsKey(HttpHeaders.CONTENT_LENGTH)) {
messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount()));
}
// TODO: fail if isStreamingMediaType?
return getDelegate().writeWith(messageBody);
}));
}
二、自定义修改报文实现
想要自己修改返回报文,那是不是直接复制上面的ModifyResponseBodyGatewayFilterFactory.writeWith就可以了?这样好像也太复杂,里面的操作流、缓存的代码让人看不太明白。那有没有简单的办法呢?有的,那就是直接把我们修改报文委派给ModifyResponseBodyGatewayFilterFactory,我们只提供一个RewriteFunction的业务实现,就可以了。
2.1 微服务hello-service定义的后端接口如下:
@Controller
@Slf4j
public class ModifyBodyController {
@RequestMapping(value = "/modify-response/hello", method = RequestMethod.POST)
@ResponseBody
public Map<String, String> modifyResponse(@RequestBody Map<String, String> map) {
return map;
}
}
2.2 定义自己的Filter Bean
@Bean
public ModifyResponseBodyFilter modifyResponseBodyFilter(
ServerCodecConfigurer codecConfigurer, Set<MessageBodyDecoder> bodyDecoders,
Set<MessageBodyEncoder> bodyEncoders) {
return new ModifyResponseBodyFilter(bodyDecoders, bodyEncoders, codecConfigurer.getReaders());
}
这段代码,实际上参考了GatewayAutoConfiguration里面对ModifyResponseBodyGatewayFilterFactory的bean的定义。ModifyResponseBodyGatewayFilterFactory需要一系列的编码、解码工具,从容器中注入进来。
三、修改响应报文具体实现
gateway定义一个GlobalFilter,修改响应报文,将原来返回的Map中,添加一行数据【“hello”: “new response body insert!!”】
注意order需要定义为NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1,即在WRITE_RESPONSE_FILTER_ORDER之前。
public class ModifyResponseBodyFilter implements GlobalFilter, Ordered {
private final Gson gson = new Gson();
private final Set<MessageBodyDecoder> messageBodyDecoders;
private final Set<MessageBodyEncoder> messageBodyEncoders;
private final List<HttpMessageReader<?>> messageReaders;
public ModifyResponseBodyFilter(Set<MessageBodyDecoder> messageBodyDecoders, Set<MessageBodyEncoder> messageBodyEncoders, List<HttpMessageReader<?>> messageReaders) {
this.messageBodyDecoders = messageBodyDecoders;
this.messageBodyEncoders = messageBodyEncoders;
this.messageReaders = messageReaders;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ModifyResponseBodyGatewayFilterFactory.Config config = new ModifyResponseBodyGatewayFilterFactory.Config();
config.setInClass(String.class);
config.setOutClass(String.class);
config.setRewriteFunction(new RewriteFunction() {
@Override
public Object apply(Object o, Object o2) {
ServerWebExchange serverWebExchange = (ServerWebExchange) o;
String oldBody = (String) o2;
if (exchange.getRequest().getURI().getRawPath().contains("modify-response")) {
Map map = gson.fromJson(oldBody, Map.class);
map.put("hello", "new response body insert!!");
return Mono.just(gson.toJson(map));
}
return Mono.just(oldBody);
}
});
return new ModifyResponseBodyGatewayFilterFactory(messageReaders,messageBodyDecoders,messageBodyEncoders)
.apply(config).filter(exchange,chain);
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
}
测试接口
curl --location 'http://localhost:8080/hello-service/modify-response/hello' \
--header 'Content-Type: application/json' \
--data '{"aaa":"bbb"}'
返回报文如下
{
"aaa": "bbb",
"hello": "new response body insert!!"
}
可以看到,我们在原有报文的基础上,插入了【 “hello”: “new response body insert!!”】
本示例源码链接:https://gitee.com/syk1234/spring-cloud-new-demo.git