使用 boost::asio 讀取網頁資料


好像很久沒寫 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() 裡面所做的事,首先就是先呼叫 iostreamconnect() 函式、指定以 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 可以直接提供就更好了…)

廣告

關於 Heresy
https://kheresy.wordpress.com

5 Responses to 使用 boost::asio 讀取網頁資料

  1. IrishBAM says:

    >> auto buf = sStream.rdbuf();

    在之前公司被 auto 搞過, 我記得同事說我用 auto 去接的 pointer 出了 scope 被 delete 掉. XD

    按讚數

    • Heresy says:

      有這種事!?可以給個範例嗎?想試試看

      按讚數

      • IrishBAM@gmail.com says:

        弄不出來 XD
        等一下再來找找看. 為了這個我們還決定 auto 只用在 iterator

        按讚數

        • IrishBAM says:

          還是忽視這則回覆吧 XD
          看來不是他在臭蓋就是遇到甚麼奇怪的 bug 或接到 smart ptr 了

          按讚數

          • Heresy says:

            如果是 smart_ptr,看狀況,可能本來就該砍掉啊 XD

            還好試不出來,不然 Heresy 一堆程式要改了

            按讚數

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s

%d 位部落客按了讚: