好像很久沒寫 Boost C++ Libraries 的介紹了?轉眼間,他的版本也已經推進到 1.53.0 了。這篇,主要是針對 Heresy 這段期間,在使用 Boost 的 ASIO 這個函式庫(官網),來讀取網頁資料的一些紀錄。
Boost ASIO 這個函式庫,是一個網路、以及 low-level IO 的函式庫,它的功能滿多的,而 Heresy 這邊主要是針對他的網路傳輸功能,更嚴謹地講,是針對他的 ip::tcp::iostream 的 Http Client 這個範例(官方程式碼)、來做使用的經驗來做紀錄;基本上不會講整個函式庫,就是單純簡單介紹這個範例、再根據自己的需要做一點延伸而已。
要使用 Boost ASIO 來存取網頁的資料,基本上是要 include boost/asio/ip/tcp.hpp 這個 Header 檔;而主要用操作的物件,則就是 boost::asio::ip::tcp::iostream 這個繼承 STL iostream 的物件。
下面是他基本的設定:
boost::asio::ip::tcp::iostream sStream; sStream.expires_from_now( boost::posix_time::seconds( 60 ) );
首先,就是先建立他的物件,然後再透過 expires_from_now() 這個函式,來設定連線逾時的時間,避免因為網路問題、一直卡在那;這邊是設定讓他的逾時時間是 60 秒,如果 60 秒沒有完成,就會被強制中斷。
接下來,則就是要做連線的動作、送出需要的要求給 HTTP Server 了;在這邊,Heresy 是把「送出要求」這個動作,包成一個 SendRequest() 的函式:
bool SendRequest( const string& rServer, const string& rPath, boost::asio::ip::tcp::iostream& rStream ) { // Establish a connection to the server. rStream.connect( rServer, "http" ); if( !rStream ) return false; // Send the request. rStream << "GET " << rPath << " HTTP/1.0\r\n"; rStream << "Host: " << rServer << "\r\n"; rStream << "Accept: */*\r\n" << "Connection: close\r\n\r\n"; return true; }
這個函式需要三個參數,第一個字串 rServer 是 Http Server 的位置,基本上就是給 domain name 或是 IP,第二個字串 rPath 則是要開啟的網頁,第三個參數則是前面建立的 boost::asio::ip::tcp::iostream 物件。
如果是要開啟 https://kheresy.wordpress.com/2010/10/13/boostcpplibraries/ 這個網頁的話,基本上就是呼叫:
SendRequest( "kheresy.wordpress.com", "/2010/10/13/boostcpplibraries/", sStream);
比較特別的,就是得把 URL 先拆開了。
而 SendRequest() 裡面所做的事,首先就是先呼叫 iostream 的 connect() 函式、指定以 http 的形式、連線到指定的伺服器 rServer。
如果連線成功的話,接下來就可以以 operator<< 的形式,送出 HTTP 的要求。這邊 Heresy 就是照抄 Boost 提供的範例了~基本上,request 也是可以有更多參數,可以視需求自己送自己要的資料(維基百科)。
在這樣送出要求後,如果都正確的話,boost::asio::ip::tcp::iostream 接下來就會收到 Http Server 傳回來的資料;在這邊,首先會收到的,會是 HTTP 固定的 header、然後才會是我們要求的東西(網頁、或檔案)。而處理 Header 的方法,基本上如下:
bool GetHttpHeader( boost::asio::ip::tcp::iostream& rStream ) { // Check that response is OK. string sHttpVersion; rStream >> sHttpVersion; unsigned int uCode; rStream >> uCode; string sMssage; getline( rStream, sMssage ); if( !rStream || sHttpVersion.substr(0, 5) != "HTTP/" ) { return false; } if( uCode != 200 ) { return false; } // Process the response headers, // which are terminated by a blank line. string header; while( getline( rStream, header ) && header != "\r" ){} return true; }
在 HTTP 的通訊協定裡,如果一切正確的話,收到的回覆訊息會是「HTTP/1.1 200 OK」這樣的形式;一開始是 HTTP 通訊協定的版本,然後是一個 HTTP 的狀態碼,代表對這個要求的回覆。所以這邊,就透過 operator>>,來分別取得 HTTP 的版本資訊(sHttpVersio)、以及狀態(uCode),並檢查是否正確,如果不正確,就可以中斷處理了。
而其中,正確的狀態碼,會是 200,其他都算是錯誤;常見的錯誤,包括了找不到檔案的 404、5xx 的伺服器錯誤等等(維基百科)。
雖然 HTTP Server 送回來的 header 長度可能不一定,不過有規定一定要以一個空行還做結束;所以這邊為了把 Header 都處理掉、來處理真正需要的資料,這邊就透過一個迴圈、搭配 getline() 這個函式,來讀取 rStream 接收到的資料,直到讀到一行只有 \r 的空行為止。
不過,這樣的處理方法,其實不算很好,因為這樣基本上是把所有 server 端傳回來的額外訊息都不管、直接扔了…如果要完整的話,最好還是要稍微處理一下;不過對於最簡單的應用來說,這邊的資料基本上是都可以無視就是了。
在把 Header 消化掉後,接下來就是讀取真的實際要用的資料了~
Boost 官方的範例,基本上只有把資料直接輸出 standard output 而已,而實際上,一般在針對網頁內容這類的文字內容、以及像是圖片這類的 binary 內容,應該是會需要不同的處理方法。
像是如果是針對一般網頁內容、這類的文字資料,如果希望以 string 的形式、取得整個檔案的內容的話,基本上可以使用:
stringstream oStream; oStream << sStream.rdbuf(); string sHtml = oStream.str();
這類的方法,來取得整個文字的內容;在執行完後,sHtml 裡面,就會是網頁的原始碼了~
不過可能要注意的是,由於網頁上可能會採用 Big-5、UTF-8 等各式各樣的編碼、來處理多國語言,所以之後如果要做後續處理,可能會需要做編碼的轉換,不過這點在這邊就先不提了。
而如果是圖片這類的 Binary 檔案的話,則可以直接購過 STL 的 ofstream,以 binary 模式開啟、把內容都寫進去:
std::ofstream outfile( rFilename, ios::binary ); if( outfile.is_open() ) { auto buf = sStream.rdbuf(); outfile << buf; outfile.close(); }
這樣的結果,就是會把 HTTP server 回傳的資料,以 binary 的形式、儲存到 rFilename 這個檔案裡。
透過這樣的程式碼,基本上就可以使用 C++ 來讀取網頁、以及 Http Server 上的檔案了~
不過基本上,這樣的寫法並不算很完整,而且也不支援 HTTPS;如果要支援 HTTPS 的話,應該還需要把 SSL 的東西,也整合進來(參考)。
而實際上,使用 Boost ASIO 來做 HTTP client,算是從比較底層、自己慢慢寫上來的,其實算是比較累、比較麻煩的做法;Heresy 現在之所以這樣做的原因,其實只是想減少使用的函式庫數量而已;如果不想費工自己刻的話,比較快的方法,就是直接去使用其他、有專門為了 HTTP 的功能寫的函式庫了~
像是 libcurl(官網),就是一套相當完整的網路通訊協定函式庫;除了 HTTP / HTTPS 外,也支援 FTP、SFTP…等等的其他網路通訊協定,使用起來也會比使用 Boost ASIO 自己寫來的簡單不少。
或者像是 C++ Network Library(cpp-netlib、官網),則就是以 Boost C++ Libraries 為基礎,開發的的高階網路函式庫,使用起來也會相對簡單;不過由於還在開發階段,功能沒有 libcurl 來的完整就是了。而 Heresy 也期待等他開發完成後,可以被整合進 Boost C++ Libraries 裡了。(當然,如果 C++14 可以直接提供就更好了…)
[…] 比如果,想要靠它來寫一個下載檔案的函式,如果先 include 了 Boost 的 ASIO 的話(之前自己的寫法),就會變成下面這種狀況: […]
讚讚
[…] Boost 的話,對他應該不至於太陌生;像是在 Boost::ASIO(參考)裡面,主要就是透過 error_code […]
讚讚
>> auto buf = sStream.rdbuf();
在之前公司被 auto 搞過, 我記得同事說我用 auto 去接的 pointer 出了 scope 被 delete 掉. XD
讚讚
有這種事!?可以給個範例嗎?想試試看
讚讚
弄不出來 XD
等一下再來找找看. 為了這個我們還決定 auto 只用在 iterator
讚讚
還是忽視這則回覆吧 XD
看來不是他在臭蓋就是遇到甚麼奇怪的 bug 或接到 smart ptr 了
讚讚
如果是 smart_ptr,看狀況,可能本來就該砍掉啊 XD
—
還好試不出來,不然 Heresy 一堆程式要改了
讚讚