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。