1. 程式人生 > 實用技巧 >spring-cloud-gateway獲取post請求body引數以及響應資料

spring-cloud-gateway獲取post請求body引數以及響應資料

@Component
@Slf4j
@AllArgsConstructor
public class HttpPostBodyFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        String method = request.getMethodValue();
        String contentType = request.getHeaders().getFirst("Content-Type");
        if ("POST".equals(method) && contentType.startsWith("multipart/form-data")){
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        try {
                            String bodyString = new String(bytes, "utf-8");
                            log.info(bodyString);
                            exchange.getAttributes().put("POST_BODY",bodyString);
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                        DataBufferUtils.release(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory()
                                    .wrap(bytes);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest)
                                        .build());
                    });
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -200;
    }
}

主要思路就是在優先順序最高的過濾器裡面,CacheBodyGlobalFilter這個全域性過濾器的目的就是把原有的request請求中的body內容讀出來,並且使用ServerHttpRequestDecorator這個請求裝飾器對request進行包裝,重寫getBody方法,並把包裝後的請求放到過濾器鏈中傳遞下去。這樣後面的過濾器中再使用exchange.getRequest().getBody()來獲取body時,實際上就是呼叫的過載後的getBody方法,獲取的最先已經快取了的body資料。這樣就能夠實現body的多次讀取了。

過濾器優先順序不一定是最高,但是要在要獲取body之前執行,然後後面在身份鑑定的等過濾器裡面,獲取到body

@Component
@Slf4j
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        return -2;
    }
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest 
= exchange.getRequest(); ServerHttpResponse originalResponse = exchange.getResponse(); //如果是post請求,將請求體取出來,再寫入 HttpMethod method = serverHttpRequest.getMethod(); //請求引數,post從請求裡獲取請求體 String requestBodyStr = HttpMethod.POST.equals(method) ? resolveBodyFromRequest(serverHttpRequest) : null; DataBufferFactory bufferFactory = originalResponse.bufferFactory(); ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { if (body instanceof Flux) { Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body; return super.writeWith(fluxBody.buffer().map(dataBuffers -> {//解決返回體分段傳輸 StringBuffer stringBuffer = new StringBuffer(); dataBuffers.forEach(dataBuffer -> { byte[] content = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(content); DataBufferUtils.release(dataBuffer); try { stringBuffer.append(new String(content, "utf-8")); } catch (Exception e) { log.error("--list.add--error", e); } }); String result = stringBuffer.toString(); //TODO,result就是response的值,想修改、檢視就隨意而為了 String url = serverHttpRequest.getPath().toString(); String urlParams = UrlUtil.getParamsByMap(serverHttpRequest.getQueryParams().toSingleValueMap()); JSONObject jsonObject = JSONObject.parseObject(result); log.info("請求長度:" + StringUtils.length(requestBodyStr) + ",返回data長度:" + StringUtils.length(jsonObject.getString("data"))); log.info("請求地址:【{}】請求引數:GET【{}】|POST:【\n{}\n】,響應資料:【\n{}\n】", url, urlParams, requestBodyStr, result); byte[] uppedContent = new String(result.getBytes(), Charset.forName("UTF-8")).getBytes(); originalResponse.getHeaders().setContentLength(uppedContent.length); return bufferFactory.wrap(uppedContent); })); } // if body is not a flux. never got there. return super.writeWith(body); } }; // replace response with decorator return chain.filter(exchange.mutate().response(decoratedResponse).build()); } /** * 從Flux<DataBuffer>中獲取字串的方法 * * @return 請求體 */ private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { //獲取請求體 Flux<DataBuffer> body = serverHttpRequest.getBody(); AtomicReference<String> bodyRef = new AtomicReference<>(); body.subscribe(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); DataBufferUtils.release(buffer); bodyRef.set(charBuffer.toString()); }); //獲取request body return bodyRef.get(); } }

這裡可以把請求地址、引數、body、響應資料一起打印出來,測試的post請求

請求引數有56.97kb,後端order-center的接收列印,把整個資料完全接收並返回出去

閘道器的列印請求引數資料

再來一個get請求的

其他的put、delete等請求均試過正常請求

用postman的壓測結果和對比,資料都完全正常,並且能通過json格式化,說明資料格式也保持了一致

程式碼還有很多可以優化改進的地方,根據自己的也無需求來,比如簽名、token等統一校驗處理