2.使用Http协议Post上传文件

2.1 背景

公司产品需要做一个关于收集程序崩溃信息的模块,需要客户端程序在崩溃发生后将崩溃日志以及转储文件发送到后台。后台是HttpServer 理所当然的便想到了使用HttpPost来上传文件。

2.2 协议格式

http协议网上说的有很多,http协议大致有三个部分:

Request-Line/Respone-LineHeaderBody三个部分。虽然网上说使用Http协议的Post上传文件的不太多,但还是找到了一份,而且基本上也确定了用法。大致上就是在HeaderContent-Type中加上一个boundary,再根据这个boundaryBody中区别文件。

这里需要注意的是,在Body中每个文件开头的说明中,都需要带上boundary,并且,这个开头说明,需要和文件内容隔上一行,否则就会出错。

假如我们将Body中每个文件的开头看成是bodyHeader。那么我们可以将http协议结构表示为如下:

Http.line //请求行,应答行

Http.header //http 头部

#空行 //空行

Http.body //http 正文体

body中表达又如下

Http.body.header //http body

<空行> //空行

Http.body.content //http body 内容

... //上面三行的循环

Http.body.tag //http body

如果需要通过post传文件,上述结构中的http.header必须要包含一项Content-Type,Content-Type中需要有boundray,这个boundray值会在Http.body.headerHttp.body.tag中用到


2.3 代码

代码我写了两份,一份是在MFC的环境下写的,引用afxinet.h来完成http操作

还有一份是使用Python写的

2.3.1 MFC代码

//************************************
// Returns:		成功返回ture,失败返回false
// _Url:		Url,允许带参数
// _UploadName:后台的上传名字
// _RetStr:	返回字符串
// _FilePathList 文件路径列表
//************************************
bool PostUploadFile(const char* _Url,const char* _UploadName,
					char*& _RetStr,const std::vector& _FilePathList)
{
	CInternetSession session;
	CHttpConnection* pConnection = NULL;
	CHttpFile* pFile = NULL;
	try{
		DWORD ServerType=0;
		CString StrServer;
		CString StrObj;
		INTERNET_PORT ServerPort=0;
		if (AfxParseURL(_Url,ServerType,StrServer,StrObj,ServerPort))
		{
			pConnection=session.GetHttpConnection(StrServer,ServerPort);
			if (pConnection)
			{
				pFile=pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST,StrObj);
				if (pFile)
				{
					/*szBoundary,任意的一个串,用以标识body中的数据*/
					//该串需要够复杂,用以区别当前请求包中的数据
					const char* szBoundary="--WebKitFormBoundaryFYOMARTzhmkR9gXE";

					/*构造头部*/
					pFile->AddRequestHeaders("Accept:*/*");
					pFile->AddRequestHeaders("Accept-Encoding:gzip, deflate");
					pFile->AddRequestHeaders("Accept-Language:zh-CN");
					pFile->AddRequestHeaders("X-Requested-With:XMLHttpRequest");

					//构造头中的Content-Type项,其中需要填上boundary
					CString szContentType="Content-Type:multipart/form-data; boundary=";
					szContentType+=szBoundary;
					pFile->AddRequestHeaders(szContentType.GetBuffer(0));

					/*打开文件,合成每个文件头部数据,取出文件数据,以及合成尾部数据,计算总长度*/
					struct DataInfo{
						char* pBuf;
						DWORD BufLen;
						DataInfo(){
							ZeroMemory(this,sizeof(DataInfo));
						}
					};
					std::vector DataInfoList;
					const char* szPerPreContent=
						"\r\n--%s\r\n"
						"Content-Disposition:form-data;name=\"%s\";filename=\"%s\"\r\n"
						"Content-Type:%s\r\n\r\n";
					DWORD dwTotalBodyLen=0;
					std::vector::const_iterator it=_FilePathList.begin();
					for (;it!=_FilePathList.end();it++)
					{
						CFile _File;
						if (_File.Open(it->c_str(),CFile::modeRead|CFile::shareDenyWrite|CFile::typeBinary))
						{
							CString FileName=_File.GetFileName();
							CString PreContent;
							if (FileName.Right(3)=="txt")
							{
								PreContent.Format(szPerPreContent,szBoundary,
									_UploadName,FileName.GetBuffer(0),"text/plain");
							}else{
								PreContent.Format(szPerPreContent,szBoundary,
									_UploadName,FileName.GetBuffer(0),"application/octet-stream");
							}

							//每个文件的头部信息
							DataInfo preDi;
							preDi.BufLen=PreContent.GetLength();
							preDi.pBuf=new char[preDi.BufLen];
							memcpy(preDi.pBuf,PreContent.GetBuffer(0),preDi.BufLen);
							DataInfoList.push_back(preDi);

							dwTotalBodyLen+=preDi.BufLen;

							//文件内容
							DataInfo ContDi;
							ContDi.BufLen=(DWORD)_File.GetLength();
							ContDi.pBuf=new char[ContDi.BufLen];
							_File.Read(ContDi.pBuf,ContDi.BufLen);
							DataInfoList.push_back(ContDi);

							dwTotalBodyLen+=ContDi.BufLen;
						}
					}

					//尾长度
					const char* szEndContent=
						"\r\n"
						"--%s--\r\n";
					CString szEnd;
					szEnd.Format(szEndContent,szBoundary);
					DataInfo EndDi;
					EndDi.BufLen=szEnd.GetLength();
					EndDi.pBuf=new char[EndDi.BufLen];
					memcpy(EndDi.pBuf,szEnd.GetBuffer(0),EndDi.BufLen);
					DataInfoList.push_back(EndDi);

					dwTotalBodyLen+=EndDi.BufLen;


					/*发送数据*/
					pFile->SendRequestEx(dwTotalBodyLen,HSR_SYNC|HSR_INITIATE);
					std::vector::iterator it3=DataInfoList.begin();
					for (;it3!=DataInfoList.end();it3++)
					{
						pFile->Write(it3->pBuf,it3->BufLen);
						delete []it3->pBuf;
					}
					pFile->EndRequest(HSR_SYNC);

					/*获取状态*/
					DWORD dwStatusCode=-1;
					pFile->QueryInfoStatusCode(dwStatusCode);
					if (dwStatusCode==HTTP_STATUS_OK)
					{
						//读返回
						_RetStr=new char[(DWORD)pFile->GetLength()];
						pFile->Read(_RetStr,(UINT)pFile->GetLength());

						if (pFile)
							delete pFile;
						if (pConnection)
							delete pConnection;
						session.Close();
						return true;
					}
				}
			}
		}
	}catch(CInternetException* e)
	{
		delete e;
	}
	if (pFile)
		delete pFile;
	if (pConnection)
		delete pConnection;
	session.Close();

	return false;
}

2.3.1 Python代码

import http.client, urllib.parse
from urllib.parse import quote
def MakePreFile(boundary,name,filename,ContentType):
    str="\r\n--"+boundary+"\r\n"
    str+="Content-Disposition: form-data; name=\""+name+"\"; filename=\""+filename+"\"\r\n"
    str+="Content-Type:"+ContentType+"\r\n\r\n"
    return str.encode()

def MakeEndFile(Boundary):
    str="\r\n\r\n--"+Boundary+"--\r\n"
    return str.encode()

def MakeFile(FilePath):
    fHandler = open(FilePath, "rb")
    buf = fHandler.read()
    fHandler.close()
    return buf

def PostFile(Url,UnloadName,Boundary,FileContenList):
    urlgroup = urllib.parse.urlparse(Url)
    headers = {"Accept": "*/*",
               "Accept-Language": "zh-cn",
               "Content-Type": "multipart/form-data; boundary="+Boundary,
               "Accept-Encoding": "gzip, deflate",
               "Connection": "keep-alive"}
    conn = http.client.HTTPConnection(urlgroup.netloc)
    urlParams = urlgroup.path+"?"+urlgroup.query
    urlParams = quote(urlParams,safe='/:?=&')
    print(urlParams)
    PostData=b""
    for i in FileContenList:
        PostData+=MakePreFile(Boundary,UnloadName,i["name"],i["ContentType"])
        PostData+=MakeFile(i["path"])
    PostData+=MakeEndFile(Boundary)
    conn.request("POST", urlParams, PostData, headers)
    response = conn.getresponse()
    return response

2.4 上传文件抓包例子

请求:

2.使用Http协议Post上传文件_第1张图片

应答:

2.使用Http协议Post上传文件_第2张图片

2.5 小结

Http协议的post上传文件,其实也没啥.但是由于我没弄清楚那个http.body.headerHttp.body.content之间需要有回车换行,导致上传一直失败,不知道问题出在那里了...所以谨此记下.

你可能感兴趣的:(工作录事辑)