1 MFC 處理 HTTP 請求的基本方法
1.1 配置本地的 HTTP 服務器
爲方便測試,可以先配置一個本地的 HTTP 服務器,根據各種需要進行定製。
我在這裏,用 JSP 定製了一個基本的 HTML 表單程序,分爲
index.jsp 和 RequestObjectInJSP.jsp 兩個文件。其中,index.jsp 用來提供表單程序,方便測試 RequestObjectInJSP.jsp 這個表單處理文件。
爲了減少在測試時期網絡通信的影響,強烈建議搭建一個本地的 Web 服務器。
1.3 用 MFC 發起 HTTP GET 請求
Get 服務類別,估計是 HTML 裏最常用的,平時瀏覽網頁用的就是這種。下面是用 GET 的方法來請求某個網頁的內容,代碼如下:
//通過 http GET 協議來獲取並保存文件
CInternetSession
session;session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1000 * 20);
session.SetOption(INTERNET_OPTION_CONNECT_BACKOFF, 1000);
session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1);
CHttpConnection*
pConnection = session.GetHttpConnection(TEXT("localhost"),(INTERNET_PORT)8080);
CHttpFile*
pFile = pConnection->OpenRequest(
CHttpConnection::HTTP_VERB_GET, TEXT("/Practice/index.jsp"));
CString
szHeaders = L"Accept: audio/x-aiff, audio/basic, audio/midi,\
audio/mpeg, audio/wav, image/jpeg, image/gif, image/jpg, image/png,\
image/mng, image/bmp, text/plain, text/html, text/htm\r\n";
pFile->AddRequestHeaders(szHeaders);
pFile->SendRequest();
DWORD
dwRet;pFile->QueryInfoStatusCode(dwRet);
if(dwRet != HTTP_STATUS_OK)
{
CString
errText;
errText.Format(L"POST出錯,錯誤碼:%d", dwRet);
AfxMessageBox(errText);
}else{
int
len = pFile->GetLength();
char
buf[2000];
int
numread;
CString
filepath;
CString
strFile = L"response.txt";
filepath.Format(L".\\%s", strFile);
CFile
myfile( filepath,
CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);
while ((numread = pFile->Read(buf,sizeof(buf)-1))
> 0) {
buf[numread] = '\0';
strFile += buf;
myfile.Write(buf, numread);
}
myfile.Close();
}
session.Close();
pFile->Close();
delete pFile;
調試上面這段代碼的時候,特別要注意以下幾點:
- CHttpConnection::GetHttpConnection() 裏第一參數,填寫的應該是類似 www.yahoo.com 這樣的根域名,如果帶上 http:// 或是子路徑,好像均會出錯。
- CHttpFile::OpenRequest() 的第一個和第二個參數很重要,會影響是否能連接,尤其是第二個參數,要輸入正確的 URI 路徑;
- 在 CHttpFile::SendRequest() 之後,一定要用 CHttpFile::QueryInfoStatusCode() 來獲得請求的狀態碼,從而判斷是否正確獲得了 http 數據;
Http 的狀態碼主要有以下幾類:
Group Meaning - 200-299 Success
- 300-399 Information
- 400-499 Request error
- 500-599 Server error
更詳細的代碼參數:
Status code Meaning - 200 URL located, transmission follows
- 400 Unintelligible request
- 404 Requested URL not found
- 405 Server does not support requested method
- 500 Unknown server error
- 503 Server capacity reached
1.4 用 MFC 發起 HTTP Post 請求
用 MFC 發起 HTTP Post 請求,主要流程和 MFC HTTP Get 代碼一樣,以下是示例代碼:
//通過 http POST 協議來發送命令給服務器
CInternetSession
session;
session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, 1000 * 20);
session.SetOption(INTERNET_OPTION_CONNECT_BACKOFF, 1000);
session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1);
CHttpConnection*
pConnection = session.GetHttpConnection( TEXT("localhost"), (INTERNET_PORT)8080);
CHttpFile*
pFile = pConnection->OpenRequest(
CHttpConnection::HTTP_VERB_POST, TEXT("/Practice/RequestObjectInJSP.jsp"),
NULL, 1,
NULL, TEXT("HTTP/1.1"), INTERNET_FLAG_RELOAD);
//需要提交的數據
CString
szHeaders = L"Content-Type: application/x-www-form-urlencoded;";
//下面這段編碼,則是可以讓服務器正常處理
CHAR*
strFormData =
"username=WaterLin&password=TestPost";
pFile->SendRequest( szHeaders, szHeaders.GetLength(), (LPVOID)strFormData, strlen(strFormData));
DWORD
dwRet;
pFile->QueryInfoStatusCode(dwRet);
if(dwRet != HTTP_STATUS_OK){
CString
errText;
errText.Format(L"POST出錯,錯誤碼:%d", dwRet);
AfxMessageBox(errText);
}else{
int
len = pFile->GetLength();
char
buf[2000];
int
numread;
CString
filepath;
CString
strFile = L"result.html";
filepath.Format(L".\\%s", strFile);
CFile
myfile(filepath,
CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);
while ((numread = pFile->Read(buf,sizeof(buf)-1))
> 0)
{
buf[numread] = '\0';
strFile += buf;
myfile.Write(buf, numread);
}
myfile.Close();
}
session.Close();
pFile->Close();
delete pFile;
以上的代碼,與 Get 對比起來,唯一的不同在於,提交 CHttpFile::SendRequest() 數據的時候,把表單的數據也帶上了。
2 疑難雜症
2.1 字符編碼,可恨的字符編碼
對於 C/C++ 程序來說,最可恨的事情之一,莫過於字符集的問題了,尤其是在網絡通信的時候,這一問題就顯得更加讓人噁心了。
如果在用 MFC 發起 HTTP Post 請求時,你用的是寬字符集的編碼,比如說,我把用
MFC 發起 HTTP Post 請求裏同樣的幾行代碼,替換成下面這幾句:
CString
szHeaders = L"Content-Type: application/x-www-form-urlencoded;charset=UTF-8";//下面這句,因爲字符集的原因,是無法讓服務器正常處理CString
strFormData = L"username=WaterLin&password=TestPost";pFile->SendRequest( szHeaders, szHeaders.GetLength(),
(LPVOID)(LPCTSTR)strFormData, lstrlen(strFormData));
如果是,在服務器端會解析爲如下這樣:
<br>Parameters:u s e r n a m e
當你用文本編輯器打開返回的文件時,會顯示如下的錯誤提示:
這個時候,雖然上面的 JSP 代碼會輸出
Character Encoding: null
這樣的值,但是服務器卻會把表單內容當成 ISO-8859-1 字符集來處理,從而把表單參數解析爲類似下面的怪胎:
看,這就是把字符集弄混了的下場!
則需要在 HTTP 報頭裏,一定要顯式加上 charset=UTF-8 這樣的約束,比如,在上面的代碼,我就是直接這樣寫的:
CString
szHeaders = L"Content-Type: application/x-www-form-urlencoded;charset=UTF-8";
這樣,服務器在收到你的報文時,就知道,你的 Form 表單內容,是用 UTF-8 來編碼的,它也會用 UTF-8 字符集來解碼你的 request,從而保證收到的消息一樣。