上一篇文章通过几个示例介绍了如何使用Coke便捷地发起Http请求,本文延续上一个话题,将coke::HttpClient的功能详细地介绍一下。

C++ Workflow中,Http任务通常通过工厂函数创建,并且可以指定重试次数等参数。而在Coke中可以通过coke::HttpClient来创建Http任务。首先介绍一下与任务相关的参数

struct HttpClientParams {
    // 当请求失败时,支持自动进行重试操作,默认不重试
    int retry_max           = 0;
    // 连接创建完成后,将请求完全发出的超时时间,默认不超时,单位毫秒
    int send_timeout        = -1;
    // 请求发出后,客户端接收到完整回复的超时时间,默认不超时
    int receive_timeout     = -1;
    // 连接保活时间,默认60秒
    int keep_alive_timeout  = 60 * 1000;

    // Http任务在框架内自动重定向的最大次数,默认为0
    int redirect_max        = 0;
    // 通过Http代理发送请求,若不使用代理则指定为空串
    // 格式为 http://proxy.host:port/ 或 http://user:pass@proxy.host:port/
    std::string proxy;
};

coke::HttpClient目前有三个接口用于创建任务

  1. 使用url创建简单的Http Get任务
  2. 使用urlmethodheaderbody创建任务
  3. 通过coke::HttpRequest准备好请求,再创建任务

具体使用方式参考下述示例代码

#include <iostream>
#include <vector>
#include <string>
#include <utility>

#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;
    }
}

coke::Task<> simple_get(coke::HttpClient &cli, const std::string &url) {
    coke::HttpResult result;
    result = co_await 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);
}

coke::Task<> with_header_body(coke::HttpClient &cli, const std::string &url) {
    coke::HttpResult result;
    std::vector<std::pair<std::string, std::string>> header;
    std::string body;
    header.emplace_back("User-Agent", "coke-http-client");

    result = co_await cli.request(url, "HEAD", header, body);

    if (result.state != coke::STATE_SUCCESS)
        std::cerr << coke::get_error_string(result.state, result.error) << std::endl;
    else
        show_response(result.resp);
}

coke::Task<> with_req(coke::HttpClient &cli, const std::string &url) {
    coke::HttpResult result;
    coke::HttpRequest req;

    req.set_method("GET");
    req.set_request_uri("/");
    req.set_http_version("HTTP/1.1");
    req.set_header_pair("User-Agent", "coke-http-client");
    req.append_output_body("");

    result = co_await cli.request(url, std::move(req));

    if (result.state != coke::STATE_SUCCESS)
        std::cerr << coke::get_error_string(result.state, result.error) << std::endl;
    else
        show_response(result.resp);
}

void http_cli(const std::string &url, const std::string &proxy) {
    coke::HttpClientParams params;
    params.retry_max = 2;
    params.redirect_max = 2;
    params.proxy = proxy;

    // 使用指定的参数创建客户端
    coke::HttpClient cli(params);

    // 依次发起Http任务
    coke::sync_wait(simple_get(cli, url));
    coke::sync_wait(with_header_body(cli, url));
    coke::sync_wait(with_req(cli, url));

    // 也可以同时发起多个任务,但展示结果的函数没有互斥锁保护,结果可能错乱
    //coke::sync_wait(
    //    simple_get(cli),
    //    with_header_body(cli),
    //    with_req(cli)
    //);
}

// ./http_client url [proxy]
int  main(int argc, char *argv[]) {
    std::string url{"https://sogou.com/"};
    std::string proxy;

    if (argc > 1 && argv[1])
        url.assign(argv[1]);
    if (argc > 2 && argv[2])
        proxy.assign(argv[2]);

    http_cli(url, proxy);

    return  0;
}

目前的代理仅支持http代理,而http代理又有两种模式

  1. 当被请求的url scheme为http时,使用普通代理模式,将Http消息的request uri部分改写为被请求的url,发送到代理服务器
  2. 当被请求的url scheme为https时,先向代理服务器发起CONNECT请求建立隧道,再继续发起正常的请求

对于模式1,因为不是由原生的C++ WorkflowHttpTask支持的功能,所以重定向功能会被禁用,且根据上一篇文章的讨论,使用coke可以很便捷地实现重定向的功能。

实际上,coke提供的coke::HttpClient是封装了workflow的Http任务的语法糖,它本身并不维护任务的状态等信息。即使不使用coke::HttpClient,熟悉C++ Workflow的同学也可以手动创建原生的HttpTask并结合coke提供的组件以协程的方式发起任务,但由于会带来关于生命周期的一系列问题,此处不展开讨论,后续会补充到coke的项目文档当中去。

本系列文章同步发布于个人网站知乎专栏,转载请注明来源,以便读者及时获取最新内容及勘误。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注