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

gateway修改响应报文,也不是一件容易的事,我们来看下如何简单地来修改返回报文

一、官方示例

先看看官方示例,如何修改一个响应报文

java 复制代码
@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的地方写,不太灵活。这种方式本质上就是调用下面的代码,实现的返回报文重写

java 复制代码
	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,可以看出来,好像也不简单啊,如果我们想要重写返回报文,是可以参考下面的代码。

java 复制代码
		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定义的后端接口如下:

java 复制代码
@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

java 复制代码
    @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之前。

java 复制代码
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;
    }
}

测试接口

bash 复制代码
curl --location 'http://localhost:8080/hello-service/modify-response/hello' \
--header 'Content-Type: application/json' \
--data '{"aaa":"bbb"}'

返回报文如下

json 复制代码
{
    "aaa": "bbb",
    "hello": "new response body insert!!"
}

可以看到,我们在原有报文的基础上,插入了【 "hello": "new response body insert!!"】

本示例源码链接:https://gitee.com/syk1234/spring-cloud-new-demo.git

相关推荐
Java后端的Ai之路2 天前
【Spring全家桶】-一文弄懂Spring Cloud Gateway
java·后端·spring cloud·gateway
研究司马懿6 天前
【云原生】Gateway API介绍
云原生·gateway
研究司马懿6 天前
【云原生】Gateway API路由、重定向、修饰符等关键操作
云原生·gateway
研究司马懿6 天前
【云原生】初识Gateway API
云原生·gateway
七夜zippoe7 天前
API网关设计模式实战 Spring Cloud Gateway路由过滤限流深度解析
java·设计模式·gateway·路由·api网关
汪碧康7 天前
一文讲解kubernetes的gateway Api的功能、架构、部署、管理及使用
云原生·容器·架构·kubernetes·gateway·kubelet·xkube
大佐不会说日语~7 天前
Docker Compose 部署 Spring Boot 应用 502 Bad Gateway 问题排查与解决
spring boot·docker·gateway·maven·故障排查
Dontla9 天前
Kubernetes流量管理双雄:Ingress与Gateway API解析(Nginx与Ingress与Gateway API的关系)
nginx·kubernetes·gateway
JavaLearnerZGQ9 天前
Gateway网关将登录用户信息传递给下游微服务(完整实现方案)
微服务·架构·gateway
Ares-Wang10 天前
网络》》BGP Border Gateway Protocol,边界网关协议
网络·gateway