关于通过http post进行文件上传的问题

前几天我基于plupload改写的文件上传模块出问题了,在本地测试的时候,只要不高于2G是没问题的,但给另外一个单位部署的那套系统居然只能上传不大于1M的文件。考虑到之前的上传也出现过不稳定的情况,接上级通知,重新写一下这个模块。

写是没问题,问题的关键是找到到底是什么原因导致的上传不靠谱。

文件上传应该包括两部分,一个数据传输的部分,一个是数据接收部分。

数据接收部分是用django写的,先看一下django是如何处理用户上传的数据的。

Django会把用户上传过来的文件封装到UploadFile类里,如果是大文件(默认大于2.5M)就先以临时文件存储,如果是小文件就直接读到内存中。以临时文件存储的类是TemporaryUploadedFile,存在内存中的文件是InMemoryUploadedFile。

UploadFile对象有如下方法:

1、UploadFile.read():

  从文件中读取全部上传数据。当上传文件过大时,可能会耗尽内存,慎用。

2、UploadFile.multiple_chunks():

  如上传文件足够大,要分成多个部分读入时,返回True.默认情况,当上传文件大于2.5M时,返回True。但这一个值可以配置。

3、UploadFile.chunks():

  返回一个上传文件的分块生成器。如multiple_chunks()返回True,必须在循环中使用chrunks()来代替read()。一般情况下直接使用chunks()就行。

4、UploadFile.name():上传文件的文件名

5、UplaodFile.size():上传文件的文件大小(字节)

由上面的说明可以写出handle_uploaded_file函数

def handle_uploaded_file(f):
  destination = open('some/file/name.txt', 'wb+')
  for chunk in f.chunks():
    destination.write(chunk)
  destination.close()

关于文件的接受和存储可以在settings.py中进行设置:

FILE_UPLOAD_MAX_MEMORY_SIZE:直接读入内存的最大上传文件大小(字节数)。当大于此值时,文件存放到磁盘。默认2.5M字节

FILE_UPLOAD_TEMP_DIR:临时文件的存储目录

FILE_UPLOAD_PERMISSIONS:权限
FILE_UPLOAD_HANDLERS:文件上传的处理器,默认是:
("django.core.files.uploadhandler.MemoryFileUploadHandler", "django.core.files.uploadhandler.TemporaryFileUploadHandler",)
即先尝试装入内存,如不行就存入临时文件。
上传小文件(62.6KB)时,request如下图所示:
上传大文件(533.8M)时,request如下图所示:

综上可知,django接收文件时,会根据文件的大小来判断是放在内存中还是以临时文件存储,如果文件过大的话也会做分块处理。这个过程看上去很合理,不应该有什么问题。
接下来看数据传输部分----http协议。
HTTP协议是浏览器与服务器进行通信或数据传输的一些约定。HTTP报文可以分为四类:GET、POST、PUT、DELETE,分别对应查、改、增、删操作。文件上传采用的是POST报文。关于上传的一些规范可查看: RFC1867
为了弄明白数据是怎么传过去的,我开始笨拙的使用wireshark来抓包分析,然后发现了一个很奇怪的现象。小文件可以抓到post报文,但大点的文件就抓不到了,只能看到服务器的响应报文,却看不到提交数据的post报文,百思不得其解,然后百度谷歌一通搜,也没得到找到相关的解释。实在没办法了,在网上发了几个帖子,得到回复是wireshark有问题。于是,我开始尝试用其他工具来抓包,搜来搜去,发现chrome和firefox都有分析http的插件。chrome是POSTMAN,firefox是FireBug,都很好用。本文的分析就是基于FireBug。
OK,那看一下包含数据的POST报文是什么样的。
请求头如下所示:
POST /home/upload HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8000/home/index
Cookie: csrftoken=YHb....bnTbIw_xk33P6xujZmU"
Connection: keep-alive
请求所包含的内容如下所示:
Content-Length	8981
Content-Type	multipart/form-data; boundary=---------------------------19470111021138404276570831403
Content-Disposition: form-data; name="file"; filename="track.shp.xml" Content-Type: text/xml
                    文件内容

boundary=-----------------------------19470111021138404276570831403
Content-Disposition: form-data; name="submit" Submit
boundary=-----------------------------19470111021138404276570831403--
也就是说,文件在http这一层是没有被分割的,通过两个boundary界定,可以通过filename来识别,通过wireshark可以发现,真正的数据分片是发生在TCP层的。

在网上搜了一下,说HTTP上传数据确实是有不靠谱的地方,比如不支持暂停、不支持断点续传而且由于层层封装是实际传输的数据较大等。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在VC中实现通过HTTPPOST方式上传文件,可以使用WinINet库中提供的API函数。下面是一个简单的示例代码: ```c++ #include <windows.h> #include <wininet.h> BOOL HttpPostFile(LPCTSTR lpszServer, LPCTSTR lpszObjectName, LPCTSTR lpszFilePath) { BOOL bResult = FALSE; HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwFileSize = 0; DWORD dwBytesRead = 0; DWORD dwBytesWritten = 0; BYTE* pBuffer = NULL; HANDLE hFile = INVALID_HANDLE_VALUE; // 打开本地文件 hFile = CreateFile(lpszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { goto Exit; } // 获取本地文件大小 dwFileSize = GetFileSize(hFile, NULL); if (dwFileSize == INVALID_FILE_SIZE) { goto Exit; } // 分配缓冲区 pBuffer = new BYTE[dwFileSize]; if (pBuffer == NULL) { goto Exit; } // 读取本地文件到缓冲区 if (!ReadFile(hFile, pBuffer, dwFileSize, &dwBytesRead, NULL)) { goto Exit; } // 打开网络会话 hInternet = InternetOpen(TEXT("MyApp"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (hInternet == NULL) { goto Exit; } // 建立连接 hConnect = InternetConnect(hInternet, lpszServer, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); if (hConnect == NULL) { goto Exit; } // 创建请求 hRequest = HttpOpenRequest(hConnect, TEXT("POST"), lpszObjectName, NULL, NULL, NULL, 0, 0); if (hRequest == NULL) { goto Exit; } // 发送请求头 if (!HttpSendRequest(hRequest, NULL, 0, NULL, 0)) { goto Exit; } // 发送请求体(即上传文件内容) if (!InternetWriteFile(hRequest, pBuffer, dwFileSize, &dwBytesWritten)) { goto Exit; } bResult = TRUE; Exit: if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } if (pBuffer != NULL) { delete[] pBuffer; } if (hRequest != NULL) { InternetCloseHandle(hRequest); } if (hConnect != NULL) { InternetCloseHandle(hConnect); } if (hInternet != NULL) { InternetCloseHandle(hInternet); } return bResult; } ``` 使用该函数上传文件的代码如下: ```c++ if (!HttpPostFile(TEXT("www.example.com"), TEXT("/upload"), TEXT("C:\\test.txt"))) { MessageBox(NULL, TEXT("上传文件失败!"), TEXT("Error"), MB_OK | MB_ICONERROR); } else { MessageBox(NULL, TEXT("上传文件成功!"), TEXT("Success"), MB_OK | MB_ICONINFORMATION); } ``` 其中,第一个参数是服务器的域名或IP地址,第二个参数是上传文件的目标地址,第三个参数是本地文件的路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值