spring inputstream empty


 

1、spring框架記錄日志導致

 

在 logging.level.root debug或trace 級別下, org.springframework.web.servlet.DispatcherServlet#logRequest中會調用 request.getParameterMap()

此時會消耗 inputstream ,導致在controller中獲取不到 inputstream

 

It will be empty if it's already consumed beforehand. This will be implicitly done whenever you call getParameter()getParameterValues()getParameterMap()getReader(), etc on the HttpServletRequest. Make sure that you don't call any of those kind of methods which by themselves need to gather information from the request body before calling getInputStream(). If your servlet isn't doing that, then start checking the servlet filters which are mapped on the same URL pattern

 

org.springframework.core.log.LogFormatUtils#traceDebug public static void traceDebug(Log logger, Function<Boolean, String> messageFactory) {
    if (logger.isDebugEnabled()) {
        boolean traceEnabled = logger.isTraceEnabled();
        //日志級別是否到trace級別
        String logMessage = messageFactory.apply(traceEnabled);
        if (traceEnabled) {
            logger.trace(logMessage);
        }
        else {
            logger.debug(logMessage);
        }
    }
}

org.springframework.web.servlet.DispatcherServlet#logRequest private void logRequest(HttpServletRequest request) {
        //debug、trace級別生效,導致inputstream為空
        LogFormatUtils.traceDebug(logger, traceOn -> {
            String params;
            if (isEnableLoggingRequestDetails()) {
                params = request.getParameterMap().entrySet().stream()
                        .map(entry -> entry.getKey() + ":" + Arrays.toString(entry.getValue()))
                        .collect(Collectors.joining(", "));
            }
            else {
                params = (request.getParameterMap().isEmpty() ? "" : "masked");
            }

 

如果 content-type為 application/x-www-form-urlencoded 且 logging.level.root 為info級別 則可以獲取到

 

如果 content-type為 application/multipart/form-data; 且 logging.level.root 為info級別 仍舊不可以獲取到

org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
        //檢測是否為文件上傳,如果滿足條件則進行解析
        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);

        // Determine handler for the current request.
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }


org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest
private void parseRequest(HttpServletRequest request) {
    try {
        Collection<Part> parts = request.getParts(); //消費inputstream
        this.multipartParameterNames = new LinkedHashSet<>(parts.size());
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
        for (Part part : parts) {
            String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            String filename = disposition.getFilename();
            if (filename != null) {
                if (filename.startsWith("=?") && filename.endsWith("?=")) {
                    filename = MimeDelegate.decode(filename);
                }
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            }
            else {
                this.multipartParameterNames.add(part.getName());
            }
        }
        setMultipartFiles(files);
    }
    catch (Throwable ex) {
        handleParseFailure(ex);
    }
}

設置 spring.servlet.multipart.enabled=false(默認為開),便可以獲取到,但是就沒法采用 MultipartFile file

其實沒有必要, file.getInputStream() 就可以獲取到輸入流,但是該流是必須上傳完畢以后才可以獲取,其實讀的是本地的緩存問題

 

注意:只要指定的級別不包含 logRequest 所在的包,都不會因為框架記錄日志而影響

 

對於  application/x-www-form-urlencoded 用 @RequestBody 總是可以獲取到,因為框架會根據 getParameterMap進行重建

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
/**
     * Invoke the method after resolving its argument values in the context of the given request.
     * <p>Argument values are commonly resolved through
     * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
     * The {@code providedArgs} parameter however may supply argument values to be used directly,
     * i.e. without argument resolution. Examples of provided argument values include a
     * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
     * Provided argument values are checked before argument resolvers.
     * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
     * resolved arguments.
     * @param request the current request
     * @param mavContainer the ModelAndViewContainer for this request
     * @param providedArgs "given" arguments matched by type, not resolved
     * @return the raw value returned by the invoked method
     * @throws Exception raised if no suitable argument resolver can be found,
     * or if the method raised an exception
     * @see #getMethodArgumentValues
     * @see #doInvoke
     */
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        //計算要調用的controller的參數
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        return doInvoke(args);
    }

//計算 @RequestBody 注解的參數, 調用 getBody方法
org.springframework.http.server.ServletServerHttpRequest#getBody
    @Override
    public InputStream getBody() throws IOException {
        if (isFormPost(this.servletRequest)) {
            //從 request.getParameterMap(); 重新計算body,因為調用 request.getParameterMap body會被消費
            return getBodyFromServletRequestParameters(this.servletRequest);
        }
        else {
            //否則返回真實 InputStream, 注意這個 InputStream, 有可能已經被消費了,所以有可能為可
            return this.servletRequest.getInputStream();
        }
    }


org.springframework.http.server.ServletServerHttpRequest#getBodyFromServletRequestParameters
/**
     * Use {@link javax.servlet.ServletRequest#getParameterMap()} to reconstruct the
     * body of a form 'POST' providing a predictable outcome as opposed to reading
     * from the body, which can fail if any other code has used the ServletRequest
     * to access a parameter, thus causing the input stream to be "consumed".
     */
    private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
        Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);

        //根據參數map重新生成body
        //注意 因為map中包含get參數,所以生成的body中也包含get參數,所以 @RequestBody  並不一定完全等於真實body參數
        Map<String, String[]> form = request.getParameterMap();
        for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
            String name = nameIterator.next();
            List<String> values = Arrays.asList(form.get(name));
            for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
                String value = valueIterator.next();
                writer.write(URLEncoder.encode(name, FORM_CHARSET.name()));
                if (value != null) {
                    writer.write('=');
                    writer.write(URLEncoder.encode(value, FORM_CHARSET.name()));
                    if (valueIterator.hasNext()) {
                        writer.write('&');
                    }
                }
            }
            if (nameIterator.hasNext()) {
                writer.append('&');
            }
        }
        writer.flush();

        return new ByteArrayInputStream(bos.toByteArray());
    }

   

2、過濾器導致

 

參考:

  https://github.com/spring-projects/spring-framework/issues/24176

  


免責聲明!

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



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