【深入解析spring cloud gateway】11 用最简单的方式修改gateway响应报文

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

你可能感兴趣的:(深入解析SpringCloud,Gateway,gateway)