一、什麼是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 支援一下!