自定义Spring Webflux 过滤器,解决请求body只能获取一次的问题

转发自私人博客https://www.jgayb.cn/?p=253

Webflux 和 servlet一样body只能获取一次,解决办法也是一样的,自定义请求包装类参考大神博客

1 背景

为了测试和调试方便打印请求和返回的报文信息。一般可以通过Aop拦截controller方法或增加过滤器打印报文信息解决。

1.1 Aop方法

使用Aop的拦截controller方法,是将方法的model对象参数转成json打印出来,因为是body字符串通过httpMessageConverter转成model对象,在这里打印的话就是又转回body字符串。有点浪费性能。

1.2 增加自定义过滤器的方法

自定义过滤器的话放在第一个过滤器,在转换成对象之前就打印,这样可以减少多余的性能消耗。

2 Webflux创建请求和返回的的装饰类

2.1 Request装饰类

创建包装类PartnerServerHttpRequestDecorator继承ServerHttpRequestDecorator,在含参构造放中打印请求url,query,headers和报文信息。

@Slf4j//lombok插件
public class PartnerServerHttpRequestDecorator extends ServerHttpRequestDecorator {

    private Flux body;

    PartnerServerHttpRequestDecorator(ServerHttpRequest delegate) {
        super(delegate);
        final String path = delegate.getURI().getPath();
        final String query = delegate.getURI().getQuery();
        final String method = Optional.ofNullable(delegate.getMethod()).orElse(HttpMethod.GET).name();
        final String headers = delegate.getHeaders().entrySet()
                .stream()
                .map(entry -> "            " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                .collect(Collectors.joining("\n"));
        final MediaType contentType = delegate.getHeaders().getContentType();
        if (log.isDebugEnabled()) {
            log.debug("\n" +
                    "HttpMethod : {}\n" +
                    "Uri        : {}\n" +
                    "Headers    : \n" +
                    "{}", method, path + (StringUtils.isEmpty(query) ? "" : "?" + query), headers);
        }
        Flux flux = super.getBody();
        if (LogUtils.legalLogMediaTypes.contains(contentType)) {
            body = flux
                    .publishOn(single()).map(dataBuffer -> LogUtils.loggingRequest(log, dataBuffer));
        } else {
            body = flux;
        }
    }

    @Override
    public Flux getBody() {
        return body;
    }

}

2.2 Response装饰类

创建响应装饰类PartnerServerHttpResponseDecorator继承ServerHttpResponseDecorator

@Slf4j
public class PartnerServerHttpResponseDecorator extends ServerHttpResponseDecorator {
    PartnerServerHttpResponseDecorator(ServerHttpResponse delegate) {
        super(delegate);
    }

    @Override
    public Mono writeAndFlushWith(Publisher> body) {
        return super.writeAndFlushWith(body);
    }

    @Override
    public Mono writeWith(Publisher body) {
        final MediaType contentType = super.getHeaders().getContentType();
        if (LogUtils.legalLogMediaTypes.contains(contentType)) {
            if (body instanceof Mono) {
                final Mono monoBody = (Mono) body;
                return super.writeWith(monoBody.publishOn(single()).map(dataBuffer -> LogUtils.loggingResponse(log, dataBuffer)));
            } else if (body instanceof Flux) {
                final Flux monoBody = (Flux) body;
                return super.writeWith(monoBody.publishOn(single()).map(dataBuffer -> LogUtils.loggingResponse(log, dataBuffer)));
            }
        }
        return super.writeWith(body);
    }

}

2.3 WebExchange装饰类

创建PayloadServerWebExchangeDecorator类继承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.4 打印日志工具类

@SuppressWarnings("WeakerAccess")
public class LogUtils {

    public static final List legalLogMediaTypes = Lists.newArrayList(MediaType.TEXT_XML,
            MediaType.APPLICATION_XML,
            MediaType.APPLICATION_JSON,
            MediaType.APPLICATION_JSON_UTF8,
            MediaType.TEXT_PLAIN,
            MediaType.TEXT_XML);

    @SuppressWarnings("unchecked")
    public static  T loggingRequest(Logger log, T buffer) {
        return logging(log, ">>>>>>>>>>", buffer);
    }

    public static  T loggingResponse(Logger log, T buffer) {
        return logging(log, "<<<<<<<<<<", buffer);
    }

    private static  T logging(Logger log, String inOrOut, T buffer) {
        try {
            InputStream dataBuffer = buffer.asInputStream();
            byte[] bytes = IOUtils.toByteArray(dataBuffer);
            NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
            if (log.isDebugEnabled()) {
                log.debug("\n" +
                        "{}Payload    : {}", inOrOut, new String(bytes));
            }
            DataBufferUtils.release(buffer);
            return (T) nettyDataBufferFactory.wrap(bytes);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }
}

3 Spring boot2 中的使用方法

在config类中增加一个bean即可

@Configuration
public class AppConfig {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE) //过滤器顺序
    public WebFilter webFilter() {
        return (exchange, chain) -> chain.filter(new PayloadServerWebExchangeDecorator(exchange));
    }

}

4 创建一个接口测试

4.1 测试接口

@RestController
@RequestMapping("/test")
@Profile({"dev", "test"})// 开发测试环境有效
public class TestController {

    @PostMapping
    public Mono> test(@RequestBody Map params) {
        params.put("success", true);
        return Mono.just(params);
    }
}

4.2 测试用例和结果

4.2.1 测试用例
curl -H "content-type: application/json" http://localhost:8088/test -d "{\"id\":111, \"username\":\"user\", \"password\":\"password\",\"age\":11}"
4.2.2 测试结果

响应信息

日志信息截图

5 其他代码

package com.quaerolife.machineremote.domain.filter;

import com.google.common.collect.Lists;
import io.netty.buffer.UnpooledByteBufAllocator;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.MediaType;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * Created by jone.wang on 2018/10/26.
 * Description:
 */
@SuppressWarnings("WeakerAccess")
public class LogUtils {

    public static final List legalLogMediaTypes = Lists.newArrayList(MediaType.TEXT_XML,
            MediaType.APPLICATION_XML,
            MediaType.APPLICATION_JSON,
            MediaType.APPLICATION_JSON_UTF8,
            MediaType.TEXT_PLAIN,
            MediaType.TEXT_XML);

    @SuppressWarnings("unchecked")
    public static  T loggingRequest(Logger log, T buffer) {
        return logging(log, ">>>>>>>>>>", buffer);
    }

    public static  T loggingResponse(Logger log, T buffer) {
        return logging(log, "<<<<<<<<<<", buffer);
    }

    private static  T logging(Logger log, String inOrOut, T buffer) {
        try {
            InputStream dataBuffer = buffer.asInputStream();
            byte[] bytes = IOUtils.toByteArray(dataBuffer);
            NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
            if (log.isDebugEnabled()) {
                log.debug("\n" +
                        "{}Payload    : {}", inOrOut, new String(bytes));
            }
            DataBufferUtils.release(buffer);
            return (T) nettyDataBufferFactory.wrap(bytes);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }
}

转载于:https://my.oschina.net/junjunyuanyuankeke/blog/2253493

你可能感兴趣的:(自定义Spring Webflux 过滤器,解决请求body只能获取一次的问题)