【深入解析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

相关推荐
珍珠是蚌的眼泪2 小时前
微服务_入门2
网关·微服务·gateway·远程调用·feign
铁板鱿鱼1403 小时前
统一网关--gateway(仅供自己参考)
gateway
bug菌¹3 小时前
滚雪球学SpringCloud[4.2讲]: Zuul:Netflix API Gateway详解
spring·spring cloud·gateway
炸裂狸花猫1 天前
Kubernetes从零到精通(12-Ingress、Gateway API)
容器·kubernetes·gateway
云来喜3 天前
关于Spring Cloud Gateway中 Filters的理解
java·spring boot·gateway
小小小小关同学3 天前
【Gateway】网关服务快速上手
java·开发语言·gateway
小小小小关同学3 天前
【Gateway】Gateway Filter Factories
网络·gateway
两仪式quq3 天前
服务网关Gateway快速入门
服务器·网络·gateway
szc17675 天前
Gateway学习笔记
笔记·学习·gateway
huaqianzkh9 天前
什么是API网关(API Gateway)?
架构·gateway