项目Github主页 Coke
在这个时间点开发本项目,有以下几点考虑
- 常用的编译器对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
的其他细节下次再分享。
本系列文章同步发布于个人网站和知乎专栏,转载请注明来源,以便读者及时获取最新内容及勘误。
本文由kedixa发表于个人博客,
转载请注明作者及出处。
本文链接:https://blog.kedixa.top/2023/coke-easy-http/