mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d92c314466 | |||
| b747fb111d | |||
| 7e0a0b2d0c | |||
| 362d064afa | |||
| 1bd88de2e5 | |||
| 0b541ffebc | |||
| 106be19c3e |
@@ -7,12 +7,12 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
||||
|
||||
It's extremely easy to setup. Just include the **httplib.h** file in your code!
|
||||
|
||||
NOTE: This is a multi-threaded 'blocking' HTTP library. If you are looking for a 'non-blocking' library, this is not the one that you want.
|
||||
NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want.
|
||||
|
||||
Simple examples
|
||||
---------------
|
||||
|
||||
#### Server
|
||||
#### Server (Multi-threaded)
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.11.0"
|
||||
#define CPPHTTPLIB_VERSION "0.11.1"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -183,6 +183,7 @@ using socket_t = SOCKET;
|
||||
#include <pthread.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using socket_t = int;
|
||||
@@ -992,6 +993,8 @@ public:
|
||||
|
||||
size_t is_socket_open() const;
|
||||
|
||||
socket_t socket() const;
|
||||
|
||||
void stop();
|
||||
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
@@ -1344,6 +1347,8 @@ public:
|
||||
|
||||
size_t is_socket_open() const;
|
||||
|
||||
socket_t socket() const;
|
||||
|
||||
void stop();
|
||||
|
||||
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
|
||||
@@ -2566,6 +2571,30 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
hints.ai_flags = socket_flags;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if (hints.ai_family == AF_UNIX) {
|
||||
const auto addrlen = host.length();
|
||||
if (addrlen > sizeof(sockaddr_un::sun_path)) return INVALID_SOCKET;
|
||||
|
||||
auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
|
||||
if (sock != INVALID_SOCKET) {
|
||||
sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
std::copy(host.begin(), host.end(), addr.sun_path);
|
||||
|
||||
hints.ai_addr = reinterpret_cast<sockaddr*>(&addr);
|
||||
hints.ai_addrlen = static_cast<socklen_t>(
|
||||
sizeof(addr) - sizeof(addr.sun_path) + addrlen);
|
||||
|
||||
if (!bind_or_connect(sock, hints)) {
|
||||
close_socket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto service = std::to_string(port);
|
||||
|
||||
if (getaddrinfo(node, service.c_str(), &hints, &result)) {
|
||||
@@ -3797,7 +3826,11 @@ class MultipartFormDataParser {
|
||||
public:
|
||||
MultipartFormDataParser() = default;
|
||||
|
||||
void set_boundary(std::string &&boundary) { boundary_ = boundary; }
|
||||
void set_boundary(std::string &&boundary) {
|
||||
boundary_ = boundary;
|
||||
dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
|
||||
crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
|
||||
}
|
||||
|
||||
bool is_valid() const { return is_valid_; }
|
||||
|
||||
@@ -3809,19 +3842,15 @@ public:
|
||||
R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
|
||||
std::regex_constants::icase);
|
||||
|
||||
static const std::string dash_ = "--";
|
||||
static const std::string crlf_ = "\r\n";
|
||||
|
||||
buf_append(buf, n);
|
||||
|
||||
while (buf_size() > 0) {
|
||||
switch (state_) {
|
||||
case 0: { // Initial boundary
|
||||
auto pattern = dash_ + boundary_ + crlf_;
|
||||
buf_erase(buf_find(pattern));
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
if (!buf_start_with(pattern)) { return false; }
|
||||
buf_erase(pattern.size());
|
||||
buf_erase(buf_find(dash_boundary_crlf_));
|
||||
if (dash_boundary_crlf_.size() > buf_size()) { return true; }
|
||||
if (!buf_start_with(dash_boundary_crlf_)) { return false; }
|
||||
buf_erase(dash_boundary_crlf_.size());
|
||||
state_ = 1;
|
||||
break;
|
||||
}
|
||||
@@ -3856,7 +3885,6 @@ public:
|
||||
file_.filename = m[2];
|
||||
}
|
||||
}
|
||||
|
||||
buf_erase(pos + crlf_.size());
|
||||
pos = buf_find(crlf_);
|
||||
}
|
||||
@@ -3864,40 +3892,25 @@ public:
|
||||
break;
|
||||
}
|
||||
case 3: { // Body
|
||||
{
|
||||
auto pattern = crlf_ + dash_;
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
|
||||
auto pos = buf_find(pattern);
|
||||
|
||||
if (crlf_dash_boundary_.size() > buf_size()) { return true; }
|
||||
auto pos = buf_find(crlf_dash_boundary_);
|
||||
if (pos < buf_size()) {
|
||||
if (!content_callback(buf_data(), pos)) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
buf_erase(pos);
|
||||
}
|
||||
{
|
||||
auto pattern = crlf_ + dash_ + boundary_;
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
|
||||
auto pos = buf_find(pattern);
|
||||
if (pos < buf_size()) {
|
||||
if (!content_callback(buf_data(), pos)) {
|
||||
buf_erase(pos + crlf_dash_boundary_.size());
|
||||
state_ = 4;
|
||||
} else {
|
||||
auto len = buf_size() - crlf_dash_boundary_.size();
|
||||
if (len > 0) {
|
||||
if (!content_callback(buf_data(), len)) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
buf_erase(pos + pattern.size());
|
||||
state_ = 4;
|
||||
} else {
|
||||
if (!content_callback(buf_data(), pattern.size())) {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
buf_erase(pattern.size());
|
||||
buf_erase(len);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3907,10 +3920,9 @@ public:
|
||||
buf_erase(crlf_.size());
|
||||
state_ = 1;
|
||||
} else {
|
||||
auto pattern = dash_ + crlf_;
|
||||
if (pattern.size() > buf_size()) { return true; }
|
||||
if (buf_start_with(pattern)) {
|
||||
buf_erase(pattern.size());
|
||||
if (dash_crlf_.size() > buf_size()) { return true; }
|
||||
if (buf_start_with(dash_crlf_)) {
|
||||
buf_erase(dash_crlf_.size());
|
||||
is_valid_ = true;
|
||||
buf_erase(buf_size()); // Remove epilogue
|
||||
} else {
|
||||
@@ -3941,7 +3953,12 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string dash_ = "--";
|
||||
const std::string crlf_ = "\r\n";
|
||||
const std::string dash_crlf_ = "--\r\n";
|
||||
std::string boundary_;
|
||||
std::string dash_boundary_crlf_;
|
||||
std::string crlf_dash_boundary_;
|
||||
|
||||
size_t state_ = 0;
|
||||
bool is_valid_ = false;
|
||||
@@ -6956,6 +6973,10 @@ inline size_t ClientImpl::is_socket_open() const {
|
||||
return socket_.is_open();
|
||||
}
|
||||
|
||||
inline socket_t ClientImpl::socket() const {
|
||||
return socket_.sock;
|
||||
}
|
||||
|
||||
inline void ClientImpl::stop() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
|
||||
@@ -7862,12 +7883,12 @@ inline Client::Client(const std::string &scheme_host_port,
|
||||
|
||||
if (is_ssl) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
cli_ = detail::make_unique<SSLClient>(host.c_str(), port,
|
||||
cli_ = detail::make_unique<SSLClient>(host, port,
|
||||
client_cert_path, client_key_path);
|
||||
is_ssl_ = is_ssl;
|
||||
#endif
|
||||
} else {
|
||||
cli_ = detail::make_unique<ClientImpl>(host.c_str(), port,
|
||||
cli_ = detail::make_unique<ClientImpl>(host, port,
|
||||
client_cert_path, client_key_path);
|
||||
}
|
||||
} else {
|
||||
@@ -8163,6 +8184,8 @@ inline Result Client::send(const Request &req) { return cli_->send(req); }
|
||||
|
||||
inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
|
||||
|
||||
inline socket_t Client::socket() const { return cli_->socket(); }
|
||||
|
||||
inline void Client::stop() { cli_->stop(); }
|
||||
|
||||
inline void
|
||||
|
||||
Binary file not shown.
+105
@@ -4750,6 +4750,43 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
EXPECT_EQ(301, res->status);
|
||||
}
|
||||
|
||||
TEST(ClientImplMethods, GetSocketTest) {
|
||||
httplib::Server svr;
|
||||
svr.Get( "/", [&](const httplib::Request& /*req*/, httplib::Response& res) {
|
||||
res.status = 200;
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen("127.0.0.1", 3333); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
{
|
||||
httplib::Client cli("http://127.0.0.1:3333");
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
// Use the behavior of cpp-httplib of opening the connection
|
||||
// only when the first request happens. If that changes,
|
||||
// this test would be obsolete.
|
||||
|
||||
EXPECT_EQ(cli.socket(), INVALID_SOCKET);
|
||||
|
||||
// This also implicitly tests the server. But other tests would fail much
|
||||
// earlier than this one to be considered.
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
|
||||
EXPECT_EQ(200, res->status);
|
||||
ASSERT_TRUE(cli.socket() != INVALID_SOCKET);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
// Disabled due to out-of-memory problem on GitHub Actions
|
||||
#ifdef _WIN64
|
||||
TEST(ServerLargeContentTest, DISABLED_SendLargeContent) {
|
||||
@@ -5027,3 +5064,71 @@ TEST(MultipartFormDataTest, WithPreamble) {
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
class UnixSocketTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override {
|
||||
std::remove(pathname_.c_str());
|
||||
}
|
||||
|
||||
void client_GET(const std::string &addr) {
|
||||
httplib::Client cli{addr};
|
||||
cli.set_address_family(AF_UNIX);
|
||||
ASSERT_TRUE(cli.is_valid());
|
||||
|
||||
const auto &result = cli.Get(pattern_);
|
||||
ASSERT_TRUE(result) << "error: " << result.error();
|
||||
|
||||
const auto &resp = result.value();
|
||||
EXPECT_EQ(resp.status, 200);
|
||||
EXPECT_EQ(resp.body, content_);
|
||||
}
|
||||
|
||||
const std::string pathname_ {"./httplib-server.sock"};
|
||||
const std::string pattern_ {"/hi"};
|
||||
const std::string content_ {"Hello World!"};
|
||||
};
|
||||
|
||||
TEST_F(UnixSocketTest, pathname) {
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t {[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(pathname_, 80)); }};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(pathname_);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
TEST_F(UnixSocketTest, abstract) {
|
||||
constexpr char svr_path[] {"\x00httplib-server.sock"};
|
||||
const std::string abstract_addr {svr_path, sizeof(svr_path) - 1};
|
||||
|
||||
httplib::Server svr;
|
||||
svr.Get(pattern_, [&](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content(content_, "text/plain");
|
||||
});
|
||||
|
||||
std::thread t {[&] {
|
||||
ASSERT_TRUE(svr.set_address_family(AF_UNIX).listen(abstract_addr, 80)); }};
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
|
||||
client_GET(abstract_addr);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
#endif
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
Reference in New Issue
Block a user