Scenes
At the service gateway level, you need to print out the user's request body and other parameters each time. The gateway uses Reactor responsive programming, which is somewhat different from the Zuul gateway's way of obtaining streams.
However, the basic idea is the same. The body stream is read in the filter and then cached back. Because the body stream, the framework only allows to read it once by default.
Ideas
1. Add a filter to intercept a request
GatewayConfig.java
Add a configuration class, configure a high-priority filter, and inject a PayloadServerWebExchangeDecorator
class that wraps request and response.
package com.demo.gateway2x.config;
import com.demo.gateway2x.decorator.PayloadServerWebExchangeDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.WebFilter;
@Configuration
public class GatewayConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) //过滤器顺序
public WebFilter webFilter() {
return (exchange, chain) -> chain.filter(new PayloadServerWebExchangeDecorator(exchange));
}
}
PayloadServerWebExchangeDecorator.java
In this class, we have implemented the framework ServerWebExchangeDecorator
class and injected two custom classes at the same time, PartnerServerHttpRequestDecorator
and PartnerServerHttpResponseDecorator
,
These two classes are used to intercept requests and responses later.
package com.demo.gateway2x.decorator;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
public class PayloadServerWebExchangeDecorator extends ServerWebExchangeDecorator {
private PartnerServerHttpRequestDecorator requestDecorator;
private PartnerServerHttpResponseDecorator responseDecorator;
public PayloadServerWebExchangeDecorator(ServerWebExchange delegate) {
super(delegate);
requestDecorator = new PartnerServerHttpRequestDecorator(delegate.getRequest());
responseDecorator = new PartnerServerHttpResponseDecorator(delegate.getResponse());
}
@Override
public ServerHttpRequest getRequest() {
return requestDecorator;
}
@Override
public ServerHttpResponse getResponse() {
return responseDecorator;
}
}
2. Intercept the request once when the request enters
PartnerServerHttpRequestDecorator.java
This class is implemented ServerHttpRequestDecorator
, and in the constructor, reactive programming is used, and the method of printing log is called. Pay attention Mono<DataBuffer> mono = DataBufferUtils.join(flux);
,
Here, Flux is merged into a Mono, because if you don’t do this, the body content will be too much and will be printed in sections. This is an important point.
After printing and RequestParamsHandle.chain
printing the log, we returned another one dataBuffer
for downward transfer, otherwise it dataBuffer
cannot be used after being read once.
package com.demo.gateway2x.decorator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static reactor.core.scheduler.Schedulers.single;
@Slf4j
public class PartnerServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private Flux<DataBuffer> body;
public PartnerServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
Flux<DataBuffer> flux = super.getBody();
if (ParamsUtils.CHAIN_MEDIA_TYPE.contains(delegate.getHeaders().getContentType())) {
Mono<DataBuffer> mono = DataBufferUtils.join(flux);
body = mono.publishOn(single()).map(dataBuffer -> RequestParamsHandle.chain(delegate, log, dataBuffer)).flux();
} else {
body = flux;
}
}
@Override
public Flux<DataBuffer> getBody() {
return body;
}
}
RequestParamsHandle.java
This class is mainly used to read dataBuffer
and do log printing processing, and it can also be used for other purposes such as parameter verification.
package com.demo.gateway2x.decorator;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
public class RequestParamsHandle {
public static <T extends DataBuffer> T chain(ServerHttpRequest delegate, Logger log, T buffer) {
ParamsUtils.BodyDecorator bodyDecorator = ParamsUtils.buildBodyDecorator(buffer);
// 参数校验 和 参数打印
log.info("Payload: {}", JSON.toJSONString(validParams(getParams(delegate, bodyDecorator.getBody()))));
return (T) bodyDecorator.getDataBuffer();
}
public static Map<String,Object> getParams(ServerHttpRequest delegate, String body) {
// 整理参数
Map<String,Object> params = new HashMap<>();
if (delegate.getQueryParams() != null) {
params.putAll(delegate.getQueryParams());
}
if (!StringUtils.isEmpty(body)) {
params.putAll(JSON.parseObject(body));
}
return params;
}
public static Map<String,Object> validParams(Map<String,Object> params) {
// todo 参数校验
return params;
}
}
3. When the result is returned, intercept the response
PartnerServerHttpResponseDecorator.java
This class is the same as the request above, intercepting the response stream and recording it.
package com.demo.gateway2x.decorator;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static reactor.core.scheduler.Schedulers.single;
@Slf4j
public class PartnerServerHttpResponseDecorator extends ServerHttpResponseDecorator {
PartnerServerHttpResponseDecorator(ServerHttpResponse delegate) {
super(delegate);
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return super.writeAndFlushWith(body);
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
final MediaType contentType = super.getHeaders().getContentType();
if (ParamsUtils.CHAIN_MEDIA_TYPE.contains(contentType)) {
if (body instanceof Mono) {
final Mono<DataBuffer> monoBody = (Mono<DataBuffer>) body;
return super.writeWith(monoBody.publishOn(single()).map(dataBuffer -> ResponseParamsHandle.chain(log, dataBuffer)));
} else if (body instanceof Flux) {
Mono<DataBuffer> mono = DataBufferUtils.join(body);
final Flux<DataBuffer> monoBody = mono.publishOn(single()).map(dataBuffer -> ResponseParamsHandle.chain(log, dataBuffer)).flux();
return super.writeWith(monoBody);
}
}
return super.writeWith(body);
}
}
ResponseParamsHandle.java
Log printing of response stream
package com.demo.gateway2x.decorator;
import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;
public class ResponseParamsHandle {
public static <T extends DataBuffer> T chain(Logger log, T buffer) {
ParamsUtils.BodyDecorator bodyDecorator = ParamsUtils.buildBodyDecorator(buffer);
// 参数校验 和 参数打印
log.info("Payload: {}", bodyDecorator.getBody());
return (T) bodyDecorator.getDataBuffer();
}
}
The following is the actual operation, sending an http request:
Console log results:
github source address: https://github.com/qiaomengnan16/gateway-2x-log-demo
to sum up
The way gateway and zuul print parameters are the same, but the gateway uses reactor, which is slightly different from zuul's direct reading stream. What you need to know here is that Flux needs to be converted to Mono. If you don’t convert it, it’s easy to divide it. Batch printing.
Refer to the following blogs:
Customize the Spring Webflux filter to solve the problem that the request body can only be obtained once: https://my.oschina.net/junjunyuanyuankeke/blog/2253493
SpringCloud Gateway obtains the post request body (request body): https://blog.51cto.com/thinklili/2329184
How to get string/object from Mono stream in Reactive Java? : Https://www.bilibili.com/read/cv5787745
How to correctly read Flux and convert it to a single inputStream:
https://stackoverflow.com/questions/46460599/how-to-correctly-read-fluxdatabuffer-and-convert-it-to-a-single-inputstream
Only one connection receive subscriber allowed solution ideas: https://blog.csdn.net/weixin_40899682/article/details/82784242