笔者在Windows编程开发时候,有个上传文件的需求,服务端给的接口是http接口,和网页上面 表单上传文件一样(form-data方式)。当然我们拿到这需求,一想 用Windows原生的API去做 肯定麻烦一点 当然也能做,再一想 我们用支持http协议的框架不就行了嘛,MFC、libcurl、OpenSSL等等应该很多。笔者比较熟悉的2个库,MFC和libcurl,所以决定用这2个库来做文件上传。
和大家一样,刚开始觉的很简单,网上搜下然后改下就OK。但是笔者却花费了些时间才完成。一是太相信postman了被postman给误导了,二是把代码写的更通用更简洁了一点。
笔者刚开用postman模拟上传文件,能成功上传。然后使用postman生成的http报文数据去组装,按理来说肯定可以的,但是就是被误导。
网上贴的代码,感觉的都不是很好,所以笔者稍微写整洁了一点,更通用了一点,下面是核心代码 贴出来。实际上可以根据文件后缀名获取到该文件的Content-Type的,可以做的更智能点 程序内部加一个数据字典就可以实现。笔者这里就偷懒了。
class PostUpFileValStruct;
typedef std::vector PostUpFileValVector;
class PostUpFileValStruct
{
public:
CString name; // 普通字段、文件字段 有值
CString filename; // 文件字段 有值
CString ContentType; // 文件字段 有值
CString content; // 普通字段 有值
};
/*
http post请求上传文件函数
*/
bool CUtility::HttpPostUploadFile(const PostUpFileValVector &PostParamVec/*in 请求参数列表*/, const CString &strURL/*in 上传文件服务器接口url*/,
std::map &requestHeaders/*in 请求头数据*/,CString &strResponse/*out 响应数据*/,
CString &strErrMsg/* out 上传文件出错信息*/)
{
bool bResult = false;
CString strPostUrl = strURL;
DWORD dw(0);
CString strServer;
CString strObject;
INTERNET_PORT nPort;
AfxParseURL(strPostUrl, dw, strServer, strObject, nPort );
CInternetSession session;
if(strURL.IsEmpty())
{
strErrMsg = _T("strURL为空!!!");
return bResult;
}
const int nTimeOut = 5000;//3000
session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, nTimeOut); //重试之间的等待延时
session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 3); //重试次数
session.SetOption(INTERNET_OPTION_SEND_TIMEOUT, nTimeOut );
session.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, nTimeOut );
CHttpConnection *pHttpConnection(NULL);
CHttpFile *pHttpFile(NULL);
try
{
pHttpConnection = session.GetHttpConnection( strServer, INTERNET_FLAG_DONT_CACHE , nPort );
CString str = L"https:";
CString strTemp = strURL.Left(str.GetLength());
BOOL bHttpsFlag(FALSE);
if (0 == strTemp.CompareNoCase(str))
{
bHttpsFlag = TRUE;
}
if (bHttpsFlag)
{
pHttpFile = pHttpConnection->OpenRequest( CHttpConnection::HTTP_VERB_POST, strObject, NULL, 0, 0, _T("HTTP/1.1"), INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD|INTERNET_FLAG_SECURE);
}
else
{
pHttpFile = pHttpConnection->OpenRequest( CHttpConnection::HTTP_VERB_POST, strObject, NULL, 0, 0, _T("HTTP/1.1"), INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_RELOAD);
}
//CString cstrBoundary = _T("----WebKitFormBoundaryoDL1nQAJxdvsAlcu");
CString cstrBoundary = _T("----") + CUtility::GetGUID(); // 这个字符串,随意生成即可,主要是界定字段数据的。
CString boundaryHead;
boundaryHead.Format(_T("Content-Type:multipart/form-data; boundary=%s"),cstrBoundary);
pHttpFile->AddRequestHeaders(_T("Accept: application/json, text/plain, */*" ) );
pHttpFile->AddRequestHeaders(_T("Accept-Encoding: gzip, deflate, br") );
pHttpFile->AddRequestHeaders(_T("Accept-Language: zh-cn"));
//pHttpFile->AddRequestHeaders(_T("Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoDL1nQAJxdvsAlcu"));
pHttpFile->AddRequestHeaders(_T("Cache-Control: no-cache"));
std::map::iterator it = requestHeaders.begin();
CString strHeadTemp;
for(; it != requestHeaders.end(); it++)
{
strHeadTemp.Format(_T("%s: %s"),it->first,it->second);
pHttpFile->AddRequestHeaders(strHeadTemp);
}
pHttpFile->AddRequestHeaders(boundaryHead);
if (bHttpsFlag)
{
DWORD dwFlags(0);
pHttpFile->QueryOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
dwFlags |= (SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_WRONG_USAGE |
SECURITY_FLAG_IGNORE_REVOCATION |
SECURITY_FLAG_IGNORE_UNKNOWN_CA);
pHttpFile->SetOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
}
if( pHttpFile != NULL )
{
// MFC在传输数据前要先发送数据长度,所以先计算上传报文数据的长度
////////--- 通过post请求上传文件报文格式如下 ---/////////
//一个普通的字段
//\r\n--{strBoundary}\r\nContent-Disposition: form-data; name="tranCode"\r\n\r\n2007
//一个文件的字段
//\r\n--{strBoundary}\r\nContent-Disposition: form-data; name="file"; filename="1590721092834.jpg"\r\nContent-Type: image/jpeg\r\n\r\n{二进制数据}
//报文结束标志
//\r\n--{strBoundary}--\r\n
// 结束部分报文
std::string strBoundary = CUtility::W2Astring(cstrBoundary);
std::string dataEnd = "\r\n--"+strBoundary+"--\r\n";
// 传输数据时是以字节为单位进行传输的,所以这里都用std::string或者char*进行处理
// 1.计算上传报文数据长度
DWORD totalLen = 0;
bool bFile = false;
for(int i = 0; i < PostParamVec.size(); i++)
{
bFile = PostParamVec[i].filename.IsEmpty() ? false:true;
CString temp;
if(bFile)
{
temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"),
cstrBoundary,PostParamVec[i].name,PostParamVec[i].filename,PostParamVec[i].ContentType);
}
else
{
temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s"),
cstrBoundary,PostParamVec[i].name,PostParamVec[i].content);
}
// post请求时,有的参数值可能带中文,需要unicode->utf8,写数据的时候同理
char* pData = NULL;
CUtility::ConvertUnicodeToUTF8(temp,pData);
if(pData)
{
totalLen += ::strlen(pData);
delete[] pData;
pData = NULL;
}
if(bFile)
{
CFile myFile;
CFileException fileException;
if ( !myFile.Open( PostParamVec[i].filename, CFile::modeRead, &fileException ) )
{
CString errMsg;
errMsg.Format(_T("Can't open file %s, error = %u\n"),PostParamVec[i].filename, fileException.m_cause);
TRACE(errMsg);
#ifdef TEXT_LOG
CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),errMsg);
#endif
}
totalLen += myFile.GetLength();
myFile.Close();
}
}
totalLen+= dataEnd.length();
pHttpFile->SendRequestEx( (DWORD)totalLen );
// 2.往服务器写数据
bFile = false;
for(int i = 0; i < PostParamVec.size(); i++)
{
bFile = PostParamVec[i].filename.IsEmpty() ? false:true;
CString temp;
if(bFile)
{
temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"),
cstrBoundary,PostParamVec[i].name,PostParamVec[i].filename,PostParamVec[i].ContentType);
}
else
{
temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s"),
cstrBoundary,PostParamVec[i].name,PostParamVec[i].content);
}
// post请求时,有的参数值可能带中文,需要unicode->utf8,写数据的时候同理
char* pData = NULL;
CUtility::ConvertUnicodeToUTF8(temp,pData);
if(pData)
{
int pDataLen = ::strlen(pData);
pHttpFile->Write(pData, pDataLen);
delete[] pData;
pData = NULL;
}
if(bFile)
{
CFile myFile;
CFileException fileException;
if ( !myFile.Open( PostParamVec[i].filename, CFile::modeRead, &fileException ) )
{
TRACE( _T("Can't open file %s, error = %u\n"),
PostParamVec[i].filename, fileException.m_cause );
}
int bufflength = 4 * 1024;
byte* buffer = new byte[bufflength];
memset(buffer,0,bufflength);
int byteRead = 0;
while ((byteRead = myFile.Read(buffer, bufflength)) != 0)
{
pHttpFile->Write(buffer, byteRead);
memset(buffer,0,bufflength);
}
myFile.Close();
}
}
pHttpFile->Write(dataEnd.c_str(),dataEnd.length());
pHttpFile->EndRequest( );
#ifdef TEXT_LOG
CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),_T(" EndRequest ok"));
#endif
DWORD dwStateCode = 0;
pHttpFile->QueryInfoStatusCode(dwStateCode);
if (dwStateCode != HTTP_STATUS_OK )
{
CString str;
str.Format(_T("服务器返回状态码:%d !!!\n"), dwStateCode);
#ifdef TEXT_LOG
CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),str);
#endif
TRACE(str);
// 认为出错
strErrMsg = str;
}
else if(dwStateCode == HTTP_STATUS_OK )
{
// 3.处理响应数据
CString contentType ;
pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_TYPE, contentType );
if(contentType.Find(_T("application/json")) >= 0)
{
bResult = true;
DWORD contentLength = 0;
BOOL b = pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,contentLength);
if(contentLength == 0)
{
contentLength = 4096;
}
char *pReadBuf = new char[contentLength +1];
memset(pReadBuf, 0, contentLength +1);
DWORD nRead = pHttpFile->Read(pReadBuf, contentLength);
// 响应数据可能带中文,需要utf8->unicode
CUtility::ConvertUTF8ToUnicode(pReadBuf, strResponse,nRead);
delete []pReadBuf;
pReadBuf = NULL;
}
else
{
// 认为出错
strErrMsg = _T("Content-type错误!!!");
#ifdef TEXT_LOG
CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),strErrMsg);
#endif
TRACE(strErrMsg);
}
#ifdef TEXT_LOG
CUtility::TextLog(L"CUtility::HttpPostUploadFile",L"QueryInfoStatusCode ok" );
#endif
}
}
}
catch (CInternetException* e)
{
TCHAR szError[1024] = {0};
e->GetErrorMessage(szError,1024);
DWORD errorCode = e->m_dwError;
CString str;
str.Format(_T("errorMsg:%s,m_dwError=%d"), szError,errorCode);
#ifdef TEXT_LOG
CUtility::TextLog(_T("CUtility::HttpPostUploadFile,HttpPostUploadFile exception"),str);
#endif
e->Delete();
TRACE( _T("\r----------------internet error:%s\r"),str );
strErrMsg = str;
}
if(pHttpConnection != NULL)
{
pHttpConnection->Close();
delete pHttpConnection;
pHttpConnection = NULL;
}
session.Close();
if(pHttpFile != NULL)
{
pHttpFile->Close();
delete pHttpFile;
pHttpFile = NULL;
}
return bResult;
}
接口调用地方代码
void CClienTestDlg::OnBnClickedButton3()
{
// 使用MFC 方式上传文件
PostUpFileValVector vec;
PostUpFileValStruct param;
param.name = _T("tranCode");
param.content = _T("2007");
vec.push_back(param);
param.name = _T("busiNo");
param.content = _T("20200527000000209096");
vec.push_back(param);
param.name = _T("sysId");
param.content = _T("ACP");
vec.push_back(param);
param.name = _T("operaId");
param.content = _T("00300");
vec.push_back(param);
param.name = _T("branchNo");
param.content = _T("00300");
vec.push_back(param);
param.name = _T("billId");
param.content = _T("ACP04");
vec.push_back(param);
param.name = _T("file");
param.content = _T("");
param.filename = _T("D:\\20200520120631.jpg");
param.ContentType = _T("image/jpeg");
vec.push_back(param);
CString upUrl = _T("http://172.xx.xxx.xxx:xxxxx/file/xxxxx");
CString cstrResponse;
CString cstrErrMsg;
std::map requestHeads;
bool bResult = CUtility::HttpPostUploadFile(vec,upUrl,requestHeads,
cstrResponse,cstrErrMsg);
CString strMsg;
if(bResult)
{
strMsg.Format(_T("文件上传成功,响应信息:%s"),cstrResponse);
MessageBox(strMsg,_T("文件上传"));
}
else
{
strMsg.Format(_T("文件上传失败!!!错误提示信息:%s"),cstrErrMsg);
MessageBox(strMsg,_T("文件上传"));
}
return;
}