MFC中使用post提交form-data上传文件

已经有将近6年时间没写过MFC了,想想以前我也是写VC++入门程序开发的,那时候写协议栈、搞语音编码、做视频压缩和实时数据传输,相比现在更多偏业务的开发,那时候搞得都是非常技术的东西。眨眼间,MFC已经退出舞台,就连微软也在主推C#.net,曾经风光无限的MFC开发现如今已经几乎消失;ActiveX有java的applet来替代,MFC桌面应用程序也由更简单友好的C#取代,而软件开发的趋势早已经从传统的C/S过渡到B/S,云等等。技术的日新月异逼迫IT人不得不马不停蹄的学习,否则,一不留神就会被这个时代所抛弃,被后来者所碾压。时刻保持一颗不断学习,谦逊,对技术敬畏的心,才能在这条路上才能走的更远走得稳。

因为一些缘故,刚毕业时候带我的老领导找我紧急支援一点东西,具体需求就是用MFC写一个抓屏程序,把当前屏幕抓取下来,实时提交给服务器,然后再由服务中转推送给客户端去显示,可以简单理解为一个桌面监控程序。让我帮忙处理抓屏和POST提交文件的功能。我听了之后本来想拒绝,尽管老领导对我的印象还是VC++coder,但毕竟我已经好多年不写MFC,着实没有信心搞定。可是又不想辜负老领导对我期望,毕竟是他是我IT路上的引路人,所以最后还答应了。然后这个周末就一直在网上搜相关的解决方案,所幸,这些东西并不复杂,关于截屏的代码非常多,大多都能满足需求。但是最后关于POST提交文件这一块遇到了困难,网上能搜索到很多关于MFC使用post上传下载文件的代码,但是几乎没有哪一个能真正用的。

我这里记录并分享一个我最后搜索并优化的方案,先看代码


CString CScreenPushDlg::MakeRequestHeaders(CString& strBoundary)
{
CString strFormat;
CString strData;
strFormat = _T("Content-Type: multipart/form-data; boundary=%s ");
strData.Format(strFormat, strBoundary);
return strData;
}

CString CScreenPushDlg::MakePreFileData(CString& strBoundary, CString& strFileName, int iRecordID)
{
CString strFormat;
CString strData;
strFormat += _T("--%s");
strFormat += _T(" ");
strFormat += _T("Content-Disposition: form-data; name="job_number"");
strFormat += _T(" ");
strFormat += _T("%d");
strFormat += _T(" ");
strFormat += _T("--%s");
strFormat += _T(" ");
strFormat += _T("Content-Disposition: form-data; name="image"; filename="%s"");
strFormat += _T(" ");
//strFormat += _T("Content-Type: audio/wav");
strFormat += _T("Content-Type: image/png");
strFormat += _T(" ");
strFormat += _T("Content-Transfer-Encoding: binary");
strFormat += _T(" ");
strData.Format(strFormat, strBoundary, iRecordID, strBoundary, strFileName);
return strData;
}

CString CScreenPushDlg::MakePostFileData(CString& strBoundary)
{
CString strFormat;
CString strData;
strFormat = _T(" ");
strFormat += _T("--%s");
strFormat += _T(" ");
strFormat += _T("Content-Disposition: form-data; name="act"");
strFormat += _T(" ");
strFormat += _T("upload_img");
strFormat += _T(" ");
strFormat += _T("--%s--");
strFormat += _T(" ");
strData.Format(strFormat, strBoundary, strBoundary);
return strData;
}
//上传文件数据至HTTP服务器
int CScreenPushDlg::UploadFile(const CString &szServerURL, const CString &szUploadFileName)
{
if (szServerURL.IsEmpty() || szUploadFileName.IsEmpty())
{
return -1;
}
BOOL bRet = FALSE;
DWORD dwServiceType = 0;
CString strServer = _T("");
CString strObject = _T("");
INTERNET_PORT nPort = 0;
bRet = AfxParseURL(szServerURL, dwServiceType, strServer, strObject, nPort);
if(!bRet)
{
return -2;
}

int nRet = 0;
CInternetSession Session;
CHttpConnection * pHttpConnection = NULL;
CFile fTrack;
CHttpFile* pHTTPFile = NULL;
CString strHTTPBoundary = _T("");
CString strPreFileData = _T("");
CString strPostFileData = _T("");
DWORD dwTotalRequestLength;
DWORD dwChunkLength = 0;
DWORD dwReadLength = 0;
DWORD dwResponseLength = 0;
TCHAR szError[MAX_PATH] = {0};
void* pBuffer = NULL;
LPSTR szResponse = NULL;
CString strResponse = _T("");
BOOL bSuccess = TRUE;
CString strDebugMessage = _T("");

if (FALSE == fTrack.Open(szUploadFileName, CFile::modeRead | CFile::shareDenyWrite))
{
//AfxMessageBox(_T("Unable to open the file."));
return -3;
}
int nPos = szUploadFileName.ReverseFind('\');
CString strFileName = szUploadFileName.Mid(nPos+1);
int iRecordID = _ttoi(m_Num);
strHTTPBoundary = _T("----istroop----");
strPreFileData = MakePreFileData(strHTTPBoundary, strFileName, iRecordID);
strPostFileData = MakePostFileData(strHTTPBoundary);
dwTotalRequestLength = strPreFileData.GetLength() + strPostFileData.GetLength() + fTrack.GetLength();
dwChunkLength = 64 * 1024;
pBuffer = malloc(dwChunkLength);
if (NULL == pBuffer)
{
fTrack.Close();
return -4;
}
try
{
pHttpConnection = Session.GetHttpConnection(strServer,nPort);
pHTTPFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject);
pHTTPFile->AddRequestHeaders(MakeRequestHeaders(strHTTPBoundary));
pHTTPFile->SendRequestEx(dwTotalRequestLength, HSR_SYNC | HSR_INITIATE);
#ifdef _UNICODE
USES_CONVERSION;
pHTTPFile->Write(W2A(strPreFileData), strPreFileData.GetLength());
#else
pHTTPFile->Write((LPSTR)(LPCSTR)strPreFileData, strPreFileData.GetLength());
#endif
dwReadLength = -1;
DWORD m_dwUploadSize = 0;
while (0 != dwReadLength)
{
// strDebugMessage.Format(_T("%u / %un"), fTrack.GetPosition(), fTrack.GetLength());
// TRACE(strDebugMessage);
dwReadLength = fTrack.Read(pBuffer, dwChunkLength);
if (0 != dwReadLength)
{
m_dwUploadSize += dwReadLength;
pHTTPFile->Write(pBuffer, dwReadLength);
}
}
#ifdef _UNICODE
pHTTPFile->Write(W2A(strPostFileData), strPostFileData.GetLength());
#else
pHTTPFile->Write((LPSTR)(LPCSTR)strPostFileData, strPostFileData.GetLength());
#endif
pHTTPFile->EndRequest(HSR_SYNC);
dwResponseLength = pHTTPFile->GetLength();
while (0 != dwResponseLength)
{
szResponse = (LPSTR)malloc(dwResponseLength + 1);
szResponse[dwResponseLength] = '';
pHTTPFile->Read(szResponse, dwResponseLength);
strResponse += szResponse;
free(szResponse);
szResponse = NULL;
dwResponseLength = pHTTPFile->GetLength();
}
//strResponse;
}
catch (CException* e)
{
// e->GetErrorMessage(szError, MAX_PATH);
// e->Delete();
nRet = -5;
}
if (NULL != pHTTPFile)
{
pHTTPFile->Close();
delete pHTTPFile;
pHTTPFile = NULL;
}

fTrack.Close();
if (NULL != pBuffer)
{
free(pBuffer);
pBuffer = NULL;
}

return nRet;
}
这段代码相比网上能搜到的大多数方案,最大的区别在于写了一个MakeRequestHeaders和MakePreFileData方法。

因为这些年主要写java和前端js开发,java中有httpclient可以直接发起post/get请求,非常方便,js中有ajax、axios等都非常方便的发起的请求,类似设置header、Content-type、入参data等都有很简单明了的方法,但是VC++中使用CHttpFile类进行post请求却相对复杂些,主要在于api封装的颗粒度。我们先来看以下下面这两张截图:

                                                                                (截图一)

                                                                        (截图二)

这是一个ajax发起的上传文件并携带参数的POST请求,我以前都是只关注截图一,从来没考虑也没看过截图二中的内容。其实对于MFC中的POST请求,就需要我们自己封装原始的数据,即自己封装好截图二中的数据格式,类似Content-Disposition、Content-Type、通过name指定参数名,通过filename指定具体文件,还要设置FormBoundar等。这些东西在java、js中时绝对不会接触到的。而MakePreFileData正是起到这个作用。

大概记录到这里吧,开发语言这东西,真的是不写就会生疏,以前写VC++都能盲打api,现在看数据类型的声明类都觉得生疏,真的是感慨万千。毕业的这几年收获最大的大概是不像以前那么玻璃心,见不得别人和自己相左的意见;但是相对的,我觉得那时候的自己更自负,而现在我却有点过分自知,甚至不那么自信。

认清自己不难,难的是知道自己的不足和缺点后怎么鞭策自己去改变。
--------------------- 

原文地址:https://www.cnblogs.com/hyhy904/p/10942239.html