Compare commits

..

13 Commits

Author SHA1 Message Date
yhirose
39c7bba7b9 Code cleanup 2019-12-17 13:05:08 -05:00
yhirose
f2476f21fc Fixed URL encoding problem when sending a request to proxy 2019-12-17 12:58:25 -05:00
yhirose
c776454c84 Updated README 2019-12-15 20:31:36 -05:00
yhirose
82a5ac735f Merge pull request #290 from yhirose/interface
Fix #285. Added set_interface method on client
2019-12-15 18:02:51 -05:00
yhirose
08bf806e92 Updated README 2019-12-15 17:55:08 -05:00
yhirose
9a41b16cbb Fix #285. Added set_interface method on client 2019-12-15 17:44:00 -05:00
yhirose
10759f0a38 Updated README 2019-12-15 00:21:32 -05:00
yhirose
58b2814fda Format code 2019-12-14 23:50:53 -05:00
yhirose
260422b7d7 Format code 2019-12-14 23:46:11 -05:00
yhirose
d2c7b447d5 Fix #289: Fixed build problem with Visual C++ 2019-12-13 09:12:50 -05:00
yhirose
72b20c08da Better API names 2019-12-13 06:56:00 -05:00
yhirose
afd6d5f9dc Removed compress parameter and added compress method on client 2019-12-12 23:09:59 -05:00
yhirose
e5827ad16f Fixed build error 2019-12-12 23:09:34 -05:00
8 changed files with 338 additions and 283 deletions

5
.clang-format Normal file
View File

@@ -0,0 +1,5 @@
BasedOnStyle: LLVM
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
Cpp11BracedListStyle: true

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@ example/hello
example/simplesvr example/simplesvr
example/benchmark example/benchmark
example/redirect example/redirect
example/upload
example/*.pem example/*.pem
test/test test/test
test/test.xcodeproj/xcuser* test/test.xcodeproj/xcuser*

View File

@@ -387,11 +387,19 @@ httplib::Client cli("yahoo.com");
auto res = cli.Get("/"); auto res = cli.Get("/");
res->status; // 301 res->status; // 301
cli.follow_location(true); cli.set_follow_location(true);
res = cli.Get("/"); res = cli.Get("/");
res->status; // 200 res->status; // 200
``` ```
### Use a specitic network interface
NOTE: This feature is not available on Windows, yet.
```cpp
cli.set_interface("eth0"); // Interface name, IP address or host name
```
OpenSSL Support OpenSSL Support
--------------- ---------------
@@ -423,6 +431,22 @@ The server applies gzip compression to the following MIME type contents:
* application/xml * application/xml
* application/xhtml+xml * application/xhtml+xml
### Compress content on client
```c++
cli.set_compress(true);
res = cli.Post("/resource/foo", "...", "text/plain");
```
Split httplib.h into .h and .cc
-------------------------------
```bash
> python3 split.py
> ls out
httplib.h httplib.cc
```
NOTE NOTE
---- ----

View File

@@ -44,14 +44,10 @@ int main(void) {
#endif #endif
// Run servers // Run servers
auto httpThread = std::thread([&]() { auto httpThread = std::thread([&]() { http.listen("localhost", 8080); });
http.listen("localhost", 8080);
});
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto httpsThread = std::thread([&]() { auto httpsThread = std::thread([&]() { https.listen("localhost", 8081); });
https.listen("localhost", 8081);
});
#endif #endif
httpThread.join(); httpThread.join();

View File

@@ -27,7 +27,7 @@ string dump_headers(const Headers &headers) {
return s; return s;
} }
string dump_multipart_files(const MultipartFiles &files) { string dump_multipart_files(const MultipartFormDataMap &files) {
string s; string s;
char buf[BUFSIZ]; char buf[BUFSIZ];

View File

@@ -5,9 +5,9 @@
// MIT License // MIT License
// //
#include <fstream>
#include <httplib.h> #include <httplib.h>
#include <iostream> #include <iostream>
#include <fstream>
using namespace httplib; using namespace httplib;
using namespace std; using namespace std;
@@ -37,7 +37,8 @@ int main(void) {
svr.Post("/post", [](const Request &req, Response &res) { svr.Post("/post", [](const Request &req, Response &res) {
auto file = req.get_file_value("file"); auto file = req.get_file_value("file");
cout << "file length: " << file.content.length() << ":" << file.filename << endl; cout << "file length: " << file.content.length() << ":" << file.filename
<< endl;
ofstream ofs(file.filename, ios::binary); ofstream ofs(file.filename, ios::binary);
ofs << file.content; ofs << file.content;

375
httplib.h
View File

@@ -114,6 +114,7 @@ using socket_t = SOCKET;
#include <arpa/inet.h> #include <arpa/inet.h>
#include <cstring> #include <cstring>
#include <ifaddrs.h>
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#ifdef CPPHTTPLIB_USE_POLL #ifdef CPPHTTPLIB_USE_POLL
@@ -229,18 +230,18 @@ using MultipartContentHeader =
class ContentReader { class ContentReader {
public: public:
using Reader = std::function<bool(ContentReceiver receiver)>; using Reader = std::function<bool(ContentReceiver receiver)>;
using MultipartReader = std::function<bool(MultipartContentHeader header, ContentReceiver receiver)>; using MultipartReader = std::function<bool(MultipartContentHeader header,
ContentReceiver receiver)>;
ContentReader(Reader reader, MultipartReader muitlpart_reader) ContentReader(Reader reader, MultipartReader muitlpart_reader)
: reader_(reader), muitlpart_reader_(muitlpart_reader) {} : reader_(reader), muitlpart_reader_(muitlpart_reader) {}
bool operator()(MultipartContentHeader header, ContentReceiver receiver) const { bool operator()(MultipartContentHeader header,
ContentReceiver receiver) const {
return muitlpart_reader_(header, receiver); return muitlpart_reader_(header, receiver);
} }
bool operator()(ContentReceiver receiver) const { bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
return reader_(receiver);
}
Reader reader_; Reader reader_;
MultipartReader muitlpart_reader_; MultipartReader muitlpart_reader_;
@@ -583,14 +584,12 @@ private:
const std::string &content_type); const std::string &content_type);
bool read_content(Stream &strm, bool last_connection, Request &req, bool read_content(Stream &strm, bool last_connection, Request &req,
Response &res); Response &res);
bool read_content_with_content_receiver(Stream &strm, bool last_connection, bool read_content_with_content_receiver(
Request &req, Response &res, Stream &strm, bool last_connection, Request &req, Response &res,
ContentReceiver receiver, ContentReceiver receiver, MultipartContentHeader multipart_header,
MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver); ContentReceiver multipart_receiver);
bool read_content_core(Stream &strm, bool last_connection, bool read_content_core(Stream &strm, bool last_connection, Request &req,
Request &req, Response &res, Response &res, ContentReceiver receiver,
ContentReceiver receiver,
MultipartContentHeader mulitpart_header, MultipartContentHeader mulitpart_header,
ContentReceiver multipart_receiver); ContentReceiver multipart_receiver);
@@ -657,78 +656,63 @@ public:
std::shared_ptr<Response> Head(const char *path, const Headers &headers); std::shared_ptr<Response> Head(const char *path, const Headers &headers);
std::shared_ptr<Response> Post(const char *path, const std::string &body, std::shared_ptr<Response> Post(const char *path, const std::string &body,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Post(const char *path, const Headers &headers, std::shared_ptr<Response> Post(const char *path, const Headers &headers,
const std::string &body, const std::string &body,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Post(const char *path, size_t content_length, std::shared_ptr<Response> Post(const char *path, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Post(const char *path, const Headers &headers, std::shared_ptr<Response> Post(const char *path, const Headers &headers,
size_t content_length, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Post(const char *path, const Params &params, std::shared_ptr<Response> Post(const char *path, const Params &params);
bool compress = false);
std::shared_ptr<Response> Post(const char *path, const Headers &headers, std::shared_ptr<Response> Post(const char *path, const Headers &headers,
const Params &params, bool compress = false); const Params &params);
std::shared_ptr<Response> Post(const char *path, std::shared_ptr<Response> Post(const char *path,
const MultipartFormDataItems &items, const MultipartFormDataItems &items);
bool compress = false);
std::shared_ptr<Response> Post(const char *path, const Headers &headers, std::shared_ptr<Response> Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items, const MultipartFormDataItems &items);
bool compress = false);
std::shared_ptr<Response> Put(const char *path, const std::string &body, std::shared_ptr<Response> Put(const char *path, const std::string &body,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Put(const char *path, const Headers &headers, std::shared_ptr<Response> Put(const char *path, const Headers &headers,
const std::string &body, const std::string &body,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Put(const char *path, size_t content_length, std::shared_ptr<Response> Put(const char *path, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Put(const char *path, const Headers &headers, std::shared_ptr<Response> Put(const char *path, const Headers &headers,
size_t content_length, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Patch(const char *path, const std::string &body, std::shared_ptr<Response> Patch(const char *path, const std::string &body,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Patch(const char *path, const Headers &headers, std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
const std::string &body, const std::string &body,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Patch(const char *path, size_t content_length, std::shared_ptr<Response> Patch(const char *path, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Patch(const char *path, const Headers &headers, std::shared_ptr<Response> Patch(const char *path, const Headers &headers,
size_t content_length, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type);
bool compress = false);
std::shared_ptr<Response> Delete(const char *path); std::shared_ptr<Response> Delete(const char *path);
@@ -754,10 +738,14 @@ public:
void set_read_timeout(time_t sec, time_t usec); void set_read_timeout(time_t sec, time_t usec);
void follow_location(bool on);
void set_auth(const char *username, const char *password); void set_auth(const char *username, const char *password);
void set_follow_location(bool on);
void set_compress(bool on);
void set_interface(const char *intf);
protected: protected:
bool process_request(Stream &strm, const Request &req, Response &res, bool process_request(Stream &strm, const Request &req, Response &res,
bool last_connection, bool &connection_close); bool last_connection, bool &connection_close);
@@ -769,22 +757,22 @@ protected:
size_t keep_alive_max_count_; size_t keep_alive_max_count_;
time_t read_timeout_sec_; time_t read_timeout_sec_;
time_t read_timeout_usec_; time_t read_timeout_usec_;
size_t follow_location_; bool follow_location_;
std::string username_; std::string username_;
std::string password_; std::string password_;
bool compress_;
std::string interface_;
private: private:
socket_t create_client_socket() const; socket_t create_client_socket() const;
bool read_response_line(Stream &strm, Response &res); bool read_response_line(Stream &strm, Response &res);
void write_request(Stream &strm, const Request &req, bool last_connection); bool write_request(Stream &strm, const Request &req, bool last_connection);
bool redirect(const Request &req, Response &res); bool redirect(const Request &req, Response &res);
std::shared_ptr<Response> std::shared_ptr<Response> send_with_content_provider(
send_with_content_provider(const char *method, const char *path, const char *method, const char *path, const Headers &headers,
const Headers &headers, const std::string &body, const std::string &body, size_t content_length,
size_t content_length, ContentProvider content_provider, const char *content_type);
ContentProvider content_provider,
const char *content_type, bool compress);
virtual bool process_and_close_socket( virtual bool process_and_close_socket(
socket_t sock, size_t request_count, socket_t sock, size_t request_count,
@@ -1072,7 +1060,7 @@ inline void read_file(const std::string &path, std::string &out) {
inline std::string file_extension(const std::string &path) { inline std::string file_extension(const std::string &path) {
std::smatch m; std::smatch m;
auto re = std::regex("\\.([a-zA-Z0-9]+)$"); static auto re = std::regex("\\.([a-zA-Z0-9]+)$");
if (std::regex_search(path, m, re)) { return m[1].str(); } if (std::regex_search(path, m, re)) { return m[1].str(); }
return std::string(); return std::string();
} }
@@ -1364,10 +1352,62 @@ inline bool is_connection_error() {
#endif #endif
} }
inline socket_t create_client_socket( inline bool bind_ip_address(socket_t sock, const char *host) {
const char *host, int port, time_t timeout_sec) { struct addrinfo hints;
struct addrinfo *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
if (getaddrinfo(host, "0", &hints, &result)) { return false; }
bool ret = false;
for (auto rp = result; rp; rp = rp->ai_next) {
const auto &ai = *rp;
if (!::bind(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen))) {
ret = true;
break;
}
}
freeaddrinfo(result);
return ret;
}
inline std::string if2ip(const std::string &ifn) {
#ifndef _WIN32
struct ifaddrs *ifap;
getifaddrs(&ifap);
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr && ifn == ifa->ifa_name) {
if (ifa->ifa_addr->sa_family == AF_INET) {
auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
char buf[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
freeifaddrs(ifap);
return std::string(buf, INET_ADDRSTRLEN);
}
}
}
}
freeifaddrs(ifap);
#endif
return std::string();
}
inline socket_t create_client_socket(const char *host, int port,
time_t timeout_sec,
const std::string &intf) {
return create_socket( return create_socket(
host, port, [=](socket_t sock, struct addrinfo &ai) -> bool { host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
if (!intf.empty()) {
auto ip = if2ip(intf);
if (ip.empty()) { ip = intf; }
if (!bind_ip_address(sock, ip.c_str())) { return false; }
}
set_nonblocking(sock, true); set_nonblocking(sock, true);
auto ret = ::connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen)); auto ret = ::connect(sock, ai.ai_addr, static_cast<int>(ai.ai_addrlen));
@@ -1926,8 +1966,7 @@ inline bool parse_multipart_boundary(const std::string &content_type,
} }
inline bool parse_range_header(const std::string &s, Ranges &ranges) { inline bool parse_range_header(const std::string &s, Ranges &ranges) {
static auto re_first_range = static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))");
std::smatch m; std::smatch m;
if (std::regex_match(s, m, re_first_range)) { if (std::regex_match(s, m, re_first_range)) {
auto pos = m.position(1); auto pos = m.position(1);
@@ -1965,9 +2004,7 @@ class MultipartFormDataParser {
public: public:
MultipartFormDataParser() {} MultipartFormDataParser() {}
void set_boundary(const std::string &boundary) { void set_boundary(const std::string &boundary) { boundary_ = boundary; }
boundary_ = boundary;
}
bool is_valid() const { return is_valid_; } bool is_valid() const { return is_valid_; }
@@ -2040,9 +2077,7 @@ public:
{ {
auto pattern = crlf_ + dash_; auto pattern = crlf_ + dash_;
auto pos = buf_.find(pattern); auto pos = buf_.find(pattern);
if (pos == std::string::npos) { if (pos == std::string::npos) { pos = buf_.size(); }
pos = buf_.size();
}
if (!content_callback(buf_.data(), pos)) { if (!content_callback(buf_.data(), pos)) {
is_valid_ = false; is_valid_ = false;
is_done_ = false; is_done_ = false;
@@ -2297,11 +2332,11 @@ inline std::string message_digest(const std::string &s, Init init,
size_t digest_length) { size_t digest_length) {
using namespace std; using namespace std;
unsigned char md[digest_length]; std::vector<unsigned char> md(digest_length, 0);
CTX ctx; CTX ctx;
init(&ctx); init(&ctx);
update(&ctx, s.data(), s.size()); update(&ctx, s.data(), s.size());
final(md, &ctx); final(md.data(), &ctx);
stringstream ss; stringstream ss;
for (auto c : md) { for (auto c : md) {
@@ -2366,10 +2401,9 @@ make_basic_authentication_header(const std::string &username,
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
inline std::pair<std::string, std::string> make_digest_authentication_header( inline std::pair<std::string, std::string> make_digest_authentication_header(
const Request &req, const Request &req, const std::map<std::string, std::string> &auth,
const std::map<std::string, std::string> &auth, size_t cnonce_count, const std::string &cnonce, const std::string &username,
size_t cnonce_count, const std::string &cnonce, const std::string &password) {
const std::string &username, const std::string &password) {
using namespace std; using namespace std;
string nc; string nc;
@@ -2397,9 +2431,7 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
auto A1 = username + ":" + auth.at("realm") + ":" + password; auto A1 = username + ":" + auth.at("realm") + ":" + password;
auto A2 = req.method + ":" + req.path; auto A2 = req.method + ":" + req.path;
if (qop == "auth-int") { if (qop == "auth-int") { A2 += ":" + H(req.body); }
A2 += ":" + H(req.body);
}
response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
":" + qop + ":" + H(A2)); ":" + qop + ":" + H(A2));
@@ -2407,15 +2439,16 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") + auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
"\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path + "\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
"\", algorithm=" + auth.at("algorithm") + ", qop=" + qop + ", nc=\"" + "\", algorithm=" + auth.at("algorithm") + ", qop=" + qop +
nc + "\", cnonce=\"" + cnonce + "\", response=\"" + response + ", nc=\"" + nc + "\", cnonce=\"" + cnonce + "\", response=\"" +
"\""; response + "\"";
return make_pair("Authorization", field); return make_pair("Authorization", field);
} }
#endif #endif
inline int parse_www_authenticate(const httplib::Response &res, inline int
parse_www_authenticate(const httplib::Response &res,
std::map<std::string, std::string> &digest_auth) { std::map<std::string, std::string> &digest_auth) {
if (res.has_header("WWW-Authenticate")) { if (res.has_header("WWW-Authenticate")) {
static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
@@ -2790,7 +2823,7 @@ inline void Server::stop() {
} }
inline bool Server::parse_request_line(const char *s, Request &req) { inline bool Server::parse_request_line(const char *s, Request &req) {
static std::regex re( const static std::regex re(
"(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) " "(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH|PRI) "
"(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n"); "(([^?]+)(?:\\?(.*?))?) (HTTP/1\\.[01])\r\n");
@@ -2969,7 +3002,8 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
inline bool Server::read_content(Stream &strm, bool last_connection, inline bool Server::read_content(Stream &strm, bool last_connection,
Request &req, Response &res) { Request &req, Response &res) {
MultipartFormDataMap::iterator cur; MultipartFormDataMap::iterator cur;
auto ret = read_content_core(strm, last_connection, req, res, auto ret = read_content_core(
strm, last_connection, req, res,
// Regular // Regular
[&](const char *buf, size_t n) { [&](const char *buf, size_t n) {
if (req.body.size() + n > req.body.max_size()) { return false; } if (req.body.size() + n > req.body.max_size()) { return false; }
@@ -2986,8 +3020,7 @@ inline bool Server::read_content(Stream &strm, bool last_connection,
if (content.size() + n > content.max_size()) { return false; } if (content.size() + n > content.max_size()) { return false; }
content.append(buf, n); content.append(buf, n);
return true; return true;
} });
);
const auto &content_type = req.get_header_value("Content-Type"); const auto &content_type = req.get_header_value("Content-Type");
if (!content_type.find("application/x-www-form-urlencoded")) { if (!content_type.find("application/x-www-form-urlencoded")) {
@@ -2997,18 +3030,15 @@ inline bool Server::read_content(Stream &strm, bool last_connection,
return ret; return ret;
} }
inline bool inline bool Server::read_content_with_content_receiver(
Server::read_content_with_content_receiver(Stream &strm, bool last_connection, Stream &strm, bool last_connection, Request &req, Response &res,
Request &req, Response &res, ContentReceiver receiver, MultipartContentHeader multipart_header,
ContentReceiver receiver,
MultipartContentHeader multipart_header,
ContentReceiver multipart_receiver) { ContentReceiver multipart_receiver) {
return read_content_core(strm, last_connection, req, res, return read_content_core(strm, last_connection, req, res, receiver,
receiver, multipart_header, multipart_receiver); multipart_header, multipart_receiver);
} }
inline bool inline bool Server::read_content_core(Stream &strm, bool last_connection,
Server::read_content_core(Stream &strm, bool last_connection,
Request &req, Response &res, Request &req, Response &res,
ContentReceiver receiver, ContentReceiver receiver,
MultipartContentHeader mulitpart_header, MultipartContentHeader mulitpart_header,
@@ -3026,7 +3056,8 @@ Server::read_content_core(Stream &strm, bool last_connection,
multipart_form_data_parser.set_boundary(boundary); multipart_form_data_parser.set_boundary(boundary);
out = [&](const char *buf, size_t n) { out = [&](const char *buf, size_t n) {
return multipart_form_data_parser.parse(buf, n, multipart_receiver, mulitpart_header); return multipart_form_data_parser.parse(buf, n, multipart_receiver,
mulitpart_header);
}; };
} else { } else {
out = receiver; out = receiver;
@@ -3172,14 +3203,13 @@ inline bool Server::routing(Request &req, Response &res, Stream &strm,
{ {
ContentReader reader( ContentReader reader(
[&](ContentReceiver receiver) { [&](ContentReceiver receiver) {
return read_content_with_content_receiver(strm, last_connection, req, res, return read_content_with_content_receiver(
receiver, nullptr, nullptr); strm, last_connection, req, res, receiver, nullptr, nullptr);
}, },
[&](MultipartContentHeader header, ContentReceiver receiver) { [&](MultipartContentHeader header, ContentReceiver receiver) {
return read_content_with_content_receiver(strm, last_connection, req, res, return read_content_with_content_receiver(
nullptr, header, receiver); strm, last_connection, req, res, nullptr, header, receiver);
} });
);
if (req.method == "POST") { if (req.method == "POST") {
if (dispatch_request_for_content_reader( if (dispatch_request_for_content_reader(
@@ -3331,14 +3361,15 @@ inline Client::Client(const char *host, int port, time_t timeout_sec)
keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT), keep_alive_max_count_(CPPHTTPLIB_KEEPALIVE_MAX_COUNT),
read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND), read_timeout_sec_(CPPHTTPLIB_READ_TIMEOUT_SECOND),
read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND), read_timeout_usec_(CPPHTTPLIB_READ_TIMEOUT_USECOND),
follow_location_(false) {} follow_location_(false), compress_(false) {}
inline Client::~Client() {} inline Client::~Client() {}
inline bool Client::is_valid() const { return true; } inline bool Client::is_valid() const { return true; }
inline socket_t Client::create_client_socket() const { inline socket_t Client::create_client_socket() const {
return detail::create_client_socket(host_.c_str(), port_, timeout_sec_); return detail::create_client_socket(host_.c_str(), port_, timeout_sec_,
interface_);
} }
inline bool Client::read_response_line(Stream &strm, Response &res) { inline bool Client::read_response_line(Stream &strm, Response &res) {
@@ -3456,46 +3487,51 @@ inline bool Client::redirect(const Request &req, Response &res) {
auto location = res.get_header_value("location"); auto location = res.get_header_value("location");
if (location.empty()) { return false; } if (location.empty()) { return false; }
std::regex re( const static std::regex re(
R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
auto scheme = is_ssl() ? "https" : "http";
std::smatch m; std::smatch m;
if (regex_match(location, m, re)) { if (!regex_match(location, m, re)) { return false; }
auto next_scheme = m[1].str(); auto next_scheme = m[1].str();
auto next_host = m[2].str(); auto next_host = m[2].str();
auto next_path = m[3].str(); auto next_path = m[3].str();
if (next_host.empty()) { next_host = host_; } if (next_host.empty()) { next_host = host_; }
if (next_path.empty()) { next_path = "/"; } if (next_path.empty()) { next_path = "/"; }
auto scheme = is_ssl() ? "https" : "http";
if (next_scheme == scheme && next_host == host_) { if (next_scheme == scheme && next_host == host_) {
return detail::redirect(*this, req, res, next_path); return detail::redirect(*this, req, res, next_path);
} else { } else {
if (next_scheme == "https") { if (next_scheme == "https") {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(next_host.c_str()); SSLClient cli(next_host.c_str());
cli.follow_location(true); cli.set_follow_location(true);
return detail::redirect(cli, req, res, next_path); return detail::redirect(cli, req, res, next_path);
#else #else
return false; return false;
#endif #endif
} else { } else {
Client cli(next_host.c_str()); Client cli(next_host.c_str());
cli.follow_location(true); cli.set_follow_location(true);
return detail::redirect(cli, req, res, next_path); return detail::redirect(cli, req, res, next_path);
} }
} }
} }
return false;
}
inline void Client::write_request(Stream &strm, const Request &req, inline bool Client::write_request(Stream &strm, const Request &req,
bool last_connection) { bool last_connection) {
BufferStream bstrm; BufferStream bstrm;
// Request line // Request line
auto path = detail::encode_url(req.path); const static std::regex re(
R"(^([^:/?#]+://[^/?#]*)?([^?#]*(?:\?[^#]*)?(?:#.*)?))");
std::smatch m;
if (!regex_match(req.path, m, re)) { return false; }
auto path = m[1].str() + detail::encode_url(m[2].str());
bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str());
@@ -3565,16 +3601,14 @@ inline void Client::write_request(Stream &strm, const Request &req,
} else { } else {
strm.write(req.body); strm.write(req.body);
} }
return true;
} }
inline std::shared_ptr<Response> Client::send_with_content_provider( inline std::shared_ptr<Response> Client::send_with_content_provider(
const char *method, const char *path, const Headers &headers, const char *method, const char *path, const Headers &headers,
const std::string &body, size_t content_length, const std::string &body, size_t content_length,
ContentProvider content_provider, const char *content_type, bool compress) { ContentProvider content_provider, const char *content_type) {
#ifndef CPPHTTPLIB_ZLIB_SUPPORT
(void)compress;
#endif
Request req; Request req;
req.method = method; req.method = method;
req.headers = headers; req.headers = headers;
@@ -3583,7 +3617,7 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
req.headers.emplace("Content-Type", content_type); req.headers.emplace("Content-Type", content_type);
#ifdef CPPHTTPLIB_ZLIB_SUPPORT #ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (compress) { if (compress_) {
if (content_provider) { if (content_provider) {
size_t offset = 0; size_t offset = 0;
while (offset < content_length) { while (offset < content_length) {
@@ -3619,7 +3653,7 @@ inline bool Client::process_request(Stream &strm, const Request &req,
Response &res, bool last_connection, Response &res, bool last_connection,
bool &connection_close) { bool &connection_close) {
// Send request // Send request
write_request(strm, req, last_connection); if (!write_request(strm, req, last_connection)) { return false; }
// Receive response and headers // Receive response and headers
if (!read_response_line(strm, res) || if (!read_response_line(strm, res) ||
@@ -3771,45 +3805,40 @@ inline std::shared_ptr<Response> Client::Head(const char *path,
inline std::shared_ptr<Response> Client::Post(const char *path, inline std::shared_ptr<Response> Client::Post(const char *path,
const std::string &body, const std::string &body,
const char *content_type, const char *content_type) {
bool compress) { return Post(path, Headers(), body, content_type);
return Post(path, Headers(), body, content_type, compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response> Client::Post(const char *path,
Client::Post(const char *path, const Headers &headers, const std::string &body, const Headers &headers,
const char *content_type, bool compress) { const std::string &body,
const char *content_type) {
return send_with_content_provider("POST", path, headers, body, 0, nullptr, return send_with_content_provider("POST", path, headers, body, 0, nullptr,
content_type, compress); content_type);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response> Client::Post(const char *path,
Client::Post(const char *path, const Params &params, bool compress) { const Params &params) {
return Post(path, Headers(), params, compress); return Post(path, Headers(), params);
} }
inline std::shared_ptr<Response> Client::Post(const char *path, inline std::shared_ptr<Response> Client::Post(const char *path,
size_t content_length, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type) {
bool compress) { return Post(path, Headers(), content_length, content_provider, content_type);
return Post(path, Headers(), content_length, content_provider, content_type,
compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response>
Client::Post(const char *path, const Headers &headers, size_t content_length, Client::Post(const char *path, const Headers &headers, size_t content_length,
ContentProvider content_provider, const char *content_type, ContentProvider content_provider, const char *content_type) {
bool compress) {
return send_with_content_provider("POST", path, headers, std::string(), return send_with_content_provider("POST", path, headers, std::string(),
content_length, content_provider, content_length, content_provider,
content_type, compress); content_type);
} }
inline std::shared_ptr<Response> Client::Post(const char *path, inline std::shared_ptr<Response>
const Headers &headers, Client::Post(const char *path, const Headers &headers, const Params &params) {
const Params &params,
bool compress) {
std::string query; std::string query;
for (auto it = params.begin(); it != params.end(); ++it) { for (auto it = params.begin(); it != params.end(); ++it) {
if (it != params.begin()) { query += "&"; } if (it != params.begin()) { query += "&"; }
@@ -3818,19 +3847,17 @@ inline std::shared_ptr<Response> Client::Post(const char *path,
query += detail::encode_url(it->second); query += detail::encode_url(it->second);
} }
return Post(path, headers, query, "application/x-www-form-urlencoded", return Post(path, headers, query, "application/x-www-form-urlencoded");
compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response>
Client::Post(const char *path, const MultipartFormDataItems &items, Client::Post(const char *path, const MultipartFormDataItems &items) {
bool compress) { return Post(path, Headers(), items);
return Post(path, Headers(), items, compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response>
Client::Post(const char *path, const Headers &headers, Client::Post(const char *path, const Headers &headers,
const MultipartFormDataItems &items, bool compress) { const MultipartFormDataItems &items) {
auto boundary = detail::make_multipart_data_boundary(); auto boundary = detail::make_multipart_data_boundary();
std::string body; std::string body;
@@ -3852,71 +3879,65 @@ Client::Post(const char *path, const Headers &headers,
body += "--" + boundary + "--\r\n"; body += "--" + boundary + "--\r\n";
std::string content_type = "multipart/form-data; boundary=" + boundary; std::string content_type = "multipart/form-data; boundary=" + boundary;
return Post(path, headers, body, content_type.c_str(), compress); return Post(path, headers, body, content_type.c_str());
} }
inline std::shared_ptr<Response> Client::Put(const char *path, inline std::shared_ptr<Response> Client::Put(const char *path,
const std::string &body, const std::string &body,
const char *content_type, const char *content_type) {
bool compress) { return Put(path, Headers(), body, content_type);
return Put(path, Headers(), body, content_type, compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response> Client::Put(const char *path,
Client::Put(const char *path, const Headers &headers, const std::string &body, const Headers &headers,
const char *content_type, bool compress) { const std::string &body,
const char *content_type) {
return send_with_content_provider("PUT", path, headers, body, 0, nullptr, return send_with_content_provider("PUT", path, headers, body, 0, nullptr,
content_type, compress); content_type);
} }
inline std::shared_ptr<Response> Client::Put(const char *path, inline std::shared_ptr<Response> Client::Put(const char *path,
size_t content_length, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type) {
bool compress) { return Put(path, Headers(), content_length, content_provider, content_type);
return Put(path, Headers(), content_length, content_provider, content_type,
compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response>
Client::Put(const char *path, const Headers &headers, size_t content_length, Client::Put(const char *path, const Headers &headers, size_t content_length,
ContentProvider content_provider, const char *content_type, ContentProvider content_provider, const char *content_type) {
bool compress) {
return send_with_content_provider("PUT", path, headers, std::string(), return send_with_content_provider("PUT", path, headers, std::string(),
content_length, content_provider, content_length, content_provider,
content_type, compress); content_type);
} }
inline std::shared_ptr<Response> Client::Patch(const char *path, inline std::shared_ptr<Response> Client::Patch(const char *path,
const std::string &body, const std::string &body,
const char *content_type, const char *content_type) {
bool compress) { return Patch(path, Headers(), body, content_type);
return Patch(path, Headers(), body, content_type, compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response> Client::Patch(const char *path,
Client::Patch(const char *path, const Headers &headers, const std::string &body, const Headers &headers,
const char *content_type, bool compress) { const std::string &body,
const char *content_type) {
return send_with_content_provider("PATCH", path, headers, body, 0, nullptr, return send_with_content_provider("PATCH", path, headers, body, 0, nullptr,
content_type, compress); content_type);
} }
inline std::shared_ptr<Response> Client::Patch(const char *path, inline std::shared_ptr<Response> Client::Patch(const char *path,
size_t content_length, size_t content_length,
ContentProvider content_provider, ContentProvider content_provider,
const char *content_type, const char *content_type) {
bool compress) { return Patch(path, Headers(), content_length, content_provider, content_type);
return Patch(path, Headers(), content_length, content_provider, content_type,
compress);
} }
inline std::shared_ptr<Response> inline std::shared_ptr<Response>
Client::Patch(const char *path, const Headers &headers, size_t content_length, Client::Patch(const char *path, const Headers &headers, size_t content_length,
ContentProvider content_provider, const char *content_type, ContentProvider content_provider, const char *content_type) {
bool compress) {
return send_with_content_provider("PATCH", path, headers, std::string(), return send_with_content_provider("PATCH", path, headers, std::string(),
content_length, content_provider, content_length, content_provider,
content_type, compress); content_type);
} }
inline std::shared_ptr<Response> Client::Delete(const char *path) { inline std::shared_ptr<Response> Client::Delete(const char *path) {
@@ -3976,13 +3997,17 @@ inline void Client::set_read_timeout(time_t sec, time_t usec) {
read_timeout_usec_ = usec; read_timeout_usec_ = usec;
} }
inline void Client::follow_location(bool on) { follow_location_ = on; }
inline void Client::set_auth(const char *username, const char *password) { inline void Client::set_auth(const char *username, const char *password) {
username_ = username; username_ = username;
password_ = password; password_ = password;
} }
inline void Client::set_follow_location(bool on) { follow_location_ = on; }
inline void Client::set_compress(bool on) { compress_ = on; }
inline void Client::set_interface(const char *intf) { interface_ = intf; }
/* /*
* SSL Implementation * SSL Implementation
*/ */

View File

@@ -30,10 +30,11 @@ const std::string JSON_DATA = "{\"hello\":\"world\"}";
const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
MultipartFormData& get_file_value(MultipartFormDataItems &files, const char *key) { MultipartFormData &get_file_value(MultipartFormDataItems &files,
auto it = std::find_if(files.begin(), files.end(), [&](const MultipartFormData &file) { const char *key) {
return file.name == key; auto it = std::find_if(
}); files.begin(), files.end(),
[&](const MultipartFormData &file) { return file.name == key; });
if (it != files.end()) { return *it; } if (it != files.end()) { return *it; }
throw std::runtime_error("invalid mulitpart form data name error"); throw std::runtime_error("invalid mulitpart form data name error");
} }
@@ -524,7 +525,7 @@ TEST(AbsoluteRedirectTest, Redirect) {
httplib::Client cli(host); httplib::Client cli(host);
#endif #endif
cli.follow_location(true); cli.set_follow_location(true);
auto res = cli.Get("/absolute-redirect/3"); auto res = cli.Get("/absolute-redirect/3");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -539,7 +540,7 @@ TEST(RedirectTest, Redirect) {
httplib::Client cli(host); httplib::Client cli(host);
#endif #endif
cli.follow_location(true); cli.set_follow_location(true);
auto res = cli.Get("/redirect/3"); auto res = cli.Get("/redirect/3");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -554,7 +555,7 @@ TEST(RelativeRedirectTest, Redirect) {
httplib::Client cli(host); httplib::Client cli(host);
#endif #endif
cli.follow_location(true); cli.set_follow_location(true);
auto res = cli.Get("/relative-redirect/3"); auto res = cli.Get("/relative-redirect/3");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -569,7 +570,7 @@ TEST(TooManyRedirectTest, Redirect) {
httplib::Client cli(host); httplib::Client cli(host);
#endif #endif
cli.follow_location(true); cli.set_follow_location(true);
auto res = cli.Get("/redirect/21"); auto res = cli.Get("/redirect/21");
ASSERT_TRUE(res == nullptr); ASSERT_TRUE(res == nullptr);
} }
@@ -582,7 +583,7 @@ TEST(YahooRedirectTest, Redirect) {
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(301, res->status); EXPECT_EQ(301, res->status);
cli.follow_location(true); cli.set_follow_location(true);
res = cli.Get("/"); res = cli.Get("/");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -590,7 +591,7 @@ TEST(YahooRedirectTest, Redirect) {
TEST(HttpsToHttpRedirectTest, Redirect) { TEST(HttpsToHttpRedirectTest, Redirect) {
httplib::SSLClient cli("httpbin.org"); httplib::SSLClient cli("httpbin.org");
cli.follow_location(true); cli.set_follow_location(true);
auto res = auto res =
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302"); cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
@@ -801,7 +802,8 @@ protected:
EXPECT_EQ("5", req.get_header_value("Content-Length")); EXPECT_EQ("5", req.get_header_value("Content-Length"));
}) })
.Post("/content_receiver", .Post("/content_receiver",
[&](const Request & req, Response &res, const ContentReader &content_reader) { [&](const Request &req, Response &res,
const ContentReader &content_reader) {
if (req.is_multipart_form_data()) { if (req.is_multipart_form_data()) {
MultipartFormDataItems files; MultipartFormDataItems files;
content_reader( content_reader(
@@ -1535,12 +1537,13 @@ TEST_F(ServerTest, PutWithContentProvider) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT #ifdef CPPHTTPLIB_ZLIB_SUPPORT
TEST_F(ServerTest, PutWithContentProviderWithGzip) { TEST_F(ServerTest, PutWithContentProviderWithGzip) {
cli_.set_compress(true);
auto res = cli_.Put( auto res = cli_.Put(
"/put", 3, "/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink sink) { [](size_t /*offset*/, size_t /*length*/, DataSink sink) {
sink("PUT", 3); sink("PUT", 3);
}, },
"text/plain", true); "text/plain");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -1548,7 +1551,8 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
} }
TEST_F(ServerTest, PutLargeFileWithGzip) { TEST_F(ServerTest, PutLargeFileWithGzip) {
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain", true); cli_.set_compress(true);
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -1621,7 +1625,8 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
} }
TEST_F(ServerTest, PostContentReceiverGzip) { TEST_F(ServerTest, PostContentReceiverGzip) {
auto res = cli_.Post("/content_receiver", "content", "text/plain", true); cli_.set_compress(true);
auto res = cli_.Post("/content_receiver", "content", "text/plain");
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
ASSERT_EQ(200, res->status); ASSERT_EQ(200, res->status);
ASSERT_EQ("content", res->body); ASSERT_EQ("content", res->body);
@@ -1802,7 +1807,8 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
{"key2", "--abcdefg123", "", ""}, {"key2", "--abcdefg123", "", ""},
}; };
auto res = cli_.Post("/gzipmultipart", items, true); cli_.set_compress(true);
auto res = cli_.Post("/gzipmultipart", items);
ASSERT_TRUE(res != nullptr); ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status); EXPECT_EQ(200, res->status);
@@ -1811,15 +1817,15 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
// Sends a raw request to a server listening at HOST:PORT. // Sends a raw request to a server listening at HOST:PORT.
static bool send_request(time_t read_timeout_sec, const std::string &req) { static bool send_request(time_t read_timeout_sec, const std::string &req) {
auto client_sock = auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5); std::string());
if (client_sock == INVALID_SOCKET) { return false; } if (client_sock == INVALID_SOCKET) { return false; }
return detail::process_and_close_socket( return detail::process_and_close_socket(
true, client_sock, 1, read_timeout_sec, 0, true, client_sock, 1, read_timeout_sec, 0,
[&](Stream& strm, bool /*last_connection*/, [&](Stream &strm, bool /*last_connection*/, bool &
bool &/*connection_close*/) -> bool { /*connection_close*/) -> bool {
if (req.size() != if (req.size() !=
static_cast<size_t>(strm.write(req.data(), req.size()))) { static_cast<size_t>(strm.write(req.data(), req.size()))) {
return false; return false;
@@ -1836,8 +1842,7 @@ static bool send_request(time_t read_timeout_sec, const std::string& req) {
TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) { TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
Server svr; Server svr;
std::string header_value; std::string header_value;
svr.Get("/validate-ws-in-headers", svr.Get("/validate-ws-in-headers", [&](const Request &req, Response &res) {
[&](const Request &req, Response &res) {
header_value = req.get_header_value("foo"); header_value = req.get_header_value("foo");
res.set_content("ok", "text/plain"); res.set_content("ok", "text/plain");
}); });
@@ -1849,8 +1854,7 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
// Only space and horizontal tab are whitespace. Make sure other whitespace- // Only space and horizontal tab are whitespace. Make sure other whitespace-
// like characters are not treated the same - use vertical tab and escape. // like characters are not treated the same - use vertical tab and escape.
const std::string req = const std::string req = "GET /validate-ws-in-headers HTTP/1.1\r\n"
"GET /validate-ws-in-headers HTTP/1.1\r\n"
"foo: \t \v bar \e\t \r\n" "foo: \t \v bar \e\t \r\n"
"Connection: close\r\n" "Connection: close\r\n"
"\r\n"; "\r\n";
@@ -1863,8 +1867,7 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) { TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
Server svr; Server svr;
svr.Get("/hi", svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
[&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain"); res.set_content("ok", "text/plain");
}); });