• 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

      

  • 相关阅读:
    flash 异性窗体
    ASCⅡ 表 关键字符
    VC 中显示位图的步骤
    输出电脑的所有Mac地址
    const char* 和 char* const
    C# 生成PDF
    vc6显示行号
    纪念一下。
    MII接口全家福
    Virtex6 GTX设计总结:预加重、均衡、输出振幅的值
  • 原文地址:https://www.cnblogs.com/siqi/p/13511138.html
Copyright © 2020-2023  润新知