C语言 c++ php mysql nginx linux lnmp lamp lanmp memcache redis 面试 笔记 ppt 设计模式 问题 远程连接

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

  

posted on 2020-08-16 00:07  思齐_  阅读(742)  评论(0编辑  收藏  举报