cpp-httplib - C++ ヘッダーのみの HTTP/HTTPS サーバーおよびクライアント ライブラリ

(A C++ header-only HTTP/HTTPS server and client library)

Created at: 2012-09-22 10:38:32
Language: C++
License: MIT

CPP-httplib

C++11 単一ファイル ヘッダーのみのクロス プラットフォーム HTTP/HTTPS ライブラリ。

セットアップは非常に簡単です。あなたのコードにhttplib.hファイルを含めるだけです!

注: このライブラリは「ブロッキング」ソケット I/O を使用します。「非ブロッキング」ソケットI / Oを持つライブラリを探しているなら、これはあなたが望むものではありません。

簡単な例

サーバー (マルチスレッド)

#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"

// HTTP
httplib::Server svr;

// HTTPS
httplib::SSLServer svr;

svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
  res.set_content("Hello World!", "text/plain");
});

svr.listen("0.0.0.0", 8080);

クライアント

#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"

// HTTP
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");

// HTTPS
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");

auto res = cli.Get("/hi");
res->status;
res->body;

SSL のサポート

SSL サポートは、 で使用できます。 リンクする必要があります。

CPPHTTPLIB_OPENSSL_SUPPORT
libssl
libcrypto

注: cpp-httplib は現在、バージョン 1.1.1 および 3.0 のみをサポートしています。

#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"

// Server
httplib::SSLServer svr("./cert.pem", "./key.pem");

// Client
httplib::Client cli("https://localhost:1234"); // scheme + host
httplib::SSLClient cli("localhost:1234"); // host
httplib::SSLClient cli("localhost", 1234); // host, port

// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");

// Disable cert verification
cli.enable_server_certificate_verification(false);

注: SSL を使用する場合、一部のオペレーティング・システムでは SIGPIPE はメッセージ単位でしか抑制できないため、すべての場合に SIGPIPE を回避することは不可能に思えますが、OpenSSL ライブラリーの内部通信のためにそうさせる方法はありません。プログラムが SIGPIPE で終了しないようにする必要がある場合、唯一の完全に一般的な方法は、SIGPIPE が自分で処理または無視するためのシグナルハンドラを設定することです。

サーバー

#include <httplib.h>

int main(void)
{
  using namespace httplib;

  Server svr;

  svr.Get("/hi", [](const Request& req, Response& res) {
    res.set_content("Hello World!", "text/plain");
  });

  svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
    auto numbers = req.matches[1];
    res.set_content(numbers, "text/plain");
  });

  svr.Get("/body-header-param", [](const Request& req, Response& res) {
    if (req.has_header("Content-Length")) {
      auto val = req.get_header_value("Content-Length");
    }
    if (req.has_param("key")) {
      auto val = req.get_param_value("key");
    }
    res.set_content(req.body, "text/plain");
  });

  svr.Get("/stop", [&](const Request& req, Response& res) {
    svr.stop();
  });

  svr.listen("localhost", 1234);
}

Post
、、およびメソッドもサポートされています。
Put
Delete
Options

ソケットを複数のインターフェイスと使用可能なポートにバインドする

int port = svr.bind_to_any_port("0.0.0.0");
svr.listen_after_bind();

静的ファイル サーバー

// Mount / to ./www directory
auto ret = svr.set_mount_point("/", "./www");
if (!ret) {
  // The specified base directory doesn't exist...
}

// Mount /public to ./www directory
ret = svr.set_mount_point("/public", "./www");

// Mount /public to ./www1 and ./www2 directories
ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search

// Remove mount /
ret = svr.remove_mount_point("/");

// Remove mount /public
ret = svr.remove_mount_point("/public");
// User defined file extension and MIME type mappings
svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");

組み込みのマッピングを次に示します。

延長 MIME タイプ 延長 MIME タイプ
.css テキスト/CSS ティッカー オーディオ/MPEG
.csv テキスト/CSV ウェバ オーディオ/ウェブ
.txt テキスト/プレーン .wav オーディオ/ウェーブ
ティッカー テキスト/VTT .otf フォント/OTF
html, htm テキスト/HTML .ttf フォント/TTF
ティッカー 画像/APNG ウォフ フォント/ウォフ
アビフ 画像/アビフ ウォフ2 フォント/woff2
.bmp 画像/BMP .7z アプリケーション/X-7Z圧縮
.gif 画像/GIF 原子 アプリケーション/アトム+XML
.png 画像/PNG .pdf アプリケーション/PDF
.svg 画像/SVG+XML MJS, JS アプリケーション/ジャバスクリプト
.webp 画像/ウェブ ジェイソン アプリケーション/JSON
.ico 画像/Xアイコン .rss アプリケーション/RSS+XML
.tif 画像/ティフ .tar アプリケーション/X-tar
.tiff 画像/ティフ xhtml, xht application/xhtml+xml
JPEG, JPG 画像/JPEG ティッカー application/xslt+xml
.mp4 ビデオ/MP4 .xml アプリケーション/XML
.mpeg ビデオ/MPEG .gz アプリケーション/gzip
.webm ビデオ/ウェブ .zip アプリケーション/郵便番号
.mp3 オーディオ/MP3 ワズム アプリケーション/WASM

ファイル要求ハンドラー

// The handler is called right before the response is sent to a client
svr.set_file_request_handler([](const Request &req, Response &res) {
  ...
});

注: これらの静的ファイル サーバー メソッドはスレッド セーフではありません。

伐採

svr.set_logger([](const auto& req, const auto& res) {
  your_logger(req, res);
});

エラーハンドラ

svr.set_error_handler([](const auto& req, auto& res) {
  auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
  char buf[BUFSIZ];
  snprintf(buf, sizeof(buf), fmt, res.status);
  res.set_content(buf, "text/html");
});

例外ハンドラー

例外ハンドラーは、ユーザー ルーティング ハンドラーがエラーをスローした場合に呼び出されます。

svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) {
  auto fmt = "<h1>Error 500</h1><p>%s</p>";
  char buf[BUFSIZ];
  try {
    std::rethrow_exception(ep);
  } catch (std::exception &e) {
    snprintf(buf, sizeof(buf), fmt, e.what());
  } catch (...) { // See the following NOTE
    snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
  }
  res.set_content(buf, "text/html");
  res.status = 500;
});

注: 再スローされた例外ポインターのブロックを指定しない場合、キャッチされない例外によってサーバーがクラッシュします。注意してください!

catch (...)

事前ルーティングハンドラー

svr.set_pre_routing_handler([](const auto& req, auto& res) {
  if (req.path == "/hello") {
    res.set_content("world", "text/html");
    return Server::HandlerResponse::Handled;
  }
  return Server::HandlerResponse::Unhandled;
});

ルーティング後のハンドラー

svr.set_post_routing_handler([](const auto& req, auto& res) {
  res.set_header("ADDITIONAL_HEADER", "value");
});

'マルチパート/フォームデータ' POST データ

svr.Post("/multipart", [&](const auto& req, auto& res) {
  auto size = req.files.size();
  auto ret = req.has_file("name1");
  const auto& file = req.get_file_value("name1");
  // file.filename;
  // file.content_type;
  // file.content;
});

コンテンツ レシーバーでコンテンツを受信する

svr.Post("/content_receiver",
  [&](const Request &req, Response &res, const ContentReader &content_reader) {
    if (req.is_multipart_form_data()) {
      // NOTE: `content_reader` is blocking until every form data field is read
      MultipartFormDataItems files;
      content_reader(
        [&](const MultipartFormData &file) {
          files.push_back(file);
          return true;
        },
        [&](const char *data, size_t data_length) {
          files.back().content.append(data, data_length);
          return true;
        });
    } else {
      std::string body;
      content_reader([&](const char *data, size_t data_length) {
        body.append(data, data_length);
        return true;
      });
    }
  });

コンテンツ プロバイダーにコンテンツを送信する

const size_t DATA_CHUNK_SIZE = 4;

svr.Get("/stream", [&](const Request &req, Response &res) {
  auto data = new std::string("abcdefg");

  res.set_content_provider(
    data->size(), // Content length
    "text/plain", // Content type
    [data](size_t offset, size_t length, DataSink &sink) {
      const auto &d = *data;
      sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
      return true; // return 'false' if you want to cancel the process.
    },
    [data](bool success) { delete data; });
});

コンテンツの長さなし:

svr.Get("/stream", [&](const Request &req, Response &res) {
  res.set_content_provider(
    "text/plain", // Content type
    [&](size_t offset, DataSink &sink) {
      if (/* there is still data */) {
        std::vector<char> data;
        // prepare data...
        sink.write(data.data(), data.size());
      } else {
        sink.done(); // No more data
      }
      return true; // return 'false' if you want to cancel the process.
    });
});

チャンク転送エンコード

svr.Get("/chunked", [&](const Request& req, Response& res) {
  res.set_chunked_content_provider(
    "text/plain",
    [](size_t offset, DataSink &sink) {
      sink.write("123", 3);
      sink.write("345", 3);
      sink.write("789", 3);
      sink.done(); // No more data
      return true; // return 'false' if you want to cancel the process.
    }
  );
});

'期待: 100-続行' ハンドラー

既定では、サーバーはヘッダーの応答を送信します。

100 Continue
Expect: 100-continue

// Send a '417 Expectation Failed' response.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
  return 417;
});
// Send a final status without reading the message body.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
  return res.status = 401;
});

キープアライブ接続

svr.set_keep_alive_max_count(2); // Default is 5
svr.set_keep_alive_timeout(10);  // Default is 5

タイムアウト

svr.set_read_timeout(5, 0); // 5 seconds
svr.set_write_timeout(5, 0); // 5 seconds
svr.set_idle_interval(0, 100000); // 100 milliseconds

要求本文を読み取るための最大ペイロード長を設定する

svr.set_payload_max_length(1024 * 1024 * 512); // 512MB

サーバー送信イベント

サーバーの例とクライアントの例を参照してください。

既定のスレッド プールのサポート

ThreadPool
は既定のタスク キューとして使用され、既定のスレッド数は 8 または です。で変更できます。
std::thread::hardware_concurrency()
CPPHTTPLIB_THREAD_POOL_COUNT

実行時にスレッド数を設定したい場合は、便利な方法はありません...しかし、ここに方法があります。

svr.new_task_queue = [] { return new ThreadPool(12); };

デフォルトのスレッドプールを自分のもので上書きする

必要に応じて、独自のスレッドプール実装を提供できます。

class YourThreadPoolTaskQueue : public TaskQueue {
public:
  YourThreadPoolTaskQueue(size_t n) {
    pool_.start_with_thread_count(n);
  }

  virtual void enqueue(std::function<void()> fn) override {
    pool_.enqueue(fn);
  }

  virtual void shutdown() override {
    pool_.shutdown_gracefully();
  }

private:
  YourThreadPool pool_;
};

svr.new_task_queue = [] {
  return new YourThreadPoolTaskQueue(12);
};

クライアント

#include <httplib.h>
#include <iostream>

int main(void)
{
  httplib::Client cli("localhost", 1234);

  if (auto res = cli.Get("/hi")) {
    if (res->status == 200) {
      std::cout << res->body << std::endl;
    }
  } else {
    auto err = res.error();
    std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
  }
}

注:スキームホストポート文字列を持つコンストラクタがサポートされるようになりました!

httplib::Client cli("localhost");
httplib::Client cli("localhost:8080");
httplib::Client cli("http://localhost");
httplib::Client cli("http://localhost:8080");
httplib::Client cli("https://localhost");
httplib::SSLClient cli("localhost");

エラーコード

からのエラーのリストは次のとおりです。

Result::error()

enum Error {
  Success = 0,
  Unknown,
  Connection,
  BindIPAddress,
  Read,
  Write,
  ExceedRedirectCount,
  Canceled,
  SSLConnection,
  SSLLoadingCerts,
  SSLServerVerification,
  UnsupportedMultipartBoundaryChars,
  Compression,
  ConnectionTimeout,
};

HTTP ヘッダーを使用した GET

httplib::Headers headers = {
  { "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);

又は

cli.set_default_headers({
  { "Accept-Encoding", "gzip, deflate" }
});
auto res = cli.Get("/hi");

投稿

res = cli.Post("/post", "text", "text/plain");
res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");

パラメータを指定して POST

httplib::Params params;
params.emplace("name", "john");
params.emplace("note", "coder");

auto res = cli.Post("/post", params);

又は

httplib::Params params{
  { "name", "john" },
  { "note", "coder" }
};

auto res = cli.Post("/post", params);

マルチパートフォームデータを使用した POST

httplib::MultipartFormDataItems items = {
  { "text1", "text default", "", "" },
  { "text2", "aωb", "", "" },
  { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
  { "file2", "{\n  \"world\", true\n}\n", "world.json", "application/json" },
  { "file3", "", "", "application/octet-stream" },
};

auto res = cli.Post("/multipart", items);

置く

res = cli.Put("/resource/foo", "text", "text/plain");

削除

res = cli.Delete("/resource/foo");

オプション

res = cli.Options("*");
res = cli.Options("/resource/foo");

タイムアウト

cli.set_connection_timeout(0, 300000); // 300 milliseconds
cli.set_read_timeout(5, 0); // 5 seconds
cli.set_write_timeout(5, 0); // 5 seconds

コンテンツ レシーバーでコンテンツを受信する

std::string body;

auto res = cli.Get("/large-data",
  [&](const char *data, size_t data_length) {
    body.append(data, data_length);
    return true;
  });
std::string body;

auto res = cli.Get(
  "/stream", Headers(),
  [&](const Response &response) {
    EXPECT_EQ(200, response.status);
    return true; // return 'false' if you want to cancel the request.
  },
  [&](const char *data, size_t data_length) {
    body.append(data, data_length);
    return true; // return 'false' if you want to cancel the request.
  });

Send content with a content provider

std::string body = ...;

auto res = cli.Post(
  "/stream", body.size(),
  [](size_t offset, size_t length, DataSink &sink) {
    sink.write(body.data() + offset, length);
    return true; // return 'false' if you want to cancel the request.
  },
  "text/plain");

Chunked transfer encoding

auto res = cli.Post(
  "/stream",
  [](size_t offset, DataSink &sink) {
    sink.os << "chunked data 1";
    sink.os << "chunked data 2";
    sink.os << "chunked data 3";
    sink.done();
    return true; // return 'false' if you want to cancel the request.
  },
  "text/plain");

With Progress Callback

httplib::Client client(url, port);

// prints: 0 / 000 bytes => 50% complete
auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
  printf("%lld / %lld bytes => %d%% complete\n",
    len, total,
    (int)(len*100/total));
  return true; // return 'false' if you want to cancel the request.
}
);

progress

Authentication

// Basic Authentication
cli.set_basic_auth("user", "pass");

// Digest Authentication
cli.set_digest_auth("user", "pass");

// Bearer Token Authentication
cli.set_bearer_token_auth("token");

NOTE: OpenSSL is required for Digest Authentication.

Proxy server support

cli.set_proxy("host", port);

// Basic Authentication
cli.set_proxy_basic_auth("user", "pass");

// Digest Authentication
cli.set_proxy_digest_auth("user", "pass");

// Bearer Token Authentication
cli.set_proxy_bearer_token_auth("pass");

NOTE: OpenSSL is required for Digest Authentication.

Range

httplib::Client cli("httpbin.org");

auto res = cli.Get("/range/32", {
  httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
});
// res->status should be 206.
// res->body should be "bcdefghijk".
httplib::make_range_header({{1, 10}, {20, -1}})      // 'Range: bytes=1-10, 20-'
httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599'
httplib::make_range_header({{0, 0}, {-1, 1}})        // 'Range: bytes=0-0, -1'

Keep-Alive connection

httplib::Client cli("localhost", 1234);

cli.Get("/hello");         // with "Connection: close"

cli.set_keep_alive(true);
cli.Get("/world");

cli.set_keep_alive(false);
cli.Get("/last-request");  // with "Connection: close"

Redirect

httplib::Client cli("yahoo.com");

auto res = cli.Get("/");
res->status; // 301

cli.set_follow_location(true);
res = cli.Get("/");
res->status; // 200

Use a specific network interface

NOTE: This feature is not available on Windows, yet.

cli.set_interface("eth0"); // Interface name, IP address or host name

Compression

The server can apply compression to the following MIME type contents:

  • all text types except text/event-stream
  • image/svg+xml
  • application/javascript
  • application/json
  • application/xml
  • application/xhtml+xml

Zlib Support

'gzip' compression is available with . should be linked.

CPPHTTPLIB_ZLIB_SUPPORT
libz

Brotli Support

Brotli compression is available with . Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail.

CPPHTTPLIB_BROTLI_SUPPORT

Compress request body on client

cli.set_compress(true);
res = cli.Post("/resource/foo", "...", "text/plain");

Compress response body on client

cli.set_decompress(false);
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res->body; // Compressed data

Use instead of
poll
select

select
system call is used as default since it's more widely supported. If you want to let cpp-httplib use instead, you can do so with .
poll
CPPHTTPLIB_USE_POLL

Split httplib.h into .h and .cc

$ ./split.py -h
usage: split.py [-h] [-e EXTENSION] [-o OUT]

This script splits httplib.h into .h and .cc parts.

optional arguments:
  -h, --help            show this help message and exit
  -e EXTENSION, --extension EXTENSION
                        extension of the implementation file (default: cc)
  -o OUT, --out OUT     where to write the files (default: out)

$ ./split.py
Wrote out/httplib.h and out/httplib.cc

手記

g++

G ++ 4.8以下では、バージョンが壊れているため、このライブラリをビルドできません。

<regex>

ウィンドウズ

前に含めるか、事前に定義して含めます。

httplib.h
Windows.h
Windows.h
WIN32_LEAN_AND_MEAN

#include <httplib.h>
#include <Windows.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <httplib.h>

注: cpp-httplib は公式には最新の Visual Studio のみをサポートしています。以前のバージョンのVisual Studioで動作する可能性がありますが、確認できなくなりました。プル要求は、C++11 準拠に違反しない限り、古いバージョンの Visual Studio では常に歓迎されます。

注: Windows 8 以前、Visual Studio 2013 以前、および Windows 上の Cygwin はサポートされていません。

ライセンス

MITライセンス(© 2022 広瀬 雄二)

特別な感謝

これらの人々は、このライブラリを単純なおもちゃからまったく別のレベルに磨くために多大な貢献をしました!