spring cloud gateway 源码解析(3)记录请求参数及返回的json

(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...

相关推荐