Webcc 是我基于 Boost Asio 开发的轻量级 C++ HTTP 程序库,同时支持客户端与服务端。 html
几年前,它只支持简单的 SOAP 调用(名字叫 cSoap),结果慢慢演化成了一个纯粹的 HTTP 程序库,目前有 250 屡次代码提交,还在不断完善,且应用在了咱们公司的产品中,击败了 Qt 的 QtHttpServer。几乎是 C++ 最好用最完善的 HTTP 程序库了。python
编译指南,目前只有英文版。ios
代码仓库: https://github.com/sprinfall/webcc。请认准连接,其余人 fork 的仓库,都不是最新的。git
功能概述github
数据串流 (Streaming)web
先来看一个完整的例子:正则表达式
#include <iostream> #include "webcc/client_session.h" #include "webcc/logger.h" int main() { // 首先配置日志输出(到控制台/命令行) WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); // 建立会话 webcc::ClientSession session; try { // 发起一个 HTTP GET 请求 auto r = session.Send(webcc::RequestBuilder{}. Get("http://httpbin.org/get") ()); // 输出响应数据 std::cout << r->data() << std::endl; } catch (const webcc::Error& error) { // 异常处理 std::cerr << error << std::endl; } return 0; }
如你所见,这里经过一个辅助类 RequestBuilder
,串联起各类参数,最后再生成一个请求对象。注意不要漏了最后的 ()
操做符。数据库
经过 Query()
能够方便地指定 URL 查询参数:编程
session.Send(webcc::RequestBuilder{}. Get("http://httpbin.org/get"). Query("key1", "value1").Query("key2", "value2") ());
要添加额外的头部也很简单:json
session.Send(webcc::RequestBuilder{}. Get("http://httpbin.org/get"). Header("Accept", "application/json") ());
访问 HTTPS 和访问 HTTP 没有差异,对用户是透明的:
session.Send(webcc::RequestBuilder{}.Get("https://httpbin.org/get")());
注意:对 HTTPS/SSL 的支持,须要启用编译选项 WEBCC_ENABLE_SSL
,也会依赖 OpenSSL。
列出 GitHub 公开事件 (public events) 也不是什么难题:
auto r = session.Send(webcc::RequestBuilder{}. Get("https://api.github.com/events") ());
而后,你能够把 r->data()
解析成 JSON 对象,随便用个什么 JSON 程序库。
我在示例程序里用的是 jsoncpp,可是 Webcc 自己并不理解 JSON,用什么 JSON 程序库,彻底是你本身的选择。
RequestBuilder
本质上是为了解决 C++ 没有“键值参数”的问题,它提供了不少函数供你定制请求的样子。
为了列出一个受权的 (authorized) GitHub 用户的“粉丝” (followers),要么使用 Basic 认证:
session.Send(webcc::RequestBuilder{}. Get("https://api.github.com/user/followers"). AuthBasic(login, password) // 应该替换成具体的帐号、密码 ());
要么使用 Token 认证:
session.Send(webcc::RequestBuilder{}. Get("https://api.github.com/user/followers"). AuthToken(token) // 应该替换成具体合法的 token ());
尽管 持久链接 (Keep-Alive) 这个功能不错,你也能够手动关掉它:
auto r = session.Send(webcc::RequestBuilder{}. Get("http://httpbin.org/get"). KeepAlive(false) // 不要 Keep-Alive ());
其余 HTTP 请求的 API 跟 GET 并没有太多差异。
POST 请求须要一个“体” (body),就 REST API 来讲一般是一个 JSON 字符串。让咱们 POST 一个 UTF-8 编码的 JSON 字符串:
session.Send(webcc::RequestBuilder{}. Post("http://httpbin.org/post"). Body("{'name'='Adam', 'age'=20}").Json().Utf8() ());
Webcc 能够把大型的响应数据串流到临时文件,串流在下载文件时特别有用。
auto r = session.Send(webcc::RequestBuilder{}. Get("http://httpbin.org/image/jpeg") (), true); // stream = true // 把串流的文件移到目标位置 r->file_body()->Move("./wolf.jpeg");
不光下载,上传也能够串流:
auto r = session.Send(webcc::RequestBuilder{}. Post("http://httpbin.org/post"). File(path) // 应该替换成具体的文件路径 ());
这个文件在 POST 时,不会一次加载到内存,而是读一块数据发一块数据,直到发送完。
注意,Content-Length
头部仍是会设置为文件的真实大小,不一样于 Transfer-Encoding: chunked
的分块数据形式。
更多示例和用法,请参考 examples 目录。
下面是个 Hello, World!
级别的服务程序。
程序运行后,打开浏览器,输入 http://localhost:8080
,页面显示 Hello, World!
。
class HelloView : public webcc::View { public: webcc::ResponsePtr Handle(webcc::RequestPtr request) override { if (request->method() == "GET") { return webcc::ResponseBuilder{}.OK().Body("Hello, World!")(); } return {}; } }; int main() { try { webcc::Server server(8080); server.Route("/", std::make_shared<HelloView>()); server.Run(); } catch (const std::exception&) { return 1; } return 0; }
简单解释一下。一个服务器 (server) 对应多个视图 (view),不一样的视图对应不一样的资源,视图经过 URL 路由,且 URL 能够为正则表达式。
完整代码请见 examples/hello_world_server。
下面看一个更复杂的例子。
假定你想建立一个关于书的服务,提供下面这些 REST API:
这是一组典型的 CRUD 操做。
前两个操做经过 BookListView
实现:
ListView,DetailView 的命名方式,参考了 Django REST Framework。ListView 针对一列资源,DetailView 针对单个资源。
class BookListView : public webcc::View { public: webcc::ResponsePtr Handle(webcc::RequestPtr request) override { if (request->method() == "GET") { return Get(request); } if (request->method() == "POST") { return Post(request); } return {}; } private: // 查询书 webcc::ResponsePtr Get(webcc::RequestPtr request); // 添加一本新书 webcc::ResponsePtr Post(webcc::RequestPtr request); };
其余操做经过 BookDetailView
实现:
class BookDetailView : public webcc::View { public: webcc::ResponsePtr Handle(webcc::RequestPtr request) override { if (request->method() == "GET") { return Get(request); } if (request->method() == "PUT") { return Put(request); } if (request->method() == "DELETE") { return Delete(request); } return {}; } protected: // 获取一本书的详情 webcc::ResponsePtr Get(webcc::RequestPtr request); // 更新一本书的信息 webcc::ResponsePtr Put(webcc::RequestPtr request); // 删除一本书 webcc::ResponsePtr Delete(webcc::RequestPtr request); };
咱们挑一个函数出来看一下吧:
webcc::ResponsePtr BookDetailView::Get(webcc::RequestPtr request) { if (request->args().size() != 1) { // NotFound (404) 意味着 URL 所指定的资源没有找到。 // 这里用 BadRequest (400) 应该也是合理的。 // 不过,后面能够看到,这个视图匹配了 "/books/(\\d+)" 这个 URL,参数确定不会有问题的。 // 因此这里的错误处理,只是出于防范,和编程的严谨。 // Webcc 没有对 URL 参数作强类型的处理,那么代码写起来太复杂了。 return webcc::ResponseBuilder{}.NotFound()(); } const std::string& book_id = request->args()[0]; // 经过 ID 找到这本书,好比,从数据库里。 // ... if (<没找到>) { return webcc::ResponseBuilder{}.NotFound()(); } // 把这本书转换成 JSON 字符串,并设为响应数据。 return webcc::ResponseBuilder{}.OK().Data(<JsonStringOfTheBook>). Json().Utf8()(); }
最后一步,把 URLs 路由到特定的视图,而后开始运行:
int main(int argc, char* argv[]) { // ... try { webcc::Server server(8080); server.Route("/books", std::make_shared<BookListView>(), { "GET", "POST" }); // ID 经过正则表达式匹配出来 server.Route(webcc::R("/books/(\\d+)"), std::make_shared<BookDetailView>(), { "GET", "PUT", "DELETE" }); // 开始运行(注意:阻塞调用) server.Run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return 1; } return 0; }
完整实现请见 examples/rest_book_server.cc。