在做第三方对接服务器时碰到的问题

Request的Body中的数据只能读取一次

读取数据

首先需要针对来自第三方的所有数据进行备份,在进行业务流程前,获取其入参并储存在数据库中。
这里采用了拦截器的方式,在业务执行前获取Request其中的入参。

    public static JSONObject handlerData(HttpServletRequest request) throws IOException, JSONException {
        StringBuffer sb = new StringBuffer();
        InputStream is = request.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
        String s = "";
        while ((s = br.readLine()) != null) {
            sb.append(s);
        }
        if (sb.toString().length() <= 0) {
            return null;
        } else {
            return JSONObject.parseObject(sb.toString());
        }
    }

重写Request

通过该方法可以通过IO流将request中的数据读取转化为JSONObject,如此就能对入参进行想要的处理了

然而实际运用中发现,经过拦截器后,到达Controller后通过@RequestBody转化而成的对象变成了空对象。
在查询了一些资料后,发现是由于默认的HttpServletRequest的对象仅能使用InputStream对象读取一次数据。

要解决这个问题需要对HttpServletRequest进行重写,重新包装一个可以重复读取的Request。

这里就要使用到HttpServletRequestWrapper这个类了,在继承此类后重写其中的getInputStream方法,并加入一个数据容器用以存储数据。代码如下所示


@Slf4j
public class ReReadRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 存储body数据
     */
    private final byte[] msgBody;

    public ReReadRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 将body数据存储到容器中
        String bodyStr = getBodyString(request);
        msgBody = bodyStr.getBytes("UTF-8");
    }

    /**
     * 获取请求Body
     *
     * @param request request
     * @return String
     */
    public String getBodyString(final ServletRequest request) {
        try {
            return inputStream2String(request.getInputStream());
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取请求Body
     *
     * @return String
     */
    public String getBodyString() {
        final InputStream inputStream = new ByteArrayInputStream(msgBody);

        return inputStream2String(inputStream);
    }

    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream inputStream
     * @return String
     */
    private String inputStream2String(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }

        return sb.toString();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream inputStream = new ByteArrayInputStream(msgBody);

        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

}

注:在设定编码格式的时候记得使用UTF-8,网上很多方法都没有设定编码格式。在本地测试时不会发现问题,但是当项目部署到Linux系统的服务器上时,会因为编码格式的错误导致汉字变为乱码。
该类错误在项目中的很多地方也会碰到,比如:

  • 使用MimeMessage发送邮件的时候,需要使用MimeUtility.encodeText来设置发件人和主题,否则部署到Linux上后邮件的发件人和主题会变成乱码。
  • 使用AES加解密时在Windows上会生成相同的秘钥,在Linux下生成变化的秘钥,导致解密失败。需要使用SecureRandom.getInstance之后再使用一次secureRandom.setSeed才能生成固定的秘钥。

此类问题在本地测试中无法发现,需要部署至测试服务器之后才能发现问题。

替换Request

新建一个Filter接口的过滤器实现类,在其中新建Request的时候新建我们重写过的ReReadRequestWrapper对象

public class ReplaceStreamFilter implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = new ReReadRequestWrapper((HttpServletRequest) request);
        chain.doFilter(requestWrapper, response);
    }

    @Override
    public void destroy() {
    
    }
}

配置过滤器注册,在这里由于仅有第三方接口有重复读取Request接口的需求,所以路径仅配置了/thirdApi/

@Configuration
public class FilterConfig {

    /**
     * 注册过滤器
     *
     * @return FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        System.out.println("覆盖request");
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(replaceStreamFilter());
        registration.addUrlPatterns("/thirdApi/*");
        registration.setName("replaceStreamFilter");
        return registration;
    }

    /**
     * 实例化StreamFilter
     *
     * @return Filter
     */
    @Bean(name = "replaceStreamFilter")
    public Filter replaceStreamFilter() {
        return new ReplaceStreamFilter();
    }
}

这样每次便可以解决Request无法重复读取的问题了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值