日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

使用WinHttp接口实现HTTP协议Get、Post和文件上传功能

發布時間:2023/11/27 生活经验 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用WinHttp接口实现HTTP协议Get、Post和文件上传功能 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

? ? ? ? 我實現了一個最新版本的接口,詳見《實現HTTP協議Get、Post和文件上傳功能——使用WinHttp接口實現》。還有基于libcurl實現的版本《實現HTTP協議Get、Post和文件上傳功能——使用libcurl接口實現》。以下是原博文:

? ? ? ? 我們在做項目開發時,往往會涉及到和服務器通信。對于安全性要求不高的情況,一般我們采用HTTP通信協議。對于喜歡挑戰底層技術的同學,可能希望使用winsocket去完成通信過程。對于希望快速開發的同學,可能希望引入諸如CURL這類的第三方庫。而本文將介紹使用WinHttp接口實現Http協議的Get、Post和文件上傳的功能。為了保證我們代碼的精簡性和易擴展性,我并不打算做的很全面——比如我不考慮HTTPS和SSL以及轉碼等。我只是希望提供一個一目了然的結構,用于指出三種功能在代碼實現上的異同點。當然在這套代碼上增加HTTPS和SSL,以及用戶名\密碼機制也是非常簡單的。(轉載請指明出于breaksoftware的csdn博客)——新版本參閱《實現HTTP協議Get、Post和文件上傳功能——使用WinHttp接口實現》。

協議口語化描述

? ? ? ? 在項目中我們可能遇到的服務端同學對協議的描述:

  1. 你可以對http://xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pk2發送Get請求,參數的Key是userkey,Value是uservalue。
  2. 你可以對http://xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pk2發送Post請求,參數的Key是Data,Value是一個很長的數據。
  3. 你可以向http://xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pk2上傳一個文件,文件的Key是Data,Value是文件的內容。哦!別忘了,還要傳文件的MD5給我們,這個MD5的參數的Key是hash,Value是文件內容的MD5值。

? ? ? ? 在上述的描述中,你可能會遇到曾經和服務端同學溝通的影子。一般來說,對于簡單協議的描述,上述基本可以涵蓋。我提煉出如下實現,來實現相關功能,具體的函數說明會在之后給出

BOOL CHttpClientSyn::TransmiteData( const std::wstring& wstrUrl, EType eType, DWORD dwTimeout)
{BOOL bSuc = FALSE;do {if ( FALSE == InitializeHttp(wstrUrl, dwTimeout)) {break;}if ( FALSE == TransmiteData(eType) ) {break;}ReceiveData();UninitializeHttp();bSuc = TRUE;} while (0);return bSuc;
}

信息準備

? ? ? ? 我們看一下1和2描述內容。可以看出,其主要差別就是一個是使用Get方式發送,一個是使用Post方式。那就是說,除了發送方式不同,我們其他的設計“基本”可以認為是統一的。那么我們就先分析下URL及追加的參數。在討論這個之前,我先引進一個結構體URL_COMPONENTS

typedef struct {DWORD           dwStructSize;LPTSTR          lpszScheme;DWORD           dwSchemeLength;INTERNET_SCHEME nScheme;LPTSTR          lpszHostName;DWORD           dwHostNameLength;INTERNET_PORT   nPort;LPTSTR          lpszUserName;DWORD           dwUserNameLength;LPTSTR          lpszPassword;DWORD           dwPasswordLength;LPTSTR          lpszUrlPath;DWORD           dwUrlPathLength;LPTSTR          lpszExtraInfo;DWORD           dwExtraInfoLength;
} URL_COMPONENTS, *LPURL_COMPONENTS;

? ? ? ? 詳細的說明,可以查看MSDN。我們可以這樣調用函數,以解析出URL中包含的信息

URL_COMPONENTS urlCom;
……
WinHttpCrackUrl( wstrUrl.c_str(), wstrUrl.length(), ICU_ESCAPE, &urlCom);

? ? ? ? 我在此,以http://xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pk2為例,做個簡要的說明:

  • dwStructSize用于表明該結構體大小,一般我們都是傳遞sizeof(URL_COMPONENTS)。
  • lpszSheme指向一段用于保存協議類型的內存空間,dwSchemeLength用于描述傳入空間的大小(以TCHARS為單位的大小,下面其他空間大小描述字段都是以TCHARS單位)。對應于我們的例子,該空間將保存的結果是:http,dwSchemeLength的值是4(執行后被修改)。
  • lpHostName指向一段用于保存域名信息的內存空間,dwHostNameLength;用于描述傳入空間的大小。對應于我們的例子,lpHostName指向的空間信息是:xxx.yyy.zzz。dwHostNameLength返回11。
  • nPort用于接收端口號。我們例子中的端口號是8324。
  • lpszUserName和lpszPassword分別用于保存URL中攜帶的用戶名和密碼。我們例子中沒有這些信息(包含密碼的格式是http://name:password@xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pv2),所以我們不需要使用這些空間,自然不必分配相應的空間。
  • lpszUrlPath指向保存URL的路徑——不包含域名的一段內存空間。對應于我們的例子,該空間的值是:/urlpath。
  • lpszExtraInfo指向保存URL中參數信息的一段內容空間。對應于我們的例子,該空間的值是?pk1=pv1&pk2=pk2

? ? ? ? 完整的實現代碼是

BOOL CHttpClientSyn::InitializeHttp( const std::wstring& wstrUrl, DWORD dwTimeout)
{BOOL bSuc = FALSE;do {URL_COMPONENTS urlCom;memset(&urlCom, 0, sizeof(urlCom));urlCom.dwStructSize = sizeof(urlCom);WCHAR wchScheme[64] = {0};urlCom.lpszScheme = wchScheme;urlCom.dwSchemeLength = ARRAYSIZE(wchScheme);WCHAR wchHostName[1024] = {0};urlCom.lpszHostName = wchHostName;urlCom.dwHostNameLength = ARRAYSIZE(wchHostName);WCHAR wchUrlPath[1024] = {0};urlCom.lpszUrlPath = wchUrlPath;urlCom.dwUrlPathLength = ARRAYSIZE(wchUrlPath);WCHAR wchExtraInfo[1024] = {0};urlCom.lpszExtraInfo = wchExtraInfo;urlCom.dwExtraInfoLength = ARRAYSIZE(wchExtraInfo);if ( FALSE == WinHttpCrackUrl( wstrUrl.c_str(), wstrUrl.length(), ICU_ESCAPE, &urlCom) ) {break;}std::wstring wstrExtraInfo = urlCom.lpszExtraInfo;

? ? ? ? 我們通過這個結構體,可以拆解開URL。這兒我們需要特別注意的是lpszExtraInfo保存的信息:?pk1=pv1&pk2=pk2。在我們口頭描述的協議中,還要增加一個參數,即userkey=uservalue。那么完整的參數將是:?pk1=pv1&pk2=pk2&userkey=uservalue。為了讓這種參數的拼接具有易擴展性,我將參數信息分拆并保存到一個Map中。然后繼承于我們基類的派生類,可以根據自己的業務特點,向我們這個Map中新增其他Key-Value對,最后我們統一生成參數串。這兒需要指出的是,這種方法只是針對GET協議,因為GET協議發送參數的方法是一致的。而POST和文件上傳協議都不需要對lpszExtraInfo解析參數,它將作為UrlPath的一部分在之后的操作中被使用。

VOID CHttpClientSyn::ParseParams(const std::wstring& wstrExtraInfo)
{int nPos = 0;nPos = wstrExtraInfo.find('?');if ( -1 == nPos ) {return;}std::wstring wstrParam = wstrExtraInfo;int nStaticMaxParamCount = MAXSTATICPARAMCOUNT;do{wstrParam = wstrParam.substr(nPos + 1, wstrExtraInfo.length() - nPos - 1);nPos = wstrParam.find('&', nPos);std::wstring wstrKeyValuePair;if ( -1 == nPos ) {wstrKeyValuePair = wstrParam;}else {wstrKeyValuePair = wstrParam.substr(0, nPos);}int nSp = wstrKeyValuePair.find('=');if ( -1 != nSp ) {StParam stParam;stParam.wstrKey = wstrKeyValuePair.substr(0, nSp);stParam.wstrValue = wstrKeyValuePair.substr( nSp + 1, wstrKeyValuePair.length() - nSp - 1);m_VecExtInfo.push_back(stParam);}}while(-1 != nPos && nStaticMaxParamCount > 0);
}

? ? ? ? 同時,我們的基類提供一個純虛函數,讓繼承類去自由增加參數

virtual VOID AddExtInfo(VecStParam& VecExtInfo) = 0;

? ? ? ? 于是在CHttpClientSyn::InitializeHttp函數中,執行

        std::wstring wstrExtraInfo = urlCom.lpszExtraInfo;ParseParams(wstrExtraInfo);AddExtInfo(m_VecExtInfo);

? ? ? ? 在本文的后面部分,我會給出各繼承類對該方法的實現。
? ? ? ? 至此,各種該準備的數據已經OK了。現在,我們要初始化Get、Post和上傳結構都要環境——打開Session并連接服務器

打開Session并連接服務器

? ? ? ? 這部分的代碼,三種方式是一致的。具體也沒什么好說明的,直接上代碼(還是之前的CHttpClientSyn::InitializeHttp中)

        m_hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 ); if ( NULL == m_hSession ) {break;}m_hConnect = WinHttpConnect( m_hSession, urlCom.lpszHostName, urlCom.nPort, 0 );if ( NULL == m_hConnect ) {break;}m_wstrUrlPath = urlCom.lpszUrlPath;bSuc = TRUE;} while (0);return bSuc;
}

? ? ? ? ? 至此,三種方式相同的執行路徑已經結束。我們要依據繼承類的調用方式,決定走三種方式中的哪個

BOOL CHttpClientSyn::TransmiteData(EType eType)
{BOOL bSuc = FALSE;switch (eType) {case eGet:{bSuc = TransmiteDataToServerByGet();}break;case ePost:{bSuc = TransmiteDataToServerByPost();}break;case eUpload:{bSuc = TransmiteDataToServerByUpload();}break;default: break;}return bSuc;
}

使用Get方式發送數據

? ? ? ?Get方式是最常用的HTTP方式。它的實現也很簡單,只要將除了Host和Port部分(上例中/urlpath?pk1=pv1&pk2=pk2&userkey=uservalue,注意那個?號)一次性發給服務器即可。注意這個發送要使用WinHttpOpenRequest來完成。

BOOL CHttpClientSyn::TransmiteDataToServerByGet()
{BOOL bSuc = FALSE;do {std::wstring wstrUrlPathAppend = m_wstrUrlPath;// 采用Get方式時,要將參數放在OpenRequest中if ( false == wstrUrlPathAppend.empty() ) {wstrUrlPathAppend += L"?";}wstrUrlPathAppend += GenerateExtInfo(m_VecExtInfo);m_hRequest = WinHttpOpenRequest(m_hConnect, L"Get",wstrUrlPathAppend.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);if ( NULL == m_hRequest ) {break;}

? ? ? ? 在請求打開后,我們還要設置頭信息。我這兒將設置頭信息的函數設置為純虛函數,這樣繼承類就要自己實現這個函數,并設置自己的頭信息。

        ModifyRequestHeader(m_hRequest);

? ? ? ? 頭信息設置好后,我們就可以發送請求了

        if ( FALSE == WinHttpSendRequest( m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) ){break;}bSuc = TRUE;} while (0);return bSuc;}

? ? ? ? 過程就是如此簡單。

? ? ? ? 我們再看下繼承類的相關實現

std::wstring CHttpTransByGet::GenerateExtInfo( const VecStParam& VecExtInfo )
{std::wstring wstrExtInf;for ( VecStParamCIter it = VecExtInfo.begin(); it != VecExtInfo.end(); it++ ) {if ( false == wstrExtInf.empty() ) {wstrExtInf += L"&";}wstrExtInf += it->wstrKey;wstrExtInf += L"=";wstrExtInf += it->wstrValue;}return wstrExtInf;
}BOOL CHttpTransByGet::ModifyRequestHeader( HINTERNET hRequest )
{std::wstring wstrHeader[] = { L"Content-type: application/x-www-form-urlencoded\r\n"};for ( size_t i = 0; i < ARRAYSIZE(wstrHeader); i++ ) {WinHttpAddRequestHeaders(hRequest, wstrHeader[i].c_str(), wstrHeader[i].length(), WINHTTP_ADDREQ_FLAG_ADD);}return TRUE;
}VOID CHttpTransByGet::AddExtInfo( VecStParam& VecExtInfo )
{for ( VecStParamCIter it = m_vecParam.begin(); it != m_vecParam.end(); it++ ) {VecExtInfo.push_back(*it);}
}

? ? ? ? 這段代碼,沒有多少要注意的,只要注意下Get方式要設置的頭信息。

使用Post方式發送數據

? ? ? ? Post方式和Get方式的有若干實現的區別。首先,我們在打開Request的時候,要設置Post方式,同時要設置打開的是UrlPath,而不是攜帶參數的部分(即上例中的/urlpath)。

BOOL CHttpClientSyn::TransmiteDataToServerByPost()
{BOOL bSuc = FALSE;do {m_hRequest = WinHttpOpenRequest(m_hConnect, L"Post",m_wstrUrlPath.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);if ( NULL == m_hRequest ) {break;}

? ? ? ?之后,我們也是要設置頭信息。這兒我們可以和上面Get方式一樣設置

        ModifyRequestHeader(m_hRequest);

? ? ? ? 最后便是數據發送。我們回顧下2中的描述:

? ? ? ? 你可以對http://xxx.yyy.zzz:8324/urlpath?pk1=pv1&pk2=pk2發送Post請求,參數的Key是Data,Value是一個很長的數據。

? ? ? ? 可以看出,我們要發送兩批數據:一個是固有參數pk1=pv2&pk2=pv2;一個是不確定的參數“參數的Key是Data,Value是一個很長的數據”。我也是按這種描述設計的:

? ? ? ? 先將容易確定的固定參數發送出去

        std::wstring wstrExtInfo = GenerateExtInfo(m_VecExtInfo);std::string strExtInfo = CW2A(wstrExtInfo.c_str(), CP_UTF8);DWORD dwTotal = strExtInfo.length();dwTotal += GetDataSize();if ( FALSE == WinHttpSendRequest( m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, dwTotal, 0)) {break;}if ( 0 != strExtInfo.length() ) {// 默認可以一次全部寫完if ( FALSE == WinHttpWriteData(m_hRequest, strExtInfo.c_str(), strExtInfo.length(), NULL ) ) {break;}}

? ? ? ? 這兒做了一個偷懶的處理,我將數據一次性寫入。當然比較嚴謹的做法是根據每次成功的長度遞減數據發送。

? ? ? ? 為了支持這種可能是Data對應的不確定數據的發送,我在基類中暴露了一個接口,供繼承函數類以向基類邏輯提供數據。我這兒分而治之,是為了區分這些數據和之前的固有數據的區別——固有數據是字符串,而自定義數據可能是2進制流。

        // 靜態分配一個數組BYTE buffer[1024]= {0};BOOL bContinue = FALSE;BOOL bSendOK = FALSE;do {DWORD dwBufferLength = sizeof(buffer);SecureZeroMemory(buffer, dwBufferLength);DWORD dwWriteSize = 0;bContinue = GetData(buffer, dwBufferLength, dwWriteSize);if ( 0 != dwWriteSize ) {bSendOK= WinHttpWriteData( m_hRequest, buffer, dwWriteSize, NULL);}else {bSendOK = TRUE;}} while ( bContinue && bSendOK );bSuc = bSendOK;} while (0);return bSuc;
}

? ? ? ? 這個邏輯,分配了一個1024字節的空間。通過繼承類(或基類,基類直接返回False)GetData函數不停填充數據,并調用WinHttpWriteData發送數據。我們看下繼承類的實現

DWORD CHttpTransByPost::GetDataSize()
{return m_dwDataSize;
}BOOL CHttpTransByPost::GetData( LPVOID lpBuffer, DWORD dwBufferSize, DWORD& dwWrite )
{BOOL bContinue = TRUE;dwWrite = 0;if ( m_dwDataSize > m_dwWriteIndex + dwBufferSize ) {dwWrite = dwBufferSize;}else {dwWrite = m_dwDataSize - m_dwWriteIndex;bContinue = FALSE;}if ( 0 != memcpy_s(lpBuffer, dwBufferSize, (LPBYTE)m_lpData + m_dwWriteIndex, dwWrite) ){bContinue = FALSE;}return bContinue;
}BOOL CHttpTransByPost::TransDataToServer( const std::wstring& wstrUrl, DWORD dwTimeout, VecStParam& vecParam, LPVOID lpData, DWORD dwDataLenInBytes )
{m_lpData = lpData;m_dwDataSize = dwDataLenInBytes;m_vecParam.assign(vecParam.begin(), vecParam.end());m_dwWriteIndex = 0;return TransmiteData(wstrUrl, eGet, dwTimeout);
}

? ? ? ? m_dwWriteIndex用于標記當前已經讀取到哪個位置。這樣這些函數將保證,基類將可以將數據讀取完畢。這兒可能有個要注意的就是:要將“&Data=”傳入lpData地址空間中。

向服務器上傳文件

? ? ? ? 向服務器上傳文件,可能是使用的頻率僅次于Get的一種方式。在編寫上傳功能時,我還是踩中了不少坑,這也是我決心將這些整理出來分享的一個很重要原因。

? ? ? ? 最開始時,我以為上傳文件無非就是一個Post請求。后來經過一些磨難后,發現事實僅非如此。

? ? ? ? 首先我們看下和Post調用相同的地方

BOOL CHttpClientSyn::TransmiteDataToServerByUpload()
{BOOL bSuc = FALSE;do {m_hRequest = WinHttpOpenRequest(m_hConnect, L"Post",m_wstrUrlPath.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);if ( NULL == m_hRequest ) {break;}ModifyRequestHeader(m_hRequest);std::wstring wstrExtInfo = GenerateExtInfo(m_VecExtInfo);std::string strExtInfo = CW2A(wstrExtInfo.c_str(), CP_UTF8);DWORD dwTotal = strExtInfo.length();dwTotal += GetDataSize();if ( FALSE == WinHttpSendRequest( m_hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, dwTotal, 0)) {break;}

? ? ? ? 這僅僅是調用流程的相同,而不同點,我都將其“埋伏”在繼承類中。我們先看繼承類中頭設置的實現

#define BOUNDARYPART L"--h1o9n8e6y6k6k"
……m_wstrNewHeader = L"Content-Type: multipart/form-data; boundary=";m_wstrNewHeader += BOUNDARYPART;m_wstrNewHeader += L"\r\n";
……
BOOL CHttpUploadFiles::ModifyRequestHeader( HINTERNET hRequest )
{return ::WinHttpAddRequestHeaders(hRequest, m_wstrNewHeader.c_str(), m_wstrNewHeader.length(), WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
}

? ? ? ??Content-Type: multipart/form-data;相關說明可以參看rfc2388,至于更詳細的文件上傳的rfc可以參看rfc1867。本文只從使用的角度去講解,所以不會去分析RFC文檔。讀者只要知道我們要設置這個頭即可。從這個頭可以看出來,我們這次請求是一個MultiPart的,即多部分組成。那么如何分隔各部分數據呢?我們使用一個分隔符,該分隔符就是上面代碼中的"--h1o9n8e6y6k6k"。我們還要在頭中告訴服務器:我們要用什么來做分隔符。于是你看到這個頭的完整信息是:

Content-Type: multipart/form-data; boundary=--h1o9n8e6y6k6k

? ? ? ? 在之后,我們還會陸續提到這個分隔字段。它將貫穿整個Post過程。

? ? ? ? 頭信息設置好后,我將發送文件

        // 靜態分配一個數組BYTE buffer[1024]= {0};BOOL bContinue = FALSE;BOOL bSendOK = FALSE;do {DWORD dwBufferLength = sizeof(buffer);SecureZeroMemory(buffer, dwBufferLength);DWORD dwWriteSize = 0;bContinue = GetData(buffer, dwBufferLength, dwWriteSize);if ( 0 != dwWriteSize ) {bSendOK= WinHttpWriteData( m_hRequest, buffer, dwWriteSize, NULL);}else {bSendOK = TRUE;}} while ( bContinue && bSendOK );

? ? ? ? 文件發送好之后,我們再將URL中帶的pk1=pv1&pk2=pv2信息發送出去。

if ( 0 != strExtInfo.length() ) {if ( FALSE == WinHttpWriteData(m_hRequest, strExtInfo.c_str(), strExtInfo.length(), NULL ) ) {break;}}bSuc = bSendOK;} while (0);return bSuc;
}

? ? ? ? 我之所以如此快速的將這個流程過掉,而沒細分講解,是希望大家避免一個坑——發送順序問題。如果這兩個順序反了,服務器可能接收不到文件。原因是在文件段(之后會介紹文件段是什么,這個名字是我臨時起意)之后,我們還要向服務器發一個普通數據段(之后會介紹普通數據段,這個名字也是我臨時起意)。否則服務器會一直等待,認為我們文件沒傳完,哪怕我們在WinHttpSendRequest設置了正確的大小。當然這個順序也不是一定要如此,我們可以將普通數據(pk1=pv1&pk2=pv2)先發送,再發送文件段,最后再發送一個無用的數據段。

? ? ? ? 我們先關注一下這段代碼

BOOL CHttpUploadFiles::TransDataToServer( const std::wstring wstrUrl, VecStParam& VecExtInfo, const std::wstring& wstrFilePath,  const std::wstring& wstrFileKey)
{m_wstrBlockStart = L"--";m_wstrBlockStart += BOUNDARYPART;m_wstrBlockStart += L"\r\n";m_strBlockStartUTF8 = CW2A(m_wstrBlockStart.c_str(), CP_UTF8);m_wstrBlockEnd =  L"\r\n--";m_wstrBlockEnd += BOUNDARYPART;m_wstrBlockEnd +=  L"--\r\n";m_wstrNewHeader = L"Content-Type: multipart/form-data; boundary=";m_wstrNewHeader += BOUNDARYPART;m_wstrNewHeader += L"\r\n";

? ? ? ? m_wstrNewHeader這個字段我們已經在之前講解過,它是需要使用WinHttpAddRequestHeaders設置的頭信息。m_wstrBlockStart 是我們整個大的數據塊(包括文件段和數據段)的一開始的標識符,即它是要“最”先傳送給服務器。m_wstrBlockEnd應該可以猜出來了——它是整個大數據塊的結尾符。即我們整個數據將要被m_wstrBlockStart和m_wstrBlockEnd包含。

----h1o9n8e6y6k6k(用\r\n)
數據
----h1o9n8e6y6k6k--(用\r\n)

? ? ? ? 然后我們看下文件段。文件段一開始是有這樣的一個頭

    std::wstring wstrUploadFileHeader;wstrUploadFileHeader = m_wstrBlockStart;wstrUploadFileHeader += L"Content-Disposition: form-data; name=\"";wstrUploadFileHeader += wstrFileKey;wstrUploadFileHeader += L"\";";wstrUploadFileHeader += L"filename=\"";wstrUploadFileHeader += wstrFileName;wstrUploadFileHeader += L"\"\r\n";wstrUploadFileHeader += L"Content-Type:application/octet-stream\r\n\r\n";m_strUploadFileHeaderUTF8 = CW2A(wstrUploadFileHeader.c_str(), CP_UTF8);

? ? ? ? 這個頭包含了文件名和文件內容對應的Key。以描述3 為例,這個Key就是name的值,就是Data。

----h1o9n8e6y6k6k(用\r\n)
Content-Disposition: form-data; name="Data";filename="uploadfilename.bin"(用\r\n)
Content-Type:application/octet-stream(用\r\n\r\n)文件內容
----h1o9n8e6y6k6k--(用\r\n)

? ? ? ? 我們再看下文件發送的流程,其實就是數據填充的過程

BOOL CHttpUploadFiles::GetData( LPVOID lpBuffer, DWORD dwBufferSize, DWORD& dwWrite )
{if ( m_strUploadFileHeaderUTF8.empty() ) {return FALSE;}if ( EHeader == m_ReadInfo.eType ) {if ( FALSE == ReadFromString(m_strUploadFileHeaderUTF8, lpBuffer, dwBufferSize, m_ReadInfo.dwReadIndex, dwWrite ) ) {return FALSE;}m_ReadInfo.dwReadIndex += dwWrite;if ( m_ReadInfo.dwReadIndex == m_strUploadFileHeaderUTF8.length() ) {m_ReadInfo.eType = EFile;m_ReadInfo.dwReadIndex = 0;return TRUE;}}else if ( EFile == m_ReadInfo.eType ){OVERLAPPED ov;memset(&ov, 0, sizeof(ov));ov.Offset = m_ReadInfo.dwReadIndex;HANDLE hFile = CreateFile( m_wstrFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );BOOL bContinue = FALSE;DWORD dwFileSize = 0;do {if ( INVALID_HANDLE_VALUE == hFile ) {dwWrite = 0;break;}LARGE_INTEGER lgFileSize = {0};if ( FALSE == GetFileSizeEx(hFile, &lgFileSize) ) {break;}if ( FALSE == ReadFile(hFile, lpBuffer, dwBufferSize, &dwWrite, &ov)) {break;}dwFileSize = lgFileSize.LowPart;bContinue = TRUE;} while (0);if ( INVALID_HANDLE_VALUE != hFile ) {CloseHandle(hFile);hFile = NULL;}m_ReadInfo.dwReadIndex += dwWrite;if ( m_ReadInfo.dwReadIndex == dwFileSize ) {m_ReadInfo.dwReadIndex = 0;bContinue = FALSE;}return bContinue;}return TRUE;
}

? ? ? ? 最后我們看下數據段的發送

std::wstring CHttpUploadFiles::GenerateExtInfo( const VecStParam& VecExtInfo )
{std::wstring wstrInfo = L"\r\n";for ( VecStParamCIter it = VecExtInfo.begin(); it != VecExtInfo.end(); it++ ) {wstrInfo += m_wstrBlockStart;wstrInfo += L"Content-Disposition:form-data;";wstrInfo += L"name=";wstrInfo += L"\"";wstrInfo += it->wstrKey;wstrInfo += L"\"";wstrInfo += L"\r\n\r\n";wstrInfo += it->wstrValue;wstrInfo += L"\r\n";}wstrInfo += m_wstrBlockEnd;return wstrInfo;
}

? ? ? ? 數據段也要使用分隔符分隔。并用固定的格式傳送參數pk1=pv1&pk2=pk2

----h1o9n8e6y6k6k(用\r\n)
Content-Disposition: form-data; name="Data";filename="uploadfilename.bin"(用\r\n)
Content-Type:application/octet-stream(用\r\n\r\n)文件內容
----h1o9n8e6y6k6k(用\r\n)
Content-Disposition:form-data;name="pk1"(用\r\n\r\n)pv1
----h1o9n8e6y6k6k(用\r\n)
Content-Disposition:form-data;name="pk2"(用\r\n\r\n)pv2
----h1o9n8e6y6k6k--(用\r\n)

? ? ? ? 至此,文件傳輸主要流程講完了,最后還要提一句,就是在Post之前,我們要獲取正確的發送包的大小。

DWORD CHttpUploadFiles::GetDataSize()
{if ( m_strUploadFileHeaderUTF8.empty() ) {return 0;}DWORD dwFileSize = 0;HANDLE hFile = CreateFile( m_wstrFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );do {if ( INVALID_HANDLE_VALUE == hFile ) {break;}LARGE_INTEGER lgFileSize = {0};if ( FALSE == GetFileSizeEx(hFile, &lgFileSize) ) {break;}if ( lgFileSize.HighPart > 0 || lgFileSize.LowPart > 0x00FFFFFF) {// 限制大小break;}dwFileSize = lgFileSize.LowPart;}while(0);if ( INVALID_HANDLE_VALUE != hFile ) {CloseHandle(hFile);hFile = NULL;}DWORD dwDataSize = 0;if ( 0 != dwFileSize ) {dwDataSize = dwFileSize + m_strUploadFileHeaderUTF8.length();}return dwDataSize;
}

? ? ? ? HTTP三種方式講解結束。附上對應的代碼。

? ? ? ? 在百度云盤上的代碼的鏈接:http://pan.baidu.com/s/1i3DZEol 密碼:2em8

? ? ? ? 再次強烈建議,請看新版本《實現HTTP協議Get、Post和文件上傳功能——使用WinHttp接口實現》《實現HTTP協議Get、Post和文件上傳功能——使用libcurl接口實現》。

總結

以上是生活随笔為你收集整理的使用WinHttp接口实现HTTP协议Get、Post和文件上传功能的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。