Webcc: 輕量級 C++ HTTP 程序庫

Webcc 是我基於 Boost Asio 開發的輕量級 C++ HTTP 程序庫,同時支持客戶端與服務端。 html

幾年前,它只支持簡單的 SOAP 調用(名字叫 cSoap),結果慢慢演化成了一個純粹的 HTTP 程序庫,目前有 250 屢次代碼提交,還在不斷完善,且應用在了咱們公司的產品中,擊敗了 Qt 的 QtHttpServer。幾乎是 C++ 最好用最完善的 HTTP 程序庫了。python

編譯指南,目前只有英文版。ios

代碼倉庫: https://github.com/sprinfall/webcc。請認準連接,其餘人 fork 的倉庫,都不是最新的。git

功能概述github

  • 跨平臺: Windows,Linux 及 MacOS
  • 簡單好用的客戶端 API,借鑑了 Python 的 requests 程序庫
  • 支持 SSL/HTTPS,依賴 OpenSSL(可選)
  • 支持 GZip 壓縮,依賴 Zlib(可選)
  • 持久鏈接 (Keep-Alive)
  • 數據串流 (Streaming)web

    • 客戶端:能夠上傳、下載大型文件
    • 服務端:能夠伺服、接收大型文件
  • 支持 Basic & Token 認證/受權
  • 超時控制(目前僅客戶端)
  • 代碼遵照 Google C++ Style
  • 自動化測試和單元測試保證質量
  • 無內存泄漏(VLD 檢測)

客戶端 API

先來看一個完整的例子:正則表達式

#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 目錄。

服務端 API

Hello, World!

下面是個 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

相關文章
相關標籤/搜索