Spring Cloud Gateway 修改请求体、响应体
Spring cloud gateway 修改请求体、响应体、ModifyRequestBodyGatewayFilterFactory修改请求体、ModifyResponseBodyGatewayFilterFactory修改响应体
·
前言
例行每半年一次的工作轮换,接手了同事的网关服务
年底了工作不是很忙,看了下前人的代码,虽然都能读懂,但感觉应该可以再优雅一点
于是把网关的相关知识又翻阅了一下
官方资料
PS:这里如果按新方案调整的话,在结构上会看起来更清晰、可读性上会得到一定的提高
但学习研究是一回事,我肯定不会去直接修改前人的代码,我们还是要对运行稳定的项目持一点敬畏心,搞得不好,手一抖就是一个BUG
原方案 - 请求体修改
- 自定义 Filter 实现 GlobalFilter, Ordered 接口
- 重写 filter 方法,具体操作如下
// 这里的 exchange 是 过滤器入参 ServerWebExchange
ServerRequest sr = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
// 在 flatMap 方法里做请求体的修改
Mono modifiedBody = sr.bodyToMono(String.class).flatMap
原方案 - 响应体修改
- 自定义 Filter 实现 GlobalFilter, Ordered 接口
- 自定义 ResponseDecorator 继承 ServerHttpResponseDecorator 类,顶层是 ServerHttpResponse 接口
- 重写 ServerHttpResponseDecorator 的 writeWith 接口,以此实现 responseBody 的修改,加密加签
@Override
@SuppressWarnings(value = "unchecked")
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<DataBuffer> fluxBody = (Flux<DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 释放掉内存
DataBufferUtils.release(join);
String originalResponseBody = new String(content, StandardCharsets.UTF_8);
// 修改响应体
String updatedResponseBody = modifyBody(originalResponseBody);
getDelegate().getHeaders().setContentLength(updatedResponseBody.getBytes().length);
return bufferFactory().wrap(updatedResponseBody.getBytes());
}));
}
return super.writeWith(body);
}
- 在 filter 方法中,添加 response 装饰器
ResponseDecorator decorator = new ResponseDecorator(exchange.getResponse());
Mono<Void> filter = chain.filter(exchange.mutate().response(decorator).build());
return filter;
新方案 - 修改客户端请求体
创建请求过滤器 RequestModifyFilter
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.Objects;
/**
* 修改客户端请求体
* @author weiheng
* @date 2024-02-01
**/
@Component
@Slf4j
public class RequestModifyFilter implements GlobalFilter, Ordered {
@Resource
private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;
@Resource
private RequestRewriteFunction requestRewriteFunction;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
MediaType mediaType = request.getHeaders().getContentType();
String method = request.getMethodValue();
if (Objects.equals(HttpMethod.POST.name(), method) && MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
// 如果是POST请求,并且请求体是 application/json 格式
return modifyRequestBodyFilter.apply(
new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, requestRewriteFunction))
.filter(exchange, chain);
} else {
return filter(exchange, chain);
}
}
@Override
public int getOrder() {
return FilterPriorityConstant.REQUEST_BODY_DECRYPT_AND_SIGNATURE_VERIFICATION;
}
}
RequestRewriteFunction
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 修改请求体
* @author weiheng
* @date 2024-01-31
**/
@Slf4j
@Component
public class RequestRewriteFunction implements RewriteFunction<byte[], byte[]> {
/**
* Applies this function to the given arguments.
*
* @param serverWebExchange the first function argument
* @param bytes the second function argument
* @return the function result
*/
@Override
public Publisher<byte[]> apply(ServerWebExchange serverWebExchange, byte[] bytes) {
ServerHttpRequest request = serverWebExchange.getRequest();
HttpHeaders headers = request.getHeaders();
String requestId = headers.getFirst(HttpConstant.HEADER_REQUEST_ID);
JSONObject requestBody = JSON.parseObject(bytes);
log.info(">>>>> requestId[{}], 原始请求体[{}]", requestId, requestBody);
try {
// TODO 写自己的请求拦截业务,比如请求体的 【解密、验签】
// 测试,看业务服务的 controller类请求体中是否添加了该属性 - 自测OK
requestBody.put("traceId", RandomUtil.randomInt());
log.info(">>>>> requestId[{}], 修改后请求体[{}]", requestId, requestBody);
return Mono.just(requestBody.toString().getBytes());
} catch (Exception e) {
log.error(">>>>> requestId[{}], 修改请求体出错", requestId, e);
throw e;
}
}
}
新方案 - 修改服务端响应体
创建过滤器 ResponseModifyFilter
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.Objects;
/**
* 修改服务端响应体
* @author weiheng
* @date 2024-02-01
**/
@Component
@Slf4j
public class ResponseModifyFilter implements GlobalFilter, Ordered {
@Resource
private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter;
@Resource
private ResponseRewriteFunction responseRewriteFunction;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
MediaType mediaType = request.getHeaders().getContentType();
String method = request.getMethodValue();
if (Objects.equals(HttpMethod.POST.name(), method) && MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
// 如果是POST请求,并且请求体是 application/json 格式
return modifyResponseBodyFilter.apply(
new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, responseRewriteFunction))
.filter(exchange, chain);
} else {
return filter(exchange, chain);
}
}
@Override
public int getOrder() {
return FilterPriorityConstant.RESPONSE_BODY_ENCRYPT_AND_ADD_SIGNATURE;
}
}
ResponseRewriteFunction
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 修改响应体
* @author weiheng
* @date 2024-01-31
**/
@Slf4j
@Component
public class ResponseRewriteFunction implements RewriteFunction<byte[], byte[]> {
/**
* Applies this function to the given arguments.
*
* @param serverWebExchange the first function argument
* @param bytes the second function argument
* @return the function result
*/
@Override
public Publisher<byte[]> apply(ServerWebExchange serverWebExchange, byte[] bytes) {
ServerHttpRequest request = serverWebExchange.getRequest();
HttpHeaders headers = request.getHeaders();
String requestId = headers.getFirst(HttpConstant.HEADER_REQUEST_ID);
JSONObject responseBody = JSON.parseObject(bytes);
log.info(">>>>> requestId[{}], 原始响应体[{}]", requestId, responseBody);
try {
// TODO 在这里写自己的业务逻辑,比如响应体的 加密、加签
// 测试,看客户端拿到的响应体中,是否有该属性 - 亲测OK
responseBody.put("traceResponse", RandomUtil.randomInt(1000, 9999));
log.info(">>>>> requestId[{}], 修改后的响应体[{}]", requestId, responseBody);
return Mono.just(responseBody.toString().getBytes());
} catch (Exception e) {
log.error(">>>>> requestId[{}], 修改响应体出错", requestId, e);
throw e;
}
}
}
把所有过滤的优先级统一管理
自定义 FilterPriorityConstant 常量类
PS:项目中有好几个过滤器,原先都是在各自的类中定义的优先级,这里统一放到常量里进行管理
这里并未展示真实项目中的所有过滤器,仅作示例
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
/**
* 过滤器优先级定义
* @author weiheng
* @date 2024-01-31
**/
public class FilterPriorityConstant {
/**
* Useful constant for the highest precedence value.
* @see java.lang.Integer#MIN_VALUE
*/
public static int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* Useful constant for the lowest precedence value.
* @see java.lang.Integer#MAX_VALUE
*/
public static int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
/** 商户权限校验 - 根据header的商户ID做接口权限校验 */
public static int MERCHANT_AUTH_VALIDATE = 2;
/** 请求体解密验签 */
public static int REQUEST_BODY_DECRYPT_AND_SIGNATURE_VERIFICATION = 3;
/** 响应体加密加签 value: -2 */
public static int RESPONSE_BODY_ENCRYPT_AND_ADD_SIGNATURE = NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
以上请求体和响应体的修改,亲测OK
更多推荐
已为社区贡献1条内容
所有评论(0)