動態

詳情 返回 返回

Coke(二):便捷地發起Http請求 - 動態 詳情

Coke項目Github主頁。

在這個時間點開發本項目,有以下幾點考慮

  1. 常用的編譯器對C++ 20的支持已經逐步完善,本項目依賴於GCC >= 11Clang >= 15
  2. 常用的操作系統發行版支持了新編譯器,例如CentOS Stream 8Ubuntu 22.04Fedora 38
  3. 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::HttpHeaderCursorcoke::HttpChunkCursorcoke::http_body_view的使用方法,藉助這些組件,遍歷Http的消息內容也是非常方便的。

需要注意的是,這裏提到的view(視圖)都是coke::HttpResponse resp中內容的引用,會在resp發生修改或者銷燬之後不可用,使用時需注意生命週期等問題。

coke::sync_wait常用於在主函數同步等待一組異步任務結束,在協程函數中需要用co_await來等待異步任務返回,而不可以使用同步的方式等待,以免導致阻塞。關於同步/異步等待的內容後續會單獨分享。

在C++ Workflow中,創建Http任務時可通過指定redirect_max參數來指定重定向的次數,這會在一次任務中自動實現重定向,調用方直接得到最終結果,然而在實際應用中有些場景需要獲得重定向的中間結果。例如:

  1. Http請求可能被劫持並重定向到惡意網站,需要分析重定向後的url來決定是否需要繼續請求
  2. 重定向到其他域名時,未根據重定向後的Host重置Cookie,會導致信息泄露或新請求失敗
  3. 發生環形重定向時無法感知

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的其他細節下次再分享。

本系列文章同步發佈於個人網站和知乎專欄,轉載請註明來源,以便讀者及時獲取最新內容及勘誤。

user avatar sherlocked93 頭像 ZhongQianwen 頭像 samhou 頭像 u_16231477 頭像 starrocks 頭像 binghe001 頭像 hedzr 頭像 seazhan 頭像 manxisuo 頭像 hello_5adf4e51b4f3e 頭像 aixiaodewulongcha_ehoerm 頭像 feixianghelanren 頭像
點贊 19 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.