• HTTP上传文件解析


    浏览器上传完整报文如下

    POST http://localhost:8080/fileUpload/ HTTP/1.1
    Host: localhost:8080
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Referer: http://localhost:8080/
    Content-Type: multipart/form-data; boundary=---------------------------114782935826962
    Content-Length: 937402
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    DNT: 1
    
    -----------------------------114782935826962
    Content-Disposition: form-data; name="text1"
    
    text default
    -----------------------------114782935826962
    Content-Disposition: form-data; name="text2"
    
    aωb
    -----------------------------114782935826962
    Content-Disposition: form-data; name="file1"; filename="timg.jpg"
    Content-Type: image/jpeg
    ... contents of file goes here ...
    ----------------114782935826962
    Content-Disposition: form-data; name="file2"; filename="timg.jpg"
    Content-Type: image/jpeg
    ... contents of file goes here ...
    ----------------114782935826962--
    
    从这里可以看出
    1. 文件上传时使用的Content-Type为 multipart/form-data
    根据rfc1867:
    The media-type multipart/form-data follows the rules of all multipart
       MIME data streams as outlined in RFC 1521. It is intended for use in
       returning the data that comes about from filling out a form.
    这个类型允许传递所有MIME(Multipurpose Internet Mail Extensions多用途互联网邮件扩展类型)数据流。
     
    2. 其通过Content-Type中的boundary=---------------------------114782935826962来分割表单中的不同参数。
    boundary是一个不会在文件流中出现的字符串,用以分割不同参数。
     
    浏览器发送后,那么后台是怎么处理的,这里以springMVC为例子。
    后台的SpringMvc接到后的处理方式如下:
    1. SpringMvc所有的请求都由DispatcherServlet进行分发,其具体代码截取如下:
    org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest, HttpServletResponse)
     
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try{
                try{
                    ...
                    processedRequest = checkMultipart(request);//检查并处理multipart的data,若存在file,则转换为StandardMultipartHttpServletRequest返回请求
                    multipartRequestParsed = (processedRequest != request);//标记是否有multipart处理过,若有,在最后要进行清理文件
                    ...
                }
                catch (Exception ex) {
                    dispatchException = ex;
                }
                
            }
            catch (Exception ex) {
                triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
            }
            catch (Throwable err) {
                triggerAfterCompletion(processedRequest, response, mappedHandler,
                        new NestedServletException("Handler processing failed", err));
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    ...
                }
                else {
                    // Clean up any resources used by a multipart request. 清理消息
                    if (multipartRequestParsed) {
                        cleanupMultipart(processedRequest);
                    }
                }
            }
        }

    接下来看看checkMultipart

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
            if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {//通过contentType中是否含有multipart/来判断是否有文件
                if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
                    if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                        logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
                    }
                }
                else if (hasMultipartException(request) ) {
                    logger.debug("Multipart resolution previously failed for current request - " +
                            "skipping re-resolution for undisturbed error rendering");
                }
                else {
                    try {
                        return this.multipartResolver.resolveMultipart(request);//将request中的数据流转成StandardMultipartHttpServletRequest
                    }
                    catch (MultipartException ex) {
                        if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                            logger.debug("Multipart resolution failed for error dispatch", ex);
                            // Keep processing error dispatch with regular request handle below
                        }
                        else {
                            throw ex;
                        }
                    }
                }
            }
            // If not returned before: return original request.
            return request;
    }

    org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(HttpServletRequest)

        @Override
        public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
            return new StandardMultipartHttpServletRequest(request, this.resolveLazily);//里面执行了parseRequest()
        }

    org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(HttpServletRequest)具体逻辑如下,其中从每部分中获取Content-Disposition,包含文件名,和元素名称,如果发现对象是文件的话(文件名不为空)则将文件放到request对象中去并返回。

    private void parseRequest(HttpServletRequest request) {
            try {
                Collection<Part> parts = request.getParts();
                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);//从每部分中获取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);
            }
        }
    接下来就是正常的处理逻辑,去执行springMvc对应的方法,从头中已经能拿到对应的file。
    在请求完成后,会进行文件资源的清理。
    org.springframework.web.multipart.support.StandardServletMultipartResolver.cleanupMultipart(MultipartHttpServletRequest)
     
    @Override
        public void cleanupMultipart(MultipartHttpServletRequest request) {
            if (!(request instanceof AbstractMultipartHttpServletRequest) ||
                    ((AbstractMultipartHttpServletRequest) request).isResolved()) {
                // To be on the safe side: explicitly delete the parts,
                // but only actual file parts (for Resin compatibility)
                try {
                    for (Part part : request.getParts()) {
                        if (request.getFile(part.getName()) != null) {
                            part.delete();
                        }
                    }
                }
                catch (Throwable ex) {
                    LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
                }
            }
        }
    参考:
    1. https://tools.ietf.org/html/rfc1867 文件上传的rfc规范
  • 相关阅读:
    Elasticsearch集成IKAnalyzer分析器
    Elasticsearch操作Document文档
    ElasticSearch简介
    Lucene查询索引
    Lucene索引维护(添加、修改、删除)
    Lucene使用IKAnalyzer分词
    Lucene搜索引擎入门
    MySQL优化(四)——读写分离
    MySQL优化(三)——主从复制
    Java 身份证验证工具类代码模板
  • 原文地址:https://www.cnblogs.com/alcc/p/9944042.html
Copyright © 2020-2023  润新知