HTTP上传文件解析

摘要:
边界是不出现在文件流中的字符串,用于分割不同的参数。浏览器发送消息后,如何在后台处理?以springMVC为例。接收到后端SpringMvc后,处理方法如下:1.SpringMvc的所有请求都由DispatcherServlet分发,具体代码拦截如下:org.springframework。网状物servlet。DispatcherServlet。doDispatchprotectedvoid doDispatchthrowsException{try{try{…processedRequest=checkMultipart;//检查并处理多部分数据。如果有文件,它将被转换为StandardMultipartHttpServletRequest,并返回请求multipartRequestParsed=(processedRequest!=request);//标记是否已处理多部分。如果是,请在最后清理文件。。。}捕获{dispatchException=ex;}}catch{triggerAfterCompletion;}catch{triggerAfterCompletion;}最后{if{…}else{//清理多个部分请求使用的资源。如果{cleanupMultipart;}}}接下来,检查MultipartprotectedHttpServletRequestcheckMultipartthrowsMultipartException{if(this.multipartResolver!

浏览器上传完整报文如下

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规范

免责声明:文章转载自《HTTP上传文件解析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇简单入手 vuex--状态管理Uploadify 3.2 参数属性、事件、方法函数详解以及配置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

WPF知识点全攻略13- 绘图

行&lt;线条X1=“10”Y1=“100”X2=“260”Y2=“100“Stroke=“黑色”StrokeDashArray=“5”StrokeThickness=“2”&gt;线冲程&gt;矩形&lt;矩形边距=“5”笔划=“黑色”高度=“100”宽度=“100“&gt;&lt;&书信电报,...

《学习opencv》笔记——矩阵和图像操作——cvAnd、cvAndS、cvAvg and cvAvgSdv

矩阵和图像的操作cvAnd函数其结构voidcvAnd;程序实例#include#include#includeintmain{IplImage*src1,*src2,*src3;src1=cvLoadImage;src2=cvLoadImage;src3=cvLoadImage;cvAnd;cvShowImage;cvShowImage;cvShowIma...

DB2字符函数简介及使用

Param2可以是编码单元16-16位UTF-16编码,也就是说,字符串表示为16位UTF-18编码字符串。Codeunits32-32位UTF-32编码,即字符串表示为32位UTF 32编码字符串。请注意,定义为FORBITDATA的字符串不能转换为图形字符。如果length<length,则来自的原始字符串短于结果中的长度。...

docker安装MySQL5.7示例!!坑,ERROR 1045 (28000): Access denied for user

处理mysql1045错误1.在/usr/local/mysql/conf中添加一个文件。d目录:mysql文件的内容是:[mysqld]skip-grant-tables2重新启动mysql:dockerstartmysql5.73进入docker:dockerexec-itmysql5.7bash4登录mysql:mysql-uroot-p5将root密...

2.页面绘制和引入组件库uView

文本+背景色的形式,而不是横幅图的形式,可以节省未来的工作量。在index.vue中,关于开关的代码:EFGHIJKLMNOPQRSTUWXYZB˃DEFGHIJKLNNOPQRSTUVWXYZEFGHIJKLMNOPQRSTUVWXYZ导出默认值{data(){return{}},onLoad()},方法:{}}。横幅{width:100%;height:...

linux系统redhat7.9安装R

1.查看系统信息[root@localhosthome]#cat/etc/redhat-releaseRedHatEnterpriseLinuxServerrelease7.9(Maipo)[root@localhosthome]#lsb_release aLSB版本::core-4.1-amd64:core-4.1-noarch:ccxx-4.1-amd6...