mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d45250fd88 | |||
| 528cacdc0d | |||
| ed1b6afa10 | |||
| 08fc7085e5 | |||
| 8333340e2c | |||
| 98a0887571 | |||
| b0a189e50e | |||
| 776b3ffbf9 | |||
| a061b97677 | |||
| d359e3a5f7 | |||
| 5928e0af1a | |||
| a5005789ff | |||
| fae30af47d | |||
| 2feea0c9ab | |||
| a2e4af54b7 | |||
| d0b123be26 | |||
| df138366e4 | |||
| c49441ae64 | |||
| e1506fa186 | |||
| ad9fd3bd93 | |||
| 05e0253195 | |||
| da26b517a3 | |||
| 2b7a968468 | |||
| 240cc85ccb | |||
| 129e2f00b8 |
@@ -3,6 +3,7 @@ tags
|
||||
example/server
|
||||
example/client
|
||||
example/hello
|
||||
example/simplecli
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
|
||||
+12
-7
@@ -1,9 +1,14 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2017
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd test
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd test
|
||||
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Release;Platform=%PLATFORM%
|
||||
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Debug;Platform=Win32
|
||||
test_script:
|
||||
- cmd: Debug\test.exe
|
||||
- cmd: x64\Release\test.exe
|
||||
|
||||
+5
-2
@@ -5,7 +5,7 @@ OPENSSL_DIR = /usr/local/opt/openssl
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
all: server client hello simplesvr upload redirect sse benchmark
|
||||
all: server client hello simplecli simplesvr upload redirect sse benchmark
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
@@ -16,6 +16,9 @@ client : client.cc ../httplib.h Makefile
|
||||
hello : hello.cc ../httplib.h Makefile
|
||||
$(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
simplecli : simplecli.cc ../httplib.h Makefile
|
||||
$(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
simplesvr : simplesvr.cc ../httplib.h Makefile
|
||||
$(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
@@ -36,4 +39,4 @@ pem:
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplesvr upload redirect sse benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect sse benchmark *.pem
|
||||
|
||||
+1
-1
@@ -15,5 +15,5 @@ int main(void) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
svr.listen("localhost", 8080);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// simplecli.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
#define CA_CERT_FILE "./ca-bundle.crt"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(void) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
httplib::url::Options options;
|
||||
options.ca_cert_file_path = CA_CERT_FILE;
|
||||
// options.server_certificate_verification = true;
|
||||
|
||||
auto res = httplib::url::Get("https://localhost:8080/hi", options);
|
||||
#else
|
||||
auto res = httplib::url::Get("http://localhost:8080/hi");
|
||||
#endif
|
||||
|
||||
if (res) {
|
||||
cout << res->status << endl;
|
||||
cout << res->get_header_value("Content-Type") << endl;
|
||||
cout << res->body << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT \
|
||||
((std::max)(1u, std::thread::hardware_concurrency() - 1))
|
||||
((std::max)(8u, std::thread::hardware_concurrency() - 1))
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -134,6 +134,7 @@ using socket_t = int;
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -174,7 +175,6 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Declaration
|
||||
*/
|
||||
@@ -262,6 +262,9 @@ struct Request {
|
||||
Headers headers;
|
||||
std::string body;
|
||||
|
||||
std::string remote_addr;
|
||||
int remote_port = -1;
|
||||
|
||||
// for server
|
||||
std::string version;
|
||||
std::string target;
|
||||
@@ -352,7 +355,7 @@ public:
|
||||
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
virtual std::string get_remote_addr() const = 0;
|
||||
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
|
||||
|
||||
template <typename... Args>
|
||||
ssize_t write_format(const char *fmt, const Args &... args);
|
||||
@@ -364,8 +367,11 @@ class TaskQueue {
|
||||
public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle(){};
|
||||
};
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
@@ -604,6 +610,8 @@ public:
|
||||
|
||||
std::shared_ptr<Response> Head(const char *path, const Headers &headers);
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path);
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
|
||||
@@ -631,6 +639,8 @@ public:
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
const MultipartFormDataItems &items);
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path);
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
|
||||
@@ -688,6 +698,8 @@ public:
|
||||
bool send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses);
|
||||
|
||||
void stop();
|
||||
|
||||
void set_timeout_sec(time_t timeout_sec);
|
||||
|
||||
void set_read_timeout(time_t sec, time_t usec);
|
||||
@@ -720,6 +732,8 @@ protected:
|
||||
bool process_request(Stream &strm, const Request &req, Response &res,
|
||||
bool last_connection, bool &connection_close);
|
||||
|
||||
std::atomic<socket_t> sock_;
|
||||
|
||||
const std::string host_;
|
||||
const int port_;
|
||||
const std::string host_and_port_;
|
||||
@@ -831,7 +845,7 @@ inline void Post(std::vector<Request> &requests, const char *path,
|
||||
req.method = "POST";
|
||||
req.path = path;
|
||||
req.headers = headers;
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
req.body = body;
|
||||
requests.emplace_back(std::move(req));
|
||||
}
|
||||
@@ -884,7 +898,7 @@ public:
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
SSL_CTX *ssl_context() const noexcept;
|
||||
SSL_CTX *ssl_context() const;
|
||||
|
||||
private:
|
||||
bool process_and_close_socket(
|
||||
@@ -1184,7 +1198,22 @@ inline int close_socket(socket_t sock) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
template <typename T>
|
||||
inline ssize_t handle_EINTR(T fn) {
|
||||
ssize_t res = false;
|
||||
while (true) {
|
||||
res = fn();
|
||||
if (res < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#define HANDLE_EINTR(method, ...) (handle_EINTR([&]() { return method(__VA_ARGS__); }))
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
@@ -1192,7 +1221,7 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return poll(&pfd_read, 1, timeout);
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1202,11 +1231,11 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
@@ -1214,7 +1243,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return poll(&pfd_read, 1, timeout);
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1224,7 +1253,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1236,13 +1265,13 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
if (poll(&pfd_read, 1, timeout) > 0 &&
|
||||
pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
auto poll_res = HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
return getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len) >= 0 &&
|
||||
!error;
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
return res >= 0 && !error;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
@@ -1257,7 +1286,7 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
if (select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
|
||||
if (HANDLE_EINTR(select, static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
|
||||
(FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
@@ -1279,7 +1308,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -1298,7 +1327,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -1317,7 +1346,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
const std::string &get_buffer() const;
|
||||
|
||||
@@ -1501,8 +1530,8 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
#ifndef _WIN32
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
struct ifaddrs *ifap;
|
||||
getifaddrs(&ifap);
|
||||
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
@@ -1518,9 +1547,9 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
#endif
|
||||
return std::string();
|
||||
}
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(const char *host, int port,
|
||||
time_t timeout_sec,
|
||||
@@ -1528,9 +1557,11 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
return create_socket(
|
||||
host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifndef _WIN32
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) { return false; }
|
||||
#endif
|
||||
}
|
||||
|
||||
set_nonblocking(sock, true);
|
||||
@@ -1550,21 +1581,32 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
});
|
||||
}
|
||||
|
||||
inline std::string get_remote_addr(socket_t sock) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
|
||||
if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()),
|
||||
nullptr, 0, NI_NUMERICHOST)) {
|
||||
return ipstr.data();
|
||||
}
|
||||
inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
|
||||
socklen_t addr_len, std::string &ip,
|
||||
int &port) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
port =
|
||||
ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
|
||||
}
|
||||
|
||||
return std::string();
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
|
||||
0, NI_NUMERICHOST)) {
|
||||
ip = ipstr.data();
|
||||
}
|
||||
}
|
||||
|
||||
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len)) {
|
||||
get_remote_ip_and_port(addr, addr_len, ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
inline const char *
|
||||
@@ -1820,7 +1862,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
// the left or right side of the header value:
|
||||
// - https://stackoverflow.com/questions/50179659/
|
||||
// - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
|
||||
static const std::regex re(R"(([^:]+):[\t ]*(.+))");
|
||||
static const std::regex re(R"(([^:]+):[\t ]*([^\t ].*))");
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(line_reader.ptr(), end, m, re)) {
|
||||
@@ -1889,9 +1931,17 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
auto chunk_len = std::stoul(line_reader.ptr(), 0, 16);
|
||||
unsigned long chunk_len;
|
||||
while (true) {
|
||||
char *end_ptr;
|
||||
|
||||
chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
|
||||
|
||||
if (end_ptr == line_reader.ptr()) { return false; }
|
||||
if (chunk_len == ULONG_MAX) { return false; }
|
||||
|
||||
if (chunk_len == 0) { break; }
|
||||
|
||||
while (chunk_len > 0) {
|
||||
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1901,8 +1951,6 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
chunk_len = std::stoul(line_reader.ptr(), 0, 16);
|
||||
}
|
||||
|
||||
if (chunk_len == 0) {
|
||||
@@ -2803,11 +2851,11 @@ inline void Response::set_header(const char *key, const std::string &val) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_redirect(const char *url, int status) {
|
||||
inline void Response::set_redirect(const char *url, int stat) {
|
||||
if (!detail::has_crlf(url)) {
|
||||
set_header("Location", url);
|
||||
if (300 <= status && status < 400) {
|
||||
this->status = status;
|
||||
if (300 <= stat && stat < 400) {
|
||||
this->status = stat;
|
||||
} else {
|
||||
this->status = 302;
|
||||
}
|
||||
@@ -2900,25 +2948,42 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
|
||||
inline SocketStream::~SocketStream() {}
|
||||
|
||||
inline bool SocketStream::is_readable() const {
|
||||
return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_writable() const {
|
||||
return detail::select_write(sock_, 0, 0) > 0;
|
||||
return select_write(sock_, 0, 0) > 0;
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
if (is_readable()) { return recv(sock_, ptr, size, 0); }
|
||||
return -1;
|
||||
if (!is_readable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return -1;
|
||||
}
|
||||
return recv(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return HANDLE_EINTR(recv, sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (is_writable()) { return send(sock_, ptr, size, 0); }
|
||||
return -1;
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return -1;
|
||||
}
|
||||
return send(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return HANDLE_EINTR(send, sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::string SocketStream::get_remote_addr() const {
|
||||
return detail::get_remote_addr(sock_);
|
||||
inline void SocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
return detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
// Buffer stream implementation
|
||||
@@ -2941,7 +3006,8 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
|
||||
return static_cast<ssize_t>(size);
|
||||
}
|
||||
|
||||
inline std::string BufferStream::get_remote_addr() const { return ""; }
|
||||
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
|
||||
int & /*port*/) const {}
|
||||
|
||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
|
||||
@@ -3421,17 +3487,16 @@ inline int Server::bind_internal(const char *host, int port, int socket_flags) {
|
||||
if (svr_sock_ == INVALID_SOCKET) { return -1; }
|
||||
|
||||
if (port == 0) {
|
||||
struct sockaddr_storage address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&address),
|
||||
&len) == -1) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (address.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
|
||||
} else if (address.ss_family == AF_INET6) {
|
||||
return ntohs(
|
||||
reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
|
||||
if (addr.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
@@ -3456,6 +3521,7 @@ inline bool Server::listen_internal() {
|
||||
auto val = detail::select_read(svr_sock_, 0, 100000);
|
||||
|
||||
if (val == 0) { // Timeout
|
||||
task_queue->on_idle();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3636,7 +3702,9 @@ Server::process_request(Stream &strm, bool last_connection,
|
||||
connection_close = true;
|
||||
}
|
||||
|
||||
req.set_header("REMOTE_ADDR", strm.get_remote_addr());
|
||||
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
|
||||
req.set_header("REMOTE_ADDR", req.remote_addr);
|
||||
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
|
||||
|
||||
if (req.has_header("Range")) {
|
||||
const auto &range_header_value = req.get_header_value("Range");
|
||||
@@ -3687,7 +3755,7 @@ inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
inline Client::Client(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
: host_(host), port_(port),
|
||||
: sock_(INVALID_SOCKET), host_(host), port_(port),
|
||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
@@ -3723,18 +3791,19 @@ inline bool Client::read_response_line(Stream &strm, Response &res) {
|
||||
}
|
||||
|
||||
inline bool Client::send(const Request &req, Response &res) {
|
||||
auto sock = create_client_socket();
|
||||
if (sock == INVALID_SOCKET) { return false; }
|
||||
sock_ = create_client_socket();
|
||||
if (sock_ == INVALID_SOCKET) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl() && !proxy_host_.empty()) {
|
||||
bool error;
|
||||
if (!connect(sock, res, error)) { return error; }
|
||||
if (!connect(sock_, res, error)) { return error; }
|
||||
}
|
||||
#endif
|
||||
|
||||
return process_and_close_socket(
|
||||
sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) {
|
||||
sock_, 1,
|
||||
[&](Stream &strm, bool last_connection, bool &connection_close) {
|
||||
return handle_request(strm, req, res, last_connection,
|
||||
connection_close);
|
||||
});
|
||||
@@ -3744,18 +3813,18 @@ inline bool Client::send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses) {
|
||||
size_t i = 0;
|
||||
while (i < requests.size()) {
|
||||
auto sock = create_client_socket();
|
||||
if (sock == INVALID_SOCKET) { return false; }
|
||||
sock_ = create_client_socket();
|
||||
if (sock_ == INVALID_SOCKET) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl() && !proxy_host_.empty()) {
|
||||
Response res;
|
||||
bool error;
|
||||
if (!connect(sock, res, error)) { return false; }
|
||||
if (!connect(sock_, res, error)) { return false; }
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!process_and_close_socket(sock, requests.size() - i,
|
||||
if (!process_and_close_socket(sock_, requests.size() - i,
|
||||
[&](Stream &strm, bool last_connection,
|
||||
bool &connection_close) -> bool {
|
||||
auto &req = requests[i++];
|
||||
@@ -3885,34 +3954,42 @@ inline bool Client::redirect(const Request &req, Response &res) {
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
|
||||
std::smatch m;
|
||||
if (!regex_match(location, m, re)) { return false; }
|
||||
if (!std::regex_match(location, m, re)) { return false; }
|
||||
|
||||
auto scheme = is_ssl() ? "https" : "http";
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto next_path = m[3].str();
|
||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
|
||||
auto next_port = port_;
|
||||
if (!port_str.empty()) {
|
||||
next_port = std::stoi(port_str);
|
||||
} else if (!next_scheme.empty()) {
|
||||
next_port = next_scheme == "https" ? 443 : 80;
|
||||
}
|
||||
|
||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||
if (next_host.empty()) { next_host = host_; }
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
if (next_scheme == scheme && next_host == host_) {
|
||||
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
|
||||
return detail::redirect(*this, req, res, next_path);
|
||||
} else {
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str());
|
||||
SSLClient cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
Client cli(next_host.c_str());
|
||||
Client cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path);
|
||||
}
|
||||
@@ -4022,7 +4099,7 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (compress_) {
|
||||
@@ -4214,6 +4291,10 @@ inline std::shared_ptr<Response> Client::Head(const char *path,
|
||||
return send(req, *res) ? res : nullptr;
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Post(const char *path) {
|
||||
return Post(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Post(const char *path,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
@@ -4286,6 +4367,10 @@ Client::Post(const char *path, const Headers &headers,
|
||||
return Post(path, headers, body, content_type.c_str());
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Put(const char *path) {
|
||||
return Put(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Put(const char *path,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
@@ -4403,6 +4488,14 @@ inline std::shared_ptr<Response> Client::Options(const char *path,
|
||||
return send(req, *res) ? res : nullptr;
|
||||
}
|
||||
|
||||
inline void Client::stop() {
|
||||
if (sock_ != INVALID_SOCKET) {
|
||||
std::atomic<socket_t> sock(sock_.exchange(INVALID_SOCKET));
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Client::set_timeout_sec(time_t timeout_sec) {
|
||||
timeout_sec_ = timeout_sec;
|
||||
}
|
||||
@@ -4501,8 +4594,8 @@ inline bool process_and_close_socket_ssl(
|
||||
auto count = keep_alive_max_count;
|
||||
while (count > 0 &&
|
||||
(is_client_request ||
|
||||
detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
||||
select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
|
||||
auto last_connection = count == 1;
|
||||
auto connection_close = false;
|
||||
@@ -4613,8 +4706,9 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline std::string SSLSocketStream::get_remote_addr() const {
|
||||
return detail::get_remote_addr(sock_);
|
||||
inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
static SSLInit sslinit_;
|
||||
@@ -4768,7 +4862,7 @@ inline long SSLClient::get_openssl_verify_result() const {
|
||||
return verify_result_;
|
||||
}
|
||||
|
||||
inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; }
|
||||
inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
|
||||
|
||||
inline bool SSLClient::process_and_close_socket(
|
||||
socket_t sock, size_t request_count,
|
||||
@@ -4959,8 +5053,73 @@ inline bool SSLClient::check_host_name(const char *pattern,
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace url {
|
||||
|
||||
struct Options {
|
||||
// TODO: support more options...
|
||||
bool follow_location = false;
|
||||
std::string client_cert_path;
|
||||
std::string client_key_path;
|
||||
|
||||
std::string ca_cert_file_path;
|
||||
std::string ca_cert_dir_path;
|
||||
bool server_certificate_verification = false;
|
||||
};
|
||||
|
||||
inline std::shared_ptr<Response> Get(const char *url, Options &options) {
|
||||
const static std::regex re(
|
||||
R"(^(https?)://([^:/?#]+)(?::(\d+))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
|
||||
std::cmatch m;
|
||||
if (!std::regex_match(url, m, re)) { return nullptr; }
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
|
||||
auto next_port = !port_str.empty() ? std::stoi(port_str)
|
||||
: (next_scheme == "https" ? 443 : 80);
|
||||
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str(), next_port, options.client_cert_path,
|
||||
options.client_key_path);
|
||||
cli.set_follow_location(options.follow_location);
|
||||
cli.set_ca_cert_path(options.ca_cert_file_path.c_str(),
|
||||
options.ca_cert_dir_path.c_str());
|
||||
cli.enable_server_certificate_verification(
|
||||
options.server_certificate_verification);
|
||||
return cli.Get(next_path.c_str());
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
} else {
|
||||
Client cli(next_host.c_str(), next_port, options.client_cert_path,
|
||||
options.client_key_path);
|
||||
cli.set_follow_location(options.follow_location);
|
||||
return cli.Get(next_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Get(const char *url) {
|
||||
Options options;
|
||||
return Get(url, options);
|
||||
}
|
||||
|
||||
} // namespace url
|
||||
|
||||
namespace detail {
|
||||
|
||||
#undef HANDLE_EINTR
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
#endif // CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
|
||||
+189
-17
@@ -649,12 +649,78 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTestWithURL, Redirect) {
|
||||
auto res = httplib::url::Get("http://yahoo.com");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
res = httplib::url::Get("http://yahoo.com", options);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
httplib::SSLClient cli("httpbin.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTestWithURL, Redirect) {
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
auto res = httplib::url::Get(
|
||||
"https://httpbin.org/"
|
||||
"redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302",
|
||||
options);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr8080;
|
||||
Server svr8081;
|
||||
|
||||
svr8080.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_redirect("http://localhost:8081/2");
|
||||
});
|
||||
|
||||
svr8081.Get("/2", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto thread8080 = std::thread([&]() { svr8080.listen("localhost", 8080); });
|
||||
|
||||
auto thread8081 = std::thread([&]() { svr8081.listen("localhost", 8081); });
|
||||
|
||||
while (!svr8080.is_running() || !svr8081.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
Client cli("localhost", 8080);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/1");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(res->body, "Hello World!");
|
||||
|
||||
svr8080.stop();
|
||||
svr8081.stop();
|
||||
thread8080.join();
|
||||
thread8081.join();
|
||||
ASSERT_FALSE(svr8080.is_running());
|
||||
ASSERT_FALSE(svr8081.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -735,6 +801,10 @@ protected:
|
||||
.Get("/remote_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
auto remote_addr = req.headers.find("REMOTE_ADDR")->second;
|
||||
EXPECT_TRUE(req.has_header("REMOTE_PORT"));
|
||||
EXPECT_EQ(req.remote_addr, req.get_header_value("REMOTE_ADDR"));
|
||||
EXPECT_EQ(req.remote_port,
|
||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
||||
res.set_content(remote_addr.c_str(), "text/plain");
|
||||
})
|
||||
.Get("/endwith%",
|
||||
@@ -871,13 +941,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("text default", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("aωb", file.content);
|
||||
}
|
||||
|
||||
@@ -890,7 +960,7 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("file3");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.content.size());
|
||||
}
|
||||
@@ -898,8 +968,24 @@ protected:
|
||||
.Post("/empty",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_EQ("text/plain", req.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty", "text/plain");
|
||||
})
|
||||
.Post("/empty-no-content-type",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_FALSE(req.has_header("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Put("/empty-no-content-type",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
EXPECT_FALSE(req.has_header("Content-Type"));
|
||||
EXPECT_EQ("0", req.get_header_value("Content-Length"));
|
||||
res.set_content("empty-no-content-type", "text/plain");
|
||||
})
|
||||
.Put("/put",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "PUT");
|
||||
@@ -970,13 +1056,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "text1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("text default", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "text2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("aωb", file.content);
|
||||
}
|
||||
|
||||
@@ -989,7 +1075,7 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "file3");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.content.size());
|
||||
}
|
||||
@@ -1054,13 +1140,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("key1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("test", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("key2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("--abcdefg123", file.content);
|
||||
}
|
||||
})
|
||||
@@ -1142,7 +1228,7 @@ TEST_F(ServerTest, HeadMethod200) {
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod200Static) {
|
||||
@@ -1151,14 +1237,14 @@ TEST_F(ServerTest, HeadMethod200Static) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ(104, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod404) {
|
||||
auto res = cli_.Head("/invalid");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodPersonJohn) {
|
||||
@@ -1244,6 +1330,20 @@ TEST_F(ServerTest, PostEmptyContent) {
|
||||
ASSERT_EQ("empty", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
||||
auto res = cli_.Post("/empty-no-content-type");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutEmptyContentWithNoContentType) {
|
||||
auto res = cli_.Put("/empty-no-content-type");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodDir) {
|
||||
auto res = cli_.Get("/dir/");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1647,6 +1747,18 @@ TEST_F(ServerTest, GetStreamedEndless) {
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, ClientStop) {
|
||||
thread t = thread([&]() {
|
||||
auto res =
|
||||
cli_.Get("/streamed-cancel",
|
||||
[&](const char *, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
cli_.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange1) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{3, 5}})}});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1992,7 +2104,7 @@ TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
|
||||
auto res = cli_.Get("/gzip", headers);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
@@ -2033,7 +2145,7 @@ TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
@@ -2093,7 +2205,8 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
#endif
|
||||
|
||||
// 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,
|
||||
std::string *resp = nullptr) {
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
|
||||
std::string());
|
||||
|
||||
@@ -2111,7 +2224,9 @@ static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
char buf[512];
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf, sizeof(buf));
|
||||
while (line_reader.getline()) {}
|
||||
while (line_reader.getline()) {
|
||||
if (resp) { *resp += line_reader.ptr(); }
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -2143,11 +2258,15 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
}
|
||||
|
||||
// Sends a raw request and verifies that there isn't a crash or exception.
|
||||
static void test_raw_request(const std::string &req) {
|
||||
static void test_raw_request(const std::string &req,
|
||||
std::string *out = nullptr) {
|
||||
Server svr;
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
// Server read timeout must be longer than the client read timeout for the
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
@@ -2160,7 +2279,7 @@ static void test_raw_request(const std::string &req) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req, out));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
@@ -2214,6 +2333,59 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
|
||||
// Make sure this doesn't crash the server.
|
||||
// In a previous version of the header line regex, the "\r" rendered the line
|
||||
// unparseable and the regex engine repeatedly backtracked, trying to look for
|
||||
// a new position where the leading white space ended and the field value
|
||||
// began.
|
||||
// The crash occurs with libc++ but not libstdc++.
|
||||
test_raw_request("GET /hi HTTP/1.1\r\n"
|
||||
"a:" + std::string(2000, ' ') + '\r' + std::string(20, 'z') +
|
||||
"\r\n"
|
||||
"\r\n");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidFirstChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"nothex\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidSecondChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"3\r\n"
|
||||
"xyz\r\n"
|
||||
"NaN\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ChunkLengthTooHighInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
// Length is too large for 64 bits.
|
||||
"1ffffffffffffffff\r\n"
|
||||
"xyz\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
|
||||
+13
-13
@@ -28,7 +28,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
@@ -40,7 +40,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
@@ -69,13 +69,13 @@
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\lib\VC;C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
@@ -84,14 +84,14 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
|
||||
@@ -108,7 +108,7 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
|
||||
@@ -118,12 +118,12 @@
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
@@ -144,7 +144,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
@@ -160,7 +160,7 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user