最近项目中使用spring cloud gateway作为网关,因为历史原因,有定制化的需求,要进行二次开发,碰到一些问题,在这先记录一下,以后有时间再详细补充。
这次是需要在请求的入口和出口分别打印报文信息。处理GET请求的时候还好,POST请求有时候参数放在requestBody中,而且2.X的版本之后,spring cloud使用 spring5 webflux方式编程,在filter中处理过一次的requestBody,下游订阅者无法接收,网上找了很多都是对某个具体路由在编码中进行配置,如下
.route("rewrite_request_upper", r -> r.host("*.rewriterequestupper.org")
.filters(f -> f.prefixPath("/httpbin")
.addResponseHeader("X-TestHeader", "rewrite_request_upper")
.modifyRequestBody(String.class, String.class,
(exchange, s) -> {
return Mono.just(s.toUpperCase()+s.toUpperCase());
})
).uri(uri)
)
而我的项目中使用了动态路由,未来路由的增减是很常见的事,这种方式没法对所有的路由进行请求拦截,很不灵活。
最后参看源码,发现了一个ModifyRequestBodyGatewayFilterFactory类,里边有对requestBody的处理逻辑,然后把源码照搬了过来,由于我只需要打印日志,稍微改一下就行,别的代码就保留了。
/**
* 参照 ModifyRequestBodyGatewayFilterFactory 写的一个处理 requestBody的filter
*
* @author huangting
*/
@Component
public class RequestHandlerFilter implements GlobalFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(RequestHandlerFilter.class);
private static final String METHOD_POST = "POST";
private static final String METHOD_GET = "GET";
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
//POST和get的处理逻辑不一样
if (METHOD_POST.equals(exchange.getRequest().getMethodValue())) {
ServerRequest serverRequest = new DefaultServerRequest(exchange);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(requestBody -> {
//打印请求报文
logRequestLog(request, requestBody);
return Mono.just(requestBody);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
// the new content type will be computed by bodyInserter
// and then set in the request decorator
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
// .log("modify_request", Level.INFO)
.then(Mono.defer(() -> {
ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
return chain.filter(exchange.mutate().request(decorator).build());
}));
} else {
//打印请求报文
logRequestLog(request, null);
chain.filter(exchange);
}
return chain.filter(exchange);
}
ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
return new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
// TODO: this causes a 'HTTP/1.1 411 Length Required' // on
// httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
}
/**
* 打印请求日志
*
* @param request
*/
public void logRequestLog(ServerHttpRequest request, String requestBody) {
String requestParam = request.getQueryParams().toString();
logger.info("请求报文 URL:{},Method:{},headers:{},param:{},requestbody:{}", request.getURI().getPath(), request.getMethod(), request.getHeaders(), requestParam, requestBody);
}
@Override
public int getOrder() {
// -1 is response write filter, must be called before that
return -3;
}
}
需要注意的是 ,我的spring cloud 是 2.1.3,Greenwich.SR3的版本,CachedBodyOutputMessage 和 DefaultServerRequest这里两个类的权限变成了spring私有的了,需要把他们copy出来作为自己项目中的类,以上的代码有用到