spring cloud gateway 原始碼解析(3)記錄請求引數及返回的json

彈肖邦の夜曲發表於2019-01-19

(1)一開始搜尋讀取引數的文章,方法大多是從body裡讀一次DataBuffer,轉成字串,然後再把字串轉成DataBuffer重新放到body裡,如:
http://www.cnblogs.com/cafeba…

(2)

上面的方法我試過可以,但是Content-Type是multipart/form-data的時候會報錯
java.lang.IllegalStateException: Only one connection receive subscriber allowed.
不知道是不是我姿勢不對。
而且如果我們在代理到第三方服務的時候才讀取body,這樣效率應該會高一些
看NettyRoutingFilter類裡的filter方法
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ·······

        Mono<HttpClientResponse> responseMono = this.httpClient.request(method, url, req -> {
            final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnEach)
                    .headers(httpHeaders)
                    .chunkedTransfer(chunkedTransfer)
                    .failOnServerError(false)
                    .failOnClientError(false);

            if (preserveHost) {
                String host = request.getHeaders().getFirst(HttpHeaders.HOST);
                proxyRequest.header(HttpHeaders.HOST, host);
            }

            if (properties.getResponseTimeout() != null) {
                proxyRequest.context(ctx -> ctx.addHandlerFirst(
                        new ReadTimeoutHandler(properties.getResponseTimeout().toMillis(), TimeUnit.MILLISECONDS)));
            }

            return proxyRequest.sendHeaders() 
                    //這裡的是ReactorServerHttpRequest呼叫了getBody()方法,
                    //所以我們只要重寫ReactorServerHttpRequest的getbody方法,加上我們讀取的邏輯就行了,
                    //gateway給我們提供了裝飾類ServerHttpRequestDecorator,我們只需把過濾器的優先順序設定高於NettyRoutingFilter(實際他已經是倒數第二的優先順序了),且把ReactorServerHttpRequest替換成ServerHttpRequestDecorator就行了
                    .send(request.getBody().map(dataBuffer ->
                            ((NettyDataBuffer) dataBuffer).getNativeBuffer()));
        });
給個stackoverflow的虛擬碼
ServerHttpRequestDecorator decoratedRequest = new ServerHttpRequestDecorator(request) {
             @Override
            public Flux<DataBuffer> getBody() { 
                 StringBuilder sb=new StringBuilder();
                  return  super.getBody().map(dataBuffer -> {
                    // probably should reuse buffers 
                    byte[] content = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(content);
                    byte[] uppedContent = new String(content, Charset.forName("UTF-8")).toUpperCase().getBytes();
                    return bufferFactory.wrap(uppedContent);
                }) ; 
            }
};

            //再說說記錄返回的json,在NettyRoutingFilter類的154行,請求第三方服務反回後,
            // Defer committing the response until all route filters have run
            // Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
            //註釋說了把返回結果放進ServerWebExchange 的引數裡了,並且在NettyWriteResponseFilter讀取
            exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);


看NettyWriteResponseFilter的讀取程式碼,
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
       return chain.filter(exchange).then(Mono.defer(() -> {
           //拿出NettyRoutingFilter放進去的Response 
            HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);

            if (clientResponse == null) {//空的就不過直接到下一個過濾器
                return Mono.empty();
            }
            log.trace("NettyWriteResponseFilter start");
            ServerHttpResponse response = exchange.getResponse();

            NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
            //TODO: what if it`s not netty

            final Flux<NettyDataBuffer> body = clientResponse.receive()
                    .retain() //TODO: needed?
                    .map(factory::wrap);

            MediaType contentType = null;
            try {
                contentType = response.getHeaders().getContentType();
            } catch (Exception e) {
                log.trace("invalid media type", e);
            }
            //判斷contentType是不是text/event-stream或者application/stream+json
            //反正上面兩種型別的結果我們肯定不用記錄,所以我們重寫response的writeWith方法就好,
            //跟上面一樣gateway也提供了個裝飾器類ServerHttpResponseDecorator
            return (isStreamingMediaType(contentType) ?
                    response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
        }));
    }
上stackoverflow的虛擬碼
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.map(dataBuffer -> {
                    // probably should reuse buffers 
                    byte[] content = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(content);
                    byte[] uppedContent = new String(content, Charset.forName("UTF-8")).toUpperCase().getBytes();
                    return bufferFactory.wrap(uppedContent);
                }));
            }
            return super.writeWith(body); // if body is not a flux. never got there.
        }           
      }; 

參考:
https://stackoverflow.com/que…

該如果返回的資料長度很長的話,資料可能會讀不完全,如果出現讀取時擷取了中文字元,導致長度變多1位,進而json的右括號消失,也可參考下面連結
參考解決方案:
https://stackoverflow.com/que…

相關文章