公司产品需要做一个关于收集程序崩溃信息的模块,需要客户端程序在崩溃发生后将崩溃日志以及转储文件发送到后台。后台是HttpServer 理所当然的便想到了使用Http的Post来上传文件。
http协议网上说的有很多,http协议大致有三个部分:
Request-Line/Respone-Line,Header,Body三个部分。虽然网上说使用Http协议的Post上传文件的不太多,但还是找到了一份,而且基本上也确定了用法。大致上就是在Header的Content-Type中加上一个boundary,再根据这个boundary在Body中区别文件。
这里需要注意的是,在Body中每个文件开头的说明中,都需要带上boundary,并且,这个开头说明,需要和文件内容隔上一行,否则就会出错。
假如我们将Body中每个文件的开头看成是body的Header。那么我们可以将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.header和Http.body.tag中用到
代码我写了两份,一份是在MFC的环境下写的,引用afxinet.h来完成http操作
还有一份是使用Python写的
//************************************
// 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;
}
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
请求:
应答:
Http协议的post上传文件,其实也没啥.但是由于我没弄清楚那个http.body.header和Http.body.content之间需要有回车换行,导致上传一直失败,不知道问题出在那里了...所以谨此记下.