「生活可以更简单, 欢迎来到我的开源世界」
  1. cpp-httplib
    1. Server 示例
      1. 绑定套接字到多个接口ip地址和任意可用端口
      2. 静态文件Server
      3. 日志 Logging
      4. 错误处理
      5. 使用’multipart/form-data’ 上传数据
      6. 使用Content receiver 接收内容
      7. 使用Content provider 提供内容
      8. 分块传输编码
      9. ‘Expect: 100-continue’ 处理
      10. 存活连接
      11. 设置超时限制
      12. 设置request body最大接收窗口
      13. Server-Sent Events
      14. Default thread pool support
      15. Override the default thread pool with yours
    2. Client 示例
      1. GET with HTTP headers
      2. POST
      3. POST with parameters
      4. POST with Multipart Form Data
      5. PUT
      6. DELETE
      7. OPTIONS
      8. Timeout
      9. Receive content with Content receiver
      10. Send content with Content provider
      11. With Progress Callback
      12. Authentication
      13. Proxy server support
      14. Range
      15. Keep-Alive connection
      16. Redirect
      17. Use a specitic network interface
    3. OpenSSL Support
    4. Compression
      1. Zlib Support
      2. Brotli Support
      3. Compress request body on client
      4. Compress response body on client
    5. Split httplib.h into .h and .cc
    6. 编译注意
      1. g++
      2. Windows
    7. License
    8. Special Thanks To
httplib库的使用
2020-08-10
C++

httplib库的使用。

cpp-httplib

cpp-httplib是一个跨平台的HTTP/HTTPS库,只有一个头文件,使用C++11新标准编程。

该库方便易用,仅需要在你的程序中引入头文件httplib.h

Server 示例

#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);
}

除了Get方法,PostPutDeleteOptions同样被支持。

绑定套接字到多个接口ip地址和任意可用端口

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

静态文件Server

// 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");

已经内建映射的文件格式:

Extension MIME Type
txt text/plain
html, htm text/html
css text/css
jpeg, jpg image/jpg
png image/png
gif image/gif
svg image/svg+xml
ico image/x-icon
json application/json
pdf application/pdf
js application/javascript
wasm application/wasm
xml application/xml
xhtml application/xhtml+xml

注意:静态文件服务提供的方法并非线程安全的

日志 Logging

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");
});

使用’multipart/form-data’ 上传数据

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;
});

使用Content receiver 接收内容

svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
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;
});
res.set_content(body, "text/plain");
}
});

使用Content provider 提供内容

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
[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] { delete data; });
});

分块传输编码

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

‘Expect: 100-continue’ 处理

默认情况下,server 发送 100 Continue 响应给 Expect: 100-continue header。

// 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_read_timeout(5, 0); // 5 seconds
svr.set_write_timeout(5, 0); // 5 seconds
svr.set_idle_interval(0, 100000); // 100 milliseconds

设置request body最大接收窗口

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

Server-Sent Events

Please see Server example and Client example.

Default thread pool support

ThreadPool is used as a default task queue, and the default thread count is set to value from std::thread::hardware_concurrency().

You can change the thread count by setting CPPHTTPLIB_THREAD_POOL_COUNT.

Override the default thread pool with yours

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);
};

Client 示例

#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();
/*...*/
}
}

NOTE: Constructor with scheme-host-port string is now supported!

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");

GET with HTTP headers

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

or

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

POST

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

POST with parameters

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

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

or

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

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

POST with Multipart Form Data

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);

PUT

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

DELETE

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

OPTIONS

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

Timeout

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

Receive content with Content receiver

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 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");

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 specitic network interface

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

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

OpenSSL Support

SSL support is available with CPPHTTPLIB_OPENSSL_SUPPORT. libssl and libcrypto should be linked.

NOTE: cpp-httplib currently supports only version 1.1.1.

#define CPPHTTPLIB_OPENSSL_SUPPORT

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

SSLClient cli("localhost", 8080);
cli.set_ca_cert_path("./ca-bundle.crt");
cli.enable_server_certificate_verification(true);

Compression

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

Zlib Support

‘gzip’ compression is available with CPPHTTPLIB_ZLIB_SUPPORT. libz should be linked.

Brotli Support

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

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

Split httplib.h into .h and .cc

> python3 split.py
> ls out
httplib.h httplib.cc

编译注意

g++

由于使用了C++11新标准,g++4.8 以及更旧的版本无法编译成功,因为<regex>库不被支持

编译参数要加上:-pthread

Windows

  1. 引用 httplib.h 之前先引用Windows.h

    #include <httplib.h>
    #include <Windows.h>
  2. 先声明WIN32_LEAN_AND_MEAN 再引用Windows.h

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

注意: Windows下的Cygwin不支持

License

MIT license (© 2020 Yuji Hirose)

Special Thanks To

These folks made great contributions to polish this library to totally another level from a simple toy!

<⇧>