01058659ab
In case we want to send a lot of data, and the receiver is slower than the sender. This will first fill up the receivers queues and after this eventually also the senders queues, until the socket is temporarily unable to accept more data to send. select_write is done with an timeout of zero, which makes the select call used always return immediately: (see http://man7.org/linux/man-pages/man2/select.2.html) This means that every marginal unavailability will make it return false for is_writable and therefore httplib will immediately abort the transfer. Therefore make this values configurable in the same way as the read timeout already is. Set the default write timeout to 5 seconds, the same default value used for the read timeout. |
||
---|---|---|
.github/workflows | ||
example | ||
test | ||
.clang-format | ||
.gitignore | ||
appveyor.yml | ||
httplib.h | ||
LICENSE | ||
README.md | ||
split.py |
cpp-httplib
A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include httplib.h file in your code!
For Windows users: Please read this note.
Server Example
#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
and Options
methods are also supported.
Bind a socket to multiple interfaces and any available port
int port = svr.bind_to_any_port("0.0.0.0");
svr.listen_after_bind();
Static File 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");
The followings are built-in mappings:
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 |
application/pdf | |
js | application/javascript |
wasm | application/wasm |
xml | application/xml |
xhtml | application/xhtml+xml |
NOTE: These the static file server methods are not thread safe.
Logging
svr.set_logger([](const auto& req, const auto& res) {
your_logger(req, res);
});
Error handler
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' POST 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;
});
Receive content with 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");
}
});
Send content with 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; });
});
Chunked transfer encoding
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.
}
);
});
Server-Sent Events
Please check here.
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);
};
'Expect: 100-continue' handler
As default, the server sends 100 Continue
response for 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;
});
Client Example
#include <httplib.h>
#include <iostream>
int main(void)
{
// IMPORTANT: 1st parameter must be a hostname or an IP adress string.
httplib::Client cli("localhost", 1234);
auto res = cli.Get("/hi");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
}
GET with HTTP headers
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);
GET 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;
});
assert(res->body.empty());
POST
res = cli.Post("/post", "text", "text/plain");
res = cli.Post("/person", "name=john1¬e=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");
Connection Timeout
cli.set_timeout_sec(5); // timeouts in 5 seconds
Receive content with Content receiver
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
std::shared_ptr<httplib::Response> 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.
}
);
Authentication
// Basic Authentication
cli.set_basic_auth("user", "pass");
// Digest Authentication
cli.set_digest_auth("user", "pass");
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");
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
cli.set_keep_alive_max_count(2); // Default is 5
std::vector<Request> requests;
Get(requests, "/get-request1");
Get(requests, "/get-request2");
Post(requests, "/post-request1", "text", "text/plain");
Post(requests, "/post-request2", "text", "text/plain");
const size_t DATA_CHUNK_SIZE = 4;
std::string data("abcdefg");
Post(requests, "/post-request-with-content-provider",
data.size(),
[&](size_t offset, size_t length, DataSink &sink){
sink.write(&data[offset], std::min(length, DATA_CHUNK_SIZE));
},
"text/plain");
std::vector<Response> responses;
if (cli.send(requests, responses)) {
for (const auto& res: responses) {
...
}
}
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 supports 1.1.1 (until 2023-09-11) and 1.0.2 (2019-12-31).
#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);
Zlib Support
'gzip' compression is available with CPPHTTPLIB_ZLIB_SUPPORT
. libz
should be linked.
The server applies gzip compression to the following MIME type contents:
- all text types
- image/svg+xml
- application/javascript
- application/json
- application/xml
- application/xhtml+xml
Compress content on client
cli.set_compress(true);
res = cli.Post("/resource/foo", "...", "text/plain");
Split httplib.h into .h and .cc
> python3 split.py
> ls out
httplib.h httplib.cc
NOTE
g++
g++ 4.8 and below cannot build this library since <regex>
in the versions are broken.
Windows
Include httplib.h
before Windows.h
or include Windows.h
by defining WIN32_LEAN_AND_MEAN
beforehand.
#include <httplib.h>
#include <Windows.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <httplib.h>
Note: Cygwin on Windows is not supported.
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!