MFC 處理 HTTP 請求的基本方法



1 MFC 處理 HTTP 請求的基本方法

1.1 配置本地的 HTTP 服務器

爲方便測試,可以先配置一個本地的 HTTP 服務器,根據各種需要進行定製。

我在這裏,用 JSP 定製了一個基本的 HTML 表單程序,分爲 index.jsp 和 RequestObjectInJSP.jsp 兩個文件。其中,index.jsp 用來提供表單程序,方便測試 RequestObjectInJSP.jsp 這個表單處理文件。

爲了減少在測試時期網絡通信的影響,強烈建議搭建一個本地的 Web 服務器。

1.2 MFC 發起 HTTP 請求的基本方法

用 CInternetSession 來發起 Http 請求,需要包含頭文件:

#include <afxinet.h>

MFC 發起 HTTP 請求的邏輯,和用 WinINet 函數集 的整體過程類似,主要的步驟在 Steps in a Typical HTTP Client Application 有詳細的描述。

Retrieving a file via. HTTP 一文也對 MFC 發起 HTTP 請求有着非常詳細的介紹。

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;

調試上面這段代碼的時候,特別要注意以下幾點:

  1. CHttpConnection::GetHttpConnection() 裏第一參數,填寫的應該是類似 www.yahoo.com 這樣的根域名,如果帶上 http:// 或是子路徑,好像均會出錯。
  2. CHttpFile::OpenRequest() 的第一個和第二個參數很重要,會影響是否能連接,尤其是第二個參數,要輸入正確的 URI 路徑;
  3. 在 CHttpFile::SendRequest() 之後,一定要用 CHttpFile::QueryInfoStatusCode() 來獲得請求的狀態碼,從而判斷是否正確獲得了 http 數據;

    Http 的狀態碼主要有以下幾類:

    Group Meaning
  4. 200-299 Success
  5. 300-399 Information
  6. 400-499 Request error
  7. 500-599 Server error

    更詳細的代碼參數:

    Status code Meaning
  8. 200 URL located, transmission follows
  9. 400 Unintelligible request
  10. 404 Requested URL not found
  11. 405 Server does not support requested method
  12. 500 Unknown server error
  13. 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

當你用文本編輯器打開返回的文件時,會顯示如下的錯誤提示:

./images/CopyError-waterlin.png

這個時候,雖然上面的 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,從而保證收到的消息一樣。

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