SpringBoot通過請求對象獲取輸入流無數據


 

在做文件上傳通過post方式進行binary上傳開發的時候能正常處理,老的設備采用同樣的協議進行傳輸的時候遇到了奇怪的事情,在SpringBoot的Controller里面直接使用HttpServletRequest的getInputStream()方法的時候獲得的輸入流無數據,通過getContentLength()獲得內容長度的時候又是有值的,但是寫入文件時通過UltraEdit打開16進制發現全部是0,同時使用DataInputStream解析ServletInputStream流報EOFException異常,接下來開始排查:

1、首先定位EOFException問題導致原因:https://www.cnblogs.com/firstdream/p/9591126.html

分析完后和我們現有問題沒有什么關系,繼續排查別的方向。

2、通過tcpdump抓包

tcpdump -i eth0 -tnn dst port 80 -w 1234.pcap

 然后通過wireshark打開終於發現問題所在,接入如下:

 

 問題出現了:由於之前老的協議采用application/x-www-form-urlencoded,之前定義協議的使用說過要采用oct-stream方式,結果前端開發人員沒有注意而采用默認application/x-www-form-urlencoded,在springmvc框架下並沒有影響。

總結:定義協議的時候一定確認請求類型和邊界。

問題解決:
出現這種情況,首先懷疑輸入流已經被使用了,由於請求輸入流是不帶緩存的,使用一次后流就無效了,通常觸發解析輸入流就是調用了getParameter()等方法,經過檢查代碼確認沒有做過相關處理,所以懷疑SpringBoot底層做了處理。
查了一下SpringBoot的自動裝配配置,在WebMvcAutoConfiguration中初始化了一個OrderedHiddenHttpMethodFilter,默認這個過濾器是生效的,相關代碼如下:

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

這是老版本的配置:

新版本:

@Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},
        matchIfMissing = false
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

默認開啟的過濾器部分:

@Bean
    @ConditionalOnMissingBean({FormContentFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.formcontent.filter",
        name = {"enabled"},
        matchIfMissing = true
    )
    public OrderedFormContentFilter formContentFilter() {
        return new OrderedFormContentFilter();
    }

  

OrderedHiddenHttpMethodFilter繼承了OrderedHiddenHttpMethodFilter,而OrderedHiddenHttpMethodFilter又繼承了HiddenHttpMethodFilter,在該類的doFilterInternal()方法中發現有對參數做處理,相關代碼如下:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
    HttpServletRequest requestToUse = request;

    if ("POST".equals(request.getMethod()) && 
    request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        String paramValue = request.getParameter(this.methodParam);
        if (StringUtils.hasLength(paramValue)) {
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            if (ALLOWED_METHODS.contains(method)) {
                requestToUse = new HttpMethodRequestWrapper(request, method);
            }
        }
    }
    filterChain.doFilter(requestToUse, response);
}

 至此就可以定位問題的所在了,找到了問題下面就來看看如何解決。

網上流傳比較多的三種解決方案:
方案一:禁用默認的過濾器
SpringBoot在自動裝配的時候注入了OrderedHiddenHttpMethodFilter,如果我們不需要該功能,在配置文件中顯示的將其設置為false。配置如下:

spring.mvc.hiddenmethod.filter.enabled=false

和方案三一樣,springboot是為了解決rest開發過程中兼容put、delete等接口使用,新版本鍾默認不啟用改過濾器,只有標識為true才生效,所以此處置為false無意義。

方案二:使用@RequestBody注解
在需要獲取請求輸入流的方法上添加字節數組的參數,並添加@RequestBody注解,示例代碼如下:

@RequestMapping("/**")
public 返回值 方法名(@RequestBody byte[] body) {
   // ...
}

此方案也不是很靠譜,針對獲取流的應用場景無意義。

方案三:自定義HiddenHttpMethodFilter過濾器
參考代碼如下:

@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter(){
        @Override
        protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
            filterChain.doFilter(request, response);
        }
    };
}

 跟進源碼其實可以發現,在springboot中不只有此一處使用了,所以這個方案不能很好解決此類問題,和方案一中類似,只有配置true才生效。

后記:

不過遺憾的是上訴三個方案都不生效,最終通過重寫OrderedHiddenHttpMethodFilter並添加業務邏輯后完成(暫時解決)。

完美解決方案如下,核心是重寫HttpServletRequestWrapper已實現getInputStream多次利用的特點,對於后續的過濾器等處理也不受影響。

代碼如下:

1、RequestWrapper.java

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * @date 2021/5/15
 * 類說明:
 */
public class RequestWrapper extends HttpServletRequestWrapper {
    //參數字節數組
    private byte[] buffer;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        InputStream is = request.getInputStream();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] tmp = new byte[1024];
        int read = 0;
        while ((read = is.read(tmp)) > 0){
            os.write(tmp,0,read);
        }
        this.buffer = os.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new BufferServletInputStream(this.buffer);
    }
}
class BufferServletInputStream extends ServletInputStream{

    private ByteArrayInputStream inputStream;

    public BufferServletInputStream(byte[] buffer) {
        this.inputStream = new ByteArrayInputStream(buffer);
    }

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

 2、重寫Filter,RequestWrapperFilter.java

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @date 2021/5/15
 * 類說明:
 */
public class RequestWrapperFilter implements Filter {
    private FilterConfig filterConfig = null;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException, IOException {
        RequestWrapper request = new RequestWrapper((HttpServletRequest) servletRequest);
        filterChain.doFilter(request, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfiguration) throws ServletException {
        this.filterConfig = filterConfiguration;
    }

    @Override
    public void destroy() {
        this.filterConfig = null;
    }
}

 3、添加@Configyration

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @date 2021/5/14
 * 類說明:多媒體上傳配置
 */
@Configuration
public class MultipartResolverConfig {

    private static final Logger logger = LoggerFactory.getLogger(MultipartResolverConfig.class);

    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(10485760);
        resolver.setDefaultEncoding("UTF-8");
        return resolver;
    }

    @Bean(name = "uploadFileFilter")
    public RequestWrapperFilter uploadFileFilter() {
        return new RequestWrapperFilter();
    }

    @Bean(name = "uploadFileBean")
    public FilterRegistrationBean uploadFileBean() {
        FilterRegistrationBean registration = new FilterRegistrationBean(uploadFileFilter());
        registration.setOrder(0);
        registration.addUrlPatterns("/upload/v1/*");
        return registration;
    }
  
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM