一、什麼是Http Client
Http協議,是全網際網路共同的語言,而Http Client,可以說是我們需要從網際網路世界獲取資料的最基本方法,它本質上是一個URL到一個網頁的轉換過程。而有了基本的Http客戶端功能,再搭配上我們想要的規則和策略,上至內容檢索下至資料分析都可以實現了。
繼上一次介紹用Workflow可以10行C++程式碼實現一個高效能Http伺服器,今天繼續給大家用C++實現一個高效能的Http客戶端也同樣很簡單!
// [http_client.cc]
#include "stdio.h"
#include "workflow/HttpMessage.h"
#include "workflow/WFTaskFactory.h"
int main (int argc, char *argv[])
{
const char *url = "https://github.com/sogou/workflow";
WFHttpTask *task = WFTaskFactory::create_http_task (url, 2, 3,
[](WFHttpTask * task) {
fprintf(stderr, "%s %s %s\r\n",
task->get_resp()->get_http_version(),
task->get_resp()->get_status_code(),
task->get_resp()->get_reason_phrase());
});
task->start();
getchar(); // press "Enter" to end.
return 0;
}
只要安裝好了Workflow,以上程式碼即可以通過以下命令編譯出一個簡單的http_client:
g++ -o http_client http_client.cc --std=c++11 -lworkflow -lssl -lcrypto -lpthread
根據Http協議,我們執行這個可執行程式 ./http_client
,就會得到以下內容:
HTTP/1.1 200 OK
同理,我們還可以通過其他api來獲得返回的其他Http header和Http body,一切內容都在這個 WFHttpTask
中。而因為Workflow是個非同步排程框架,因此這個任務發出之後,不會阻塞當前執行緒,外加內部自帶的連線複用,從根本上保證了我們的Http Client的高效能。
接下來給大家詳細講解一下原理~
二、請求的過程
1. 建立Http任務
上述demo可以看到,請求是通過發起一個Workflow的Http非同步任務來實現的,建立任務的介面如下:
WFHttpTask *create_http_task(const std::string& url,
int redirect_max, int retry_max,
http_callback_t callback);
第一個引數就是我們要請求的URL。對應的,在一開始的示例中,我們的重定向次數redirect_max是2次,而重試次數retry_max是3次。第四個引數是一個回撥函式,示例中我們用了一個lambda,由於Workflow的任務都是非同步的,因此我們處理結果這件事情是被動通知我們的,結果回來就會調起這個回撥函式,格式如下:
using http_callback_t = std::function<void (WFHttpTask *)>;
2. 填寫header併發出
我們的網路互動無非是請求-回覆,對應到Http Client上,在我們建立好了task之後,我們有一些時機是處理請求的,在Http協議裡,就是在header裡填好協議相關的事情,比如我們可以通過Connection來指定希望得到建立Http的長連線,以節省下次建立連線的耗時,那麼我們可以把Connection設定為Keep-Alive。示例如下:
protocol::HttpRequest *req = task->get_req();
req->add_header_pair("Connection", "Keep-Alive");
task->start();
最後我們會把設定好請求的任務,通過 task->start();
發出。最開始的 http_client.cc
示例中,有一個 getchar();
語句,是因為我們的非同步任務發出後是非阻塞的,當前執行緒不暫時停住就會退出,而我們希望等到回撥函式回來,因此我們可以用多種暫停的方式。
3. 處理返回結果
一個返回結果,根據Http協議,會包含三部分:訊息行、訊息頭header、訊息正文body。如果我們想要獲取body,可以這樣:
const void *body;
size_t body_len;
task->get_resp()->get_parsed_body(&body, &body_len);
三、高效能的基本保證
我們使用C++來寫Http Client,最香的就是可以利用其高效能。Workflow對高併發是如何保證的呢?其實就兩點:
- 純非同步;
- 連線複用;
前者是對執行緒資源的重複利用、後者是對連線資源的重複利用,這些框架層級都為使用者管理好了,充分減少開發者的心智負擔。
1. 非同步排程模式
同步和非同步的模式直接決定了我們的Http Client可以有多大的併發度。為什麼呢?通過下圖可以先看看同步框架發起三個Http任務,執行緒模型是怎樣的:
網路延遲往往非常大,如果我們在同步等待任務回來的話,執行緒就會一直被佔用。這時候我們需要看看非同步框架是如何實現的:
如圖所示,只要任務發出之後,執行緒即可做其他事情,我們傳入了一個回撥函式做非同步通知,因此等任務的網路回覆收完之後,再讓執行緒執行這個回撥函式即可拿到Http請求的結果,期間多個任務併發出去的時候,執行緒是可以複用的,輕鬆達到幾十萬的QPS併發度。
2. 連線複用
我們剛才有提到,只要我們建立了長連線,即可提高效率。為什麼呢?因為框架對連線有複用。我們先來看看如果一個請求就建立一個連線,會是什麼樣的情況:
很顯然,佔用大量的連線是對系統資源的浪費,而且每次都要做connect以及close是非常耗時的,除了TCP常見的握手以外,許多應用層協議建立連線的過程也會相對複雜。但使用Workflow就不會有這樣的煩惱,Workflow會在任務發出的時候自動查詢當前可以複用的連線,如果沒有才會自動建立,完全不需要開發者關心連線如何複用的細節:
3. 解鎖其他功能
當然,除了以上的高效能以外,一個高效能的Http Client往往還有許多其他的需求,這裡可以結合實際情況與大家分享:
- 結合workflow的串並聯任務流,實現超大規模並行抓取;
- 按順序或者按指定速度請求某個站點的內容,避免請求過猛被封禁;
- Http Client遇到redirect可以自動幫我做跳轉,一步到位請求到最終結果;
- 希望通過proxy代理訪問
HTTP
與HTTPS
資源;
以上這些需求,要求框架對於Http任務的編排有超高的靈活性,以及對實際需求(比如redirect、ssl代理等功能)有非常接地氣的支援,這些Workflow都已經實現。
專案地址
https://github.com/sogou/workflow
歡迎使用 workflow 並 star 支援一下!