Coke項目Github主頁。
在這個時間點開發本項目,有以下幾點考慮
- 常用的編譯器對C++ 20的支持已經逐步完善,本項目依賴於
GCC >= 11或Clang >= 15 - 常用的操作系統發行版支持了新編譯器,例如
CentOS Stream 8、Ubuntu 22.04、Fedora 38等 - C++ Workflow使用回調函數的方式組織異步任務,一部分習慣寫同步代碼的用户可能會對此感到困擾,尤其是隨着回調函數個數的增加,其理解難度也會越來越高
藉助C++ 20提供的協程組件,Coke使C++ Workflow的任務以順序的方式編寫,大大簡化了理解的複雜度。Coke項目的一項重要目標就是簡潔、便捷,讓代碼寫起來方便、讀起來方便、跑起來也方便。
下面通過發起Http請求的示例來展示Coke的便捷性
#include <iostream>
#include <string>
#include <string_view>
#include "coke/coke.h"
#include "coke/http_client.h"
#include "coke/http_utils.h"
void show_response(const coke::HttpResponse &resp) {
std::cout << resp.get_http_version() << ' '
<< resp.get_status_code() << ' '
<< resp.get_reason_phrase() << std::endl;
// 使用 coke::HttpHeaderCursor 方便地遍歷所有header
for (const coke::HttpHeaderView &header : coke::HttpHeaderCursor(resp))
std::cout << header.name << ": " << header.value << std::endl;
std::cout << "\n";
// 為了簡潔起見,省略了Http body內容的輸出
if (resp.is_chunked()) {
// 對於Chunk編碼的Http body,可使用coke::HttpChunkCursor來遍歷
for (std::string_view chunk : coke::HttpChunkCursor(resp))
std::cout << "chunk body length " << chunk.size() << std::endl;
}
else {
// 對於非Chunk編碼的Http body,可使用coke::http_body_view函數獲取
std::string_view body_view = coke::http_body_view(resp);
std::cout << "\nbody length " << body_view.size() << std::endl;
}
}
int main(int argc, char *argv[]) {
coke::HttpClient cli;
std::string url;
if (argc > 1 && argv[1])
url.assign(argv[1]);
else
url.assign("http://sogou.com/");
// 使用coke::HttpClient發起GET請求,並同步等待結果
coke::HttpResult result = coke::sync_wait(cli.request(url));
if (result.state != coke::STATE_SUCCESS)
std::cerr << coke::get_error_string(result.state, result.error) << std::endl;
else
show_response(result.resp);
return 0;
}
在上述示例代碼中,主函數通過關鍵的coke::sync_wait(cli.request(url));一句話,完成了Http GET請求的創建、發送以及同步等待結果,充分體現了Coke的便捷性。在show_response函數中,展示了coke::HttpHeaderCursor、coke::HttpChunkCursor、coke::http_body_view的使用方法,藉助這些組件,遍歷Http的消息內容也是非常方便的。
需要注意的是,這裏提到的view(視圖)都是coke::HttpResponse resp中內容的引用,會在resp發生修改或者銷燬之後不可用,使用時需注意生命週期等問題。
coke::sync_wait常用於在主函數同步等待一組異步任務結束,在協程函數中需要用co_await來等待異步任務返回,而不可以使用同步的方式等待,以免導致阻塞。關於同步/異步等待的內容後續會單獨分享。
在C++ Workflow中,創建Http任務時可通過指定redirect_max參數來指定重定向的次數,這會在一次任務中自動實現重定向,調用方直接得到最終結果,然而在實際應用中有些場景需要獲得重定向的中間結果。例如:
- Http請求可能被劫持並重定向到惡意網站,需要分析重定向後的url來決定是否需要繼續請求
- 重定向到其他域名時,未根據重定向後的Host重置Cookie,會導致信息泄露或新請求失敗
- 發生環形重定向時無法感知
coke::HttpClient支持設置redirect_max參數,但實現一個更安全的重定向邏輯依然很簡單
#include <iostream>
#include <string>
#include <string_view>
#include <set>
#include <cstdlib>
#include "coke/coke.h"
#include "coke/http_client.h"
#include "coke/http_utils.h"
bool need_redirect(const coke::HttpResponse &resp, std::string &redirect_url) {
const char *status = resp.get_status_code();
int code = status ? std::atoi(status) : 0;
// 簡單起見,此處僅實現一個極其樸素的檢查重定向的方法,所以可能出現未能正確重定向的情況,
// 嚴謹的方法請參考相關標準
if (code == 301 || code == 302) {
for (const coke::HttpHeaderView &header : coke::HttpHeaderCursor(resp)) {
if (header.name == "Location" || header.name == "location") {
// value也可能是個相對路徑,此處忽略檢查
// header.value是個view,會隨着resp的銷燬變得不可用,所以需要redirect_url拷貝一份
redirect_url = header.value;
return true;
}
}
}
return false;
}
// `show_response`函數內容與上一個示例相同,為了節約篇幅,此處省略
// void show_response(const coke::HttpResponse &resp);
coke::Task<> http_get(std::string url, int max_redirect) {
std::set<std::string> history;
std::string redirect_url;
coke::HttpClient cli;
coke::HttpResult res;
int i = 0;
for (i = 0; i < max_redirect; i++) {
// 記錄重定向歷史,檢查是否有環形重定向
if (history.find(url) != history.end()) {
std::cerr << "Url redirect loop" << std::endl;
co_return;
}
std::cout << "Visit " << url << std::endl;
history.insert(url);
// 使用coke::HttpClient發起GET請求,在協程中使用co_await等待,不可使用同步等待
res = co_await cli.request(url);
if (res.state != coke::STATE_SUCCESS) {
std::cerr << "Request failed url:" << url << std::endl;
co_return;
}
// 檢查是否仍需重定向
if (!need_redirect(res.resp, url))
break;
}
if (i == max_redirect)
std::cerr << "Too many redirect" << std::endl;
else
show_response(res.resp);
}
int main(int argc, char *argv[]) {
std::string url;
if (argc > 1 && argv[1])
url.assign(argv[1]);
else
url.assign("http://sogou.com/");
coke::sync_wait(http_get(url, 5));
return 0;
}
本文通過兩個示例展示了Coke的便捷性,為避免篇幅過長,coke::HttpClient的其他細節下次再分享。
本系列文章同步發佈於個人網站和知乎專欄,轉載請註明來源,以便讀者及時獲取最新內容及勘誤。