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\r\n");
	strData.Format(strFormat, strBoundary);
	return strData;
}

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

CString CScreenPushDlg::MakePostFileData(CString& strBoundary)
{
	CString strFormat;
	CString strData;
	strFormat = _T("\r\n");
	strFormat += _T("--%s");
	strFormat += _T("\r\n");
	strFormat += _T("Content-Disposition: form-data; name=\"act\"");
	strFormat += _T("\r\n\r\n");
	strFormat += _T("upload_img");
	strFormat += _T("\r\n");
	strFormat += _T("--%s--");
	strFormat += _T("\r\n");
	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] = '\0';
			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,現在看數據類型的聲明類都覺得生疏,真的是感慨萬千。畢業的這幾年收穫最大的大概是不像以前那麼玻璃心,見不得別人和自己相左的意見;但是相對的,我覺得那時候的自己更自負,而現在我卻有點過分自知,甚至不那麼自信。

認清自己不難,難的是知道自己的不足和缺點後怎麼鞭策自己去改變。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章