mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c88b09bc6b | |||
| 87fab847b8 | |||
| 4e6055f084 | |||
| 975cf0dae5 | |||
| 4854a694cd | |||
| b1f8e986bf | |||
| c5ee208775 | |||
| ddfdacfa49 | |||
| 2514ebc20f | |||
| 4f9c6540b2 | |||
| 21c9a6a1ff | |||
| 7f6d413ddd | |||
| 88277139e7 | |||
| 6cdd3493a1 | |||
| 9c91b6f4a6 | |||
| cee838e335 | |||
| d82c82db2c | |||
| ba638ff38e | |||
| da0c6579fa | |||
| 52a18c78a5 | |||
| 048edec9ed | |||
| af56b7ec0b | |||
| 6c3e8482f7 | |||
| 390f2c41f6 | |||
| aa04feebb4 | |||
| 45f3694f82 |
+11
@@ -0,0 +1,11 @@
|
||||
FROM yhirose4dockerhub/ubuntu-builder AS builder
|
||||
WORKDIR /build
|
||||
COPY httplib.h .
|
||||
COPY docker/main.cc .
|
||||
RUN g++ -std=c++23 -static -o server -O2 -I. -DCPPHTTPLIB_USE_POLL main.cc && strip server
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /build/server /server
|
||||
COPY docker/html/index.html /html/index.html
|
||||
EXPOSE 80
|
||||
CMD ["/server"]
|
||||
@@ -848,6 +848,22 @@ $ ./split.py
|
||||
Wrote out/httplib.h and out/httplib.cc
|
||||
```
|
||||
|
||||
Dockerfile for Static HTTP Server
|
||||
---------------------------------
|
||||
|
||||
Dockerfile for static HTTP server is available. Port number of this HTTP server is 80, and it serves static files from `/html` directory in the container.
|
||||
|
||||
```bash
|
||||
> docker build -t cpp-httplib-server .
|
||||
...
|
||||
|
||||
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
|
||||
Serving HTTP on 0.0.0.0 port 80 ...
|
||||
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
|
||||
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
|
||||
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
|
||||
```
|
||||
|
||||
NOTE
|
||||
----
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
http:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./docker/html:/html
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to cpp-httplib!</title>
|
||||
<style>
|
||||
html { color-scheme: light dark; }
|
||||
body { width: 35em; margin: 0 auto;
|
||||
font-family: Tahoma, Verdana, Arial, sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to cpp-httplib!</h1>
|
||||
<p>If you see this page, the cpp-httplib web server is successfully installed and
|
||||
working. Further configuration is required.</p>
|
||||
|
||||
<p>For online documentation and support please refer to
|
||||
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
|
||||
|
||||
<p><em>Thank you for using cpp-httplib.</em></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <format>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
constexpr auto error_html = R"(<html>
|
||||
<head><title>{} {}</title></head>
|
||||
<body>
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr><center>cpp-httplib/{}</center>
|
||||
</body>
|
||||
</html>
|
||||
)";
|
||||
|
||||
void sigint_handler(int s) { exit(1); }
|
||||
|
||||
std::string time_local() {
|
||||
auto p = std::chrono::system_clock::now();
|
||||
auto t = std::chrono::system_clock::to_time_t(p);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&t), "%d/%b/%Y:%H:%M:%S %z");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string log(auto &req, auto &res) {
|
||||
auto remote_user = "-"; // TODO:
|
||||
auto request = std::format("{} {} {}", req.method, req.path, req.version);
|
||||
auto body_bytes_sent = res.get_header_value("Content-Length");
|
||||
auto http_referer = "-"; // TODO:
|
||||
auto http_user_agent = req.get_header_value("User-Agent", "-");
|
||||
|
||||
// NOTE: From NGINX defualt access log format
|
||||
// log_format combined '$remote_addr - $remote_user [$time_local] '
|
||||
// '"$request" $status $body_bytes_sent '
|
||||
// '"$http_referer" "$http_user_agent"';
|
||||
return std::format(R"({} - {} [{}] "{}" {} {} "{}" "{}")", req.remote_addr,
|
||||
remote_user, time_local(), request, res.status,
|
||||
body_bytes_sent, http_referer, http_user_agent);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
auto base_dir = "./html";
|
||||
auto host = "0.0.0.0";
|
||||
auto port = 80;
|
||||
|
||||
httplib::Server svr;
|
||||
|
||||
svr.set_error_handler([](auto & /*req*/, auto &res) {
|
||||
auto body =
|
||||
std::format(error_html, res.status, httplib::status_message(res.status),
|
||||
CPPHTTPLIB_VERSION);
|
||||
|
||||
res.set_content(body, "text/html");
|
||||
});
|
||||
|
||||
svr.set_logger(
|
||||
[](auto &req, auto &res) { std::cout << log(req, res) << std::endl; });
|
||||
|
||||
svr.set_mount_point("/", base_dir);
|
||||
|
||||
std::cout << std::format("Serving HTTP on {0} port {1} ...", host, port)
|
||||
<< std::endl;
|
||||
|
||||
auto ret = svr.listen(host, port);
|
||||
|
||||
return ret ? 0 : 1;
|
||||
}
|
||||
+1
-1
@@ -58,4 +58,4 @@ pem:
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request *.pem
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.16.2"
|
||||
#define CPPHTTPLIB_VERSION "0.17.1"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -19,7 +19,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
|
||||
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5
|
||||
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
|
||||
@@ -90,6 +90,10 @@
|
||||
#define CPPHTTPLIB_TCP_NODELAY false
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_IPV6_V6ONLY
|
||||
#define CPPHTTPLIB_IPV6_V6ONLY false
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_RECV_BUFSIZ
|
||||
#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
|
||||
#endif
|
||||
@@ -565,8 +569,10 @@ struct Request {
|
||||
#endif
|
||||
|
||||
bool has_header(const std::string &key) const;
|
||||
std::string get_header_value(const std::string &key, size_t id = 0) const;
|
||||
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
|
||||
std::string get_header_value(const std::string &key, const char *def = "",
|
||||
size_t id = 0) const;
|
||||
uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
|
||||
size_t id = 0) const;
|
||||
size_t get_header_value_count(const std::string &key) const;
|
||||
void set_header(const std::string &key, const std::string &val);
|
||||
|
||||
@@ -597,8 +603,10 @@ struct Response {
|
||||
std::string location; // Redirect location
|
||||
|
||||
bool has_header(const std::string &key) const;
|
||||
std::string get_header_value(const std::string &key, size_t id = 0) const;
|
||||
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
|
||||
std::string get_header_value(const std::string &key, const char *def = "",
|
||||
size_t id = 0) const;
|
||||
uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0,
|
||||
size_t id = 0) const;
|
||||
size_t get_header_value_count(const std::string &key) const;
|
||||
void set_header(const std::string &key, const std::string &val);
|
||||
|
||||
@@ -896,6 +904,7 @@ public:
|
||||
|
||||
Server &set_address_family(int family);
|
||||
Server &set_tcp_nodelay(bool on);
|
||||
Server &set_ipv6_v6only(bool on);
|
||||
Server &set_socket_options(SocketOptions socket_options);
|
||||
|
||||
Server &set_default_headers(Headers headers);
|
||||
@@ -928,6 +937,7 @@ public:
|
||||
bool is_running() const;
|
||||
void wait_until_ready() const;
|
||||
void stop();
|
||||
void decommission();
|
||||
|
||||
std::function<TaskQueue *(void)> new_task_queue;
|
||||
|
||||
@@ -1002,7 +1012,7 @@ private:
|
||||
virtual bool process_and_close_socket(socket_t sock);
|
||||
|
||||
std::atomic<bool> is_running_{false};
|
||||
std::atomic<bool> done_{false};
|
||||
std::atomic<bool> is_decommisioned{false};
|
||||
|
||||
struct MountPointEntry {
|
||||
std::string mount_point;
|
||||
@@ -1035,6 +1045,7 @@ private:
|
||||
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
|
||||
SocketOptions socket_options_ = default_socket_options;
|
||||
|
||||
Headers default_headers_;
|
||||
@@ -1091,9 +1102,10 @@ public:
|
||||
// Request Headers
|
||||
bool has_request_header(const std::string &key) const;
|
||||
std::string get_request_header_value(const std::string &key,
|
||||
const char *def = "",
|
||||
size_t id = 0) const;
|
||||
uint64_t get_request_header_value_u64(const std::string &key,
|
||||
size_t id = 0) const;
|
||||
uint64_t def = 0, size_t id = 0) const;
|
||||
size_t get_request_header_value_count(const std::string &key) const;
|
||||
|
||||
private:
|
||||
@@ -1316,6 +1328,7 @@ public:
|
||||
|
||||
void set_address_family(int family);
|
||||
void set_tcp_nodelay(bool on);
|
||||
void set_ipv6_v6only(bool on);
|
||||
void set_socket_options(SocketOptions socket_options);
|
||||
|
||||
void set_connection_timeout(time_t sec, time_t usec = 0);
|
||||
@@ -1453,6 +1466,7 @@ protected:
|
||||
|
||||
int address_family_ = AF_UNSPEC;
|
||||
bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
|
||||
bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
|
||||
SocketOptions socket_options_ = nullptr;
|
||||
|
||||
bool compress_ = false;
|
||||
@@ -1914,8 +1928,8 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
|
||||
}
|
||||
|
||||
inline uint64_t get_header_value_u64(const Headers &headers,
|
||||
const std::string &key, size_t id,
|
||||
uint64_t def) {
|
||||
const std::string &key, uint64_t def,
|
||||
size_t id) {
|
||||
auto rng = headers.equal_range(key);
|
||||
auto it = rng.first;
|
||||
std::advance(it, static_cast<ssize_t>(id));
|
||||
@@ -1928,13 +1942,13 @@ inline uint64_t get_header_value_u64(const Headers &headers,
|
||||
} // namespace detail
|
||||
|
||||
inline uint64_t Request::get_header_value_u64(const std::string &key,
|
||||
size_t id) const {
|
||||
return detail::get_header_value_u64(headers, key, id, 0);
|
||||
uint64_t def, size_t id) const {
|
||||
return detail::get_header_value_u64(headers, key, def, id);
|
||||
}
|
||||
|
||||
inline uint64_t Response::get_header_value_u64(const std::string &key,
|
||||
size_t id) const {
|
||||
return detail::get_header_value_u64(headers, key, id, 0);
|
||||
uint64_t def, size_t id) const {
|
||||
return detail::get_header_value_u64(headers, key, def, id);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@@ -1962,19 +1976,19 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) {
|
||||
}
|
||||
|
||||
inline void default_socket_options(socket_t sock) {
|
||||
int yes = 1;
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
|
||||
reinterpret_cast<const char *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
#else
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
|
||||
reinterpret_cast<const void *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#else
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const void *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -2119,8 +2133,9 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
|
||||
}
|
||||
|
||||
inline uint64_t Result::get_request_header_value_u64(const std::string &key,
|
||||
uint64_t def,
|
||||
size_t id) const {
|
||||
return detail::get_header_value_u64(request_headers_, key, id, 0);
|
||||
return detail::get_header_value_u64(request_headers_, key, def, id);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
@@ -2183,6 +2198,10 @@ make_basic_authentication_header(const std::string &username,
|
||||
|
||||
namespace detail {
|
||||
|
||||
bool is_file(const std::string &path);
|
||||
|
||||
bool is_dir(const std::string &path);
|
||||
|
||||
std::string encode_query_param(const std::string &value);
|
||||
|
||||
std::string decode_url(const std::string &s, bool convert_plus_to_space);
|
||||
@@ -2212,15 +2231,18 @@ bool process_client_socket(socket_t sock, time_t read_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
std::function<bool(Stream &)> callback);
|
||||
|
||||
socket_t create_client_socket(
|
||||
const std::string &host, const std::string &ip, int port,
|
||||
int address_family, bool tcp_nodelay, SocketOptions socket_options,
|
||||
time_t connection_timeout_sec, time_t connection_timeout_usec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error);
|
||||
socket_t create_client_socket(const std::string &host, const std::string &ip,
|
||||
int port, int address_family, bool tcp_nodelay,
|
||||
bool ipv6_v6only, SocketOptions socket_options,
|
||||
time_t connection_timeout_sec,
|
||||
time_t connection_timeout_usec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
const std::string &intf, Error &error);
|
||||
|
||||
const char *get_header_value(const Headers &headers, const std::string &key,
|
||||
size_t id = 0, const char *def = nullptr);
|
||||
const char *def, size_t id);
|
||||
|
||||
std::string params_to_query_str(const Params ¶ms);
|
||||
|
||||
@@ -2507,20 +2529,6 @@ inline std::string base64_encode(const std::string &in) {
|
||||
return out;
|
||||
}
|
||||
|
||||
inline bool is_file(const std::string &path) {
|
||||
#ifdef _WIN32
|
||||
return _access_s(path.c_str(), 0) == 0;
|
||||
#else
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool is_dir(const std::string &path) {
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
inline bool is_valid_path(const std::string &path) {
|
||||
size_t level = 0;
|
||||
size_t i = 0;
|
||||
@@ -2563,6 +2571,16 @@ inline bool is_valid_path(const std::string &path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool is_file(const std::string &path) {
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
inline bool is_dir(const std::string &path) {
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
inline std::string encode_query_param(const std::string &value) {
|
||||
std::ostringstream escaped;
|
||||
escaped.fill('0');
|
||||
@@ -2772,6 +2790,10 @@ inline bool stream_line_reader::getline() {
|
||||
fixed_buffer_used_size_ = 0;
|
||||
glowable_buffer_.clear();
|
||||
|
||||
#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
||||
char prev_byte = 0;
|
||||
#endif
|
||||
|
||||
for (size_t i = 0;; i++) {
|
||||
char byte;
|
||||
auto n = strm_.read(&byte, 1);
|
||||
@@ -2788,7 +2810,12 @@ inline bool stream_line_reader::getline() {
|
||||
|
||||
append(byte);
|
||||
|
||||
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
||||
if (byte == '\n') { break; }
|
||||
#else
|
||||
if (prev_byte == '\r' && byte == '\n') { break; }
|
||||
prev_byte = byte;
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2829,9 +2856,7 @@ inline bool mmap::open(const char *path) {
|
||||
wpath += path[i];
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
|
||||
WINAPI_PARTITION_GAMES) && \
|
||||
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||||
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
|
||||
OPEN_EXISTING, NULL);
|
||||
#else
|
||||
@@ -2841,26 +2866,23 @@ inline bool mmap::open(const char *path) {
|
||||
|
||||
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | \
|
||||
WINAPI_PARTITION_GAMES)
|
||||
LARGE_INTEGER size{};
|
||||
if (!::GetFileSizeEx(hFile_, &size)) { return false; }
|
||||
// If the following line doesn't compile due to QuadPart, update Windows SDK.
|
||||
// See:
|
||||
// https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
|
||||
if (static_cast<ULONGLONG>(size.QuadPart) >
|
||||
std::numeric_limits<decltype(size_)>::max()) {
|
||||
// `size_t` might be 32-bits, on 32-bits Windows.
|
||||
return false;
|
||||
}
|
||||
size_ = static_cast<size_t>(size.QuadPart);
|
||||
#else
|
||||
DWORD sizeHigh;
|
||||
DWORD sizeLow;
|
||||
sizeLow = ::GetFileSize(hFile_, &sizeHigh);
|
||||
if (sizeLow == INVALID_FILE_SIZE) { return false; }
|
||||
size_ = (static_cast<size_t>(sizeHigh) << (sizeof(DWORD) * 8)) | sizeLow;
|
||||
#endif
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
|
||||
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||||
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||
hMapping_ =
|
||||
::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
|
||||
#else
|
||||
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, size.HighPart,
|
||||
size.LowPart, NULL);
|
||||
hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
#endif
|
||||
|
||||
if (hMapping_ == NULL) {
|
||||
@@ -2868,8 +2890,7 @@ inline bool mmap::open(const char *path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && \
|
||||
(_WIN32_WINNT >= _WIN32_WINNT_WIN8)
|
||||
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||
addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
|
||||
#else
|
||||
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
|
||||
@@ -2945,7 +2966,10 @@ template <typename T> inline ssize_t handle_EINTR(T fn) {
|
||||
ssize_t res = 0;
|
||||
while (true) {
|
||||
res = fn();
|
||||
if (res < 0 && errno == EINTR) { continue; }
|
||||
if (res < 0 && errno == EINTR) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
@@ -3226,10 +3250,28 @@ inline int shutdown_socket(socket_t sock) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::string escape_abstract_namespace_unix_domain(const std::string& s) {
|
||||
if (s.size() > 1 && s[0] == '\0') {
|
||||
auto ret = s;
|
||||
ret[0] = '@';
|
||||
return ret;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string unescape_abstract_namespace_unix_domain(const std::string& s) {
|
||||
if (s.size() > 1 && s[0] == '@') {
|
||||
auto ret = s;
|
||||
ret[0] = '\0';
|
||||
return ret;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename BindOrConnect>
|
||||
socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
int address_family, int socket_flags, bool tcp_nodelay,
|
||||
SocketOptions socket_options,
|
||||
bool ipv6_v6only, SocketOptions socket_options,
|
||||
BindOrConnect bind_or_connect) {
|
||||
// Get address info
|
||||
const char *node = nullptr;
|
||||
@@ -3238,7 +3280,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = 0;
|
||||
hints.ai_protocol = IPPROTO_IP;
|
||||
|
||||
if (!ip.empty()) {
|
||||
node = ip.c_str();
|
||||
@@ -3266,7 +3308,9 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
if (sock != INVALID_SOCKET) {
|
||||
sockaddr_un addr{};
|
||||
addr.sun_family = AF_UNIX;
|
||||
std::copy(host.begin(), host.end(), addr.sun_path);
|
||||
|
||||
auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
|
||||
std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
|
||||
|
||||
hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
|
||||
hints.ai_addrlen = static_cast<socklen_t>(
|
||||
@@ -3278,7 +3322,8 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
|
||||
if (socket_options) { socket_options(sock); }
|
||||
|
||||
if (!bind_or_connect(sock, hints)) {
|
||||
bool dummy;
|
||||
if (!bind_or_connect(sock, hints, dummy)) {
|
||||
close_socket(sock);
|
||||
sock = INVALID_SOCKET;
|
||||
}
|
||||
@@ -3339,36 +3384,39 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port,
|
||||
#endif
|
||||
|
||||
if (tcp_nodelay) {
|
||||
auto yes = 1;
|
||||
auto opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const char *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
||||
reinterpret_cast<const void *>(&yes), sizeof(yes));
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#endif
|
||||
}
|
||||
|
||||
if (rp->ai_family == AF_INET6) {
|
||||
auto opt = ipv6_v6only ? 1 : 0;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const void *>(&opt), sizeof(opt));
|
||||
#endif
|
||||
}
|
||||
|
||||
if (socket_options) { socket_options(sock); }
|
||||
|
||||
if (rp->ai_family == AF_INET6) {
|
||||
auto no = 0;
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const char *>(&no), sizeof(no));
|
||||
#else
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
|
||||
reinterpret_cast<const void *>(&no), sizeof(no));
|
||||
#endif
|
||||
}
|
||||
|
||||
// bind or connect
|
||||
if (bind_or_connect(sock, *rp)) {
|
||||
auto quit = false;
|
||||
if (bind_or_connect(sock, *rp, quit)) {
|
||||
freeaddrinfo(result);
|
||||
return sock;
|
||||
}
|
||||
|
||||
close_socket(sock);
|
||||
|
||||
if (quit) { break; }
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
@@ -3463,13 +3511,15 @@ inline std::string if2ip(int address_family, const std::string &ifn) {
|
||||
|
||||
inline socket_t create_client_socket(
|
||||
const std::string &host, const std::string &ip, int port,
|
||||
int address_family, bool tcp_nodelay, SocketOptions socket_options,
|
||||
time_t connection_timeout_sec, time_t connection_timeout_usec,
|
||||
time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
int address_family, bool tcp_nodelay, bool ipv6_v6only,
|
||||
SocketOptions socket_options, time_t connection_timeout_sec,
|
||||
time_t connection_timeout_usec, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, const std::string &intf, Error &error) {
|
||||
auto sock = create_socket(
|
||||
host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options),
|
||||
[&](socket_t sock2, struct addrinfo &ai) -> bool {
|
||||
host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
|
||||
std::move(socket_options),
|
||||
[&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifdef USE_IF2IP
|
||||
auto ip_from_if = if2ip(address_family, intf);
|
||||
@@ -3493,7 +3543,10 @@ inline socket_t create_client_socket(
|
||||
}
|
||||
error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
|
||||
connection_timeout_usec);
|
||||
if (error != Error::Success) { return false; }
|
||||
if (error != Error::Success) {
|
||||
if (error == Error::ConnectionTimeout) { quit = true; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set_nonblocking(sock2, false);
|
||||
@@ -3941,8 +3994,8 @@ inline bool has_header(const Headers &headers, const std::string &key) {
|
||||
}
|
||||
|
||||
inline const char *get_header_value(const Headers &headers,
|
||||
const std::string &key, size_t id,
|
||||
const char *def) {
|
||||
const std::string &key, const char *def,
|
||||
size_t id) {
|
||||
auto rng = headers.equal_range(key);
|
||||
auto it = rng.first;
|
||||
std::advance(it, static_cast<ssize_t>(id));
|
||||
@@ -3988,6 +4041,18 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
auto val = compare_case_ignore(key, "Location")
|
||||
? std::string(p, end)
|
||||
: decode_url(std::string(p, end), false);
|
||||
|
||||
// NOTE: From RFC 9110:
|
||||
// Field values containing CR, LF, or NUL characters are
|
||||
// invalid and dangerous, due to the varying ways that
|
||||
// implementations might parse and interpret those
|
||||
// characters; a recipient of CR, LF, or NUL within a field
|
||||
// value MUST either reject the message or replace each of
|
||||
// those characters with SP before further processing or
|
||||
// forwarding of that message.
|
||||
static const std::string CR_LF_NUL("\r\n\0", 3);
|
||||
if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; }
|
||||
|
||||
fn(key, val);
|
||||
return true;
|
||||
}
|
||||
@@ -4025,10 +4090,12 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
// Exclude line terminator
|
||||
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
|
||||
|
||||
parse_header(line_reader.ptr(), end,
|
||||
[&](const std::string &key, const std::string &val) {
|
||||
headers.emplace(key, val);
|
||||
});
|
||||
if (!parse_header(line_reader.ptr(), end,
|
||||
[&](const std::string &key, std::string &val) {
|
||||
headers.emplace(key, val);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -4139,7 +4206,7 @@ inline bool read_content_chunked(Stream &strm, T &x,
|
||||
|
||||
inline bool is_chunked_transfer_encoding(const Headers &headers) {
|
||||
return compare_case_ignore(
|
||||
get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked");
|
||||
get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
@@ -5482,8 +5549,8 @@ inline bool Request::has_header(const std::string &key) const {
|
||||
}
|
||||
|
||||
inline std::string Request::get_header_value(const std::string &key,
|
||||
size_t id) const {
|
||||
return detail::get_header_value(headers, key, id, "");
|
||||
const char *def, size_t id) const {
|
||||
return detail::get_header_value(headers, key, def, id);
|
||||
}
|
||||
|
||||
inline size_t Request::get_header_value_count(const std::string &key) const {
|
||||
@@ -5547,8 +5614,9 @@ inline bool Response::has_header(const std::string &key) const {
|
||||
}
|
||||
|
||||
inline std::string Response::get_header_value(const std::string &key,
|
||||
const char *def,
|
||||
size_t id) const {
|
||||
return detail::get_header_value(headers, key, id, "");
|
||||
return detail::get_header_value(headers, key, def, id);
|
||||
}
|
||||
|
||||
inline size_t Response::get_header_value_count(const std::string &key) const {
|
||||
@@ -5633,8 +5701,9 @@ inline bool Result::has_request_header(const std::string &key) const {
|
||||
}
|
||||
|
||||
inline std::string Result::get_request_header_value(const std::string &key,
|
||||
const char *def,
|
||||
size_t id) const {
|
||||
return detail::get_header_value(request_headers_, key, id, "");
|
||||
return detail::get_header_value(request_headers_, key, def, id);
|
||||
}
|
||||
|
||||
inline size_t
|
||||
@@ -6042,6 +6111,11 @@ inline Server &Server::set_tcp_nodelay(bool on) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_ipv6_v6only(bool on) {
|
||||
ipv6_v6only_ = on;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline Server &Server::set_socket_options(SocketOptions socket_options) {
|
||||
socket_options_ = std::move(socket_options);
|
||||
return *this;
|
||||
@@ -6093,27 +6167,27 @@ inline Server &Server::set_payload_max_length(size_t length) {
|
||||
|
||||
inline bool Server::bind_to_port(const std::string &host, int port,
|
||||
int socket_flags) {
|
||||
return bind_internal(host, port, socket_flags) >= 0;
|
||||
auto ret = bind_internal(host, port, socket_flags);
|
||||
if (ret == -1) { is_decommisioned = true; }
|
||||
return ret >= 0;
|
||||
}
|
||||
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
|
||||
return bind_internal(host, 0, socket_flags);
|
||||
auto ret = bind_internal(host, 0, socket_flags);
|
||||
if (ret == -1) { is_decommisioned = true; }
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool Server::listen_after_bind() {
|
||||
auto se = detail::scope_exit([&]() { done_ = true; });
|
||||
return listen_internal();
|
||||
}
|
||||
inline bool Server::listen_after_bind() { return listen_internal(); }
|
||||
|
||||
inline bool Server::listen(const std::string &host, int port,
|
||||
int socket_flags) {
|
||||
auto se = detail::scope_exit([&]() { done_ = true; });
|
||||
return bind_to_port(host, port, socket_flags) && listen_internal();
|
||||
}
|
||||
|
||||
inline bool Server::is_running() const { return is_running_; }
|
||||
|
||||
inline void Server::wait_until_ready() const {
|
||||
while (!is_running() && !done_) {
|
||||
while (!is_running_ && !is_decommisioned) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
}
|
||||
}
|
||||
@@ -6125,8 +6199,11 @@ inline void Server::stop() {
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
is_decommisioned = false;
|
||||
}
|
||||
|
||||
inline void Server::decommission() { is_decommisioned = true; }
|
||||
|
||||
inline bool Server::parse_request_line(const char *s, Request &req) const {
|
||||
auto len = strlen(s);
|
||||
if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
|
||||
@@ -6469,8 +6546,8 @@ Server::create_server_socket(const std::string &host, int port,
|
||||
SocketOptions socket_options) const {
|
||||
return detail::create_socket(
|
||||
host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
|
||||
std::move(socket_options),
|
||||
[](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
ipv6_v6only_, std::move(socket_options),
|
||||
[](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
|
||||
if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
|
||||
return false;
|
||||
}
|
||||
@@ -6481,6 +6558,8 @@ Server::create_server_socket(const std::string &host, int port,
|
||||
|
||||
inline int Server::bind_internal(const std::string &host, int port,
|
||||
int socket_flags) {
|
||||
if (is_decommisioned) { return -1; }
|
||||
|
||||
if (!is_valid()) { return -1; }
|
||||
|
||||
svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
|
||||
@@ -6506,6 +6585,8 @@ inline int Server::bind_internal(const std::string &host, int port,
|
||||
}
|
||||
|
||||
inline bool Server::listen_internal() {
|
||||
if (is_decommisioned) { return false; }
|
||||
|
||||
auto ret = true;
|
||||
is_running_ = true;
|
||||
auto se = detail::scope_exit([&]() { is_running_ = false; });
|
||||
@@ -6595,6 +6676,7 @@ inline bool Server::listen_internal() {
|
||||
task_queue->shutdown();
|
||||
}
|
||||
|
||||
is_decommisioned = !ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -6982,9 +7064,10 @@ inline ClientImpl::ClientImpl(const std::string &host, int port)
|
||||
inline ClientImpl::ClientImpl(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
: host_(host), port_(port),
|
||||
host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
: host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
|
||||
host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {
|
||||
}
|
||||
|
||||
inline ClientImpl::~ClientImpl() {
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
@@ -7014,6 +7097,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
url_encode_ = rhs.url_encode_;
|
||||
address_family_ = rhs.address_family_;
|
||||
tcp_nodelay_ = rhs.tcp_nodelay_;
|
||||
ipv6_v6only_ = rhs.ipv6_v6only_;
|
||||
socket_options_ = rhs.socket_options_;
|
||||
compress_ = rhs.compress_;
|
||||
decompress_ = rhs.decompress_;
|
||||
@@ -7042,9 +7126,9 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
return detail::create_client_socket(
|
||||
proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
ipv6_v6only_, socket_options_, connection_timeout_sec_,
|
||||
connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_, interface_, error);
|
||||
}
|
||||
|
||||
// Check is custom IP specified for host_
|
||||
@@ -7053,10 +7137,10 @@ inline socket_t ClientImpl::create_client_socket(Error &error) const {
|
||||
if (it != addr_map_.end()) { ip = it->second; }
|
||||
|
||||
return detail::create_client_socket(
|
||||
host_, ip, port_, address_family_, tcp_nodelay_, socket_options_,
|
||||
connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_,
|
||||
read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_,
|
||||
error);
|
||||
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
|
||||
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
|
||||
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, interface_, error);
|
||||
}
|
||||
|
||||
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
|
||||
@@ -7693,6 +7777,14 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (res.has_header("Content-Length")) {
|
||||
if (!req.content_receiver) {
|
||||
auto len = std::min<size_t>(res.get_header_value_u64("Content-Length"),
|
||||
res.body.max_size());
|
||||
if (len > 0) { res.body.reserve(len); }
|
||||
}
|
||||
}
|
||||
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, std::move(progress), std::move(out),
|
||||
@@ -8452,6 +8544,8 @@ inline void ClientImpl::set_address_family(int family) {
|
||||
|
||||
inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
|
||||
|
||||
inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
|
||||
|
||||
inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
|
||||
socket_options_ = std::move(socket_options);
|
||||
}
|
||||
|
||||
+105
-13
@@ -467,25 +467,25 @@ TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValue) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
||||
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
|
||||
EXPECT_STREQ("text/plain", val);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValueInt) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
|
||||
auto val = detail::get_header_value_u64(headers, "Content-Length", 100, 0);
|
||||
EXPECT_EQ(100ull, val);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, RegularValue) {
|
||||
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
||||
auto val = detail::get_header_value(headers, "Content-Type", "text/plain", 0);
|
||||
EXPECT_STREQ("text/html", val);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, RegularValueWithDifferentCase) {
|
||||
Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "content-type", 0, "text/plain");
|
||||
auto val = detail::get_header_value(headers, "content-type", "text/plain", 0);
|
||||
EXPECT_STREQ("text/html", val);
|
||||
}
|
||||
|
||||
@@ -4652,7 +4652,7 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
auto error = Error::Success;
|
||||
|
||||
auto client_sock = detail::create_client_socket(
|
||||
HOST, "", PORT, AF_UNSPEC, false, nullptr,
|
||||
HOST, "", PORT, AF_UNSPEC, false, false, nullptr,
|
||||
/*connection_timeout_sec=*/5, 0,
|
||||
/*read_timeout_sec=*/5, 0,
|
||||
/*write_timeout_sec=*/5, 0, std::string(), error);
|
||||
@@ -4718,6 +4718,9 @@ static void test_raw_request(const std::string &req,
|
||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Get("/header_field_value_check", [&](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
|
||||
@@ -4851,6 +4854,14 @@ TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
|
||||
std::string out;
|
||||
std::string request(
|
||||
"GET /header_field_value_check HTTP/1.1\r\nTest: [\r\x00\n]\r\n\r\n", 55);
|
||||
test_raw_request(request, &out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
@@ -4926,6 +4937,52 @@ TEST(ServerStopTest, ListenFailure) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, Decommision) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request &, Response &res) { res.body = "hi..."; });
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
auto is_even = !(i % 2);
|
||||
|
||||
std::thread t{[&] {
|
||||
try {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
if (is_even) {
|
||||
throw std::runtime_error("Some thing that happens to go wrong.");
|
||||
}
|
||||
|
||||
svr.listen(HOST, PORT);
|
||||
} catch (...) { svr.decommission(); }
|
||||
}};
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Server is up
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Get("/hi");
|
||||
if (is_even) {
|
||||
EXPECT_FALSE(res);
|
||||
} else {
|
||||
EXPECT_TRUE(res);
|
||||
EXPECT_EQ("hi...", res->body);
|
||||
}
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
|
||||
// Server is down...
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Get("/hi");
|
||||
EXPECT_FALSE(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
Server svr;
|
||||
|
||||
@@ -5167,8 +5224,14 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
auto listen_thread = std::thread([&svr] { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
@@ -5176,7 +5239,7 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
||||
|
||||
auto result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t offset, size_t length, DataSink &sink) {
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
@@ -5189,7 +5252,7 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
||||
// Recoonect
|
||||
result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t offset, size_t length, DataSink &sink) {
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
@@ -5199,16 +5262,13 @@ TEST(KeepAliveTest, SSLClientReconnectionPost) {
|
||||
|
||||
result = cli.Post(
|
||||
"/hi", content.size(),
|
||||
[&content](size_t offset, size_t length, DataSink &sink) {
|
||||
[&content](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
sink.write(content.c_str(), content.size());
|
||||
return true;
|
||||
},
|
||||
"text/plain");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
svr.stop();
|
||||
f.wait();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7512,3 +7572,35 @@ TEST(UniversalClientImplTest, Ipv6LiteralAddress) {
|
||||
CLIENT_PRIVATE_KEY_FILE);
|
||||
EXPECT_EQ(cli.port(), port);
|
||||
}
|
||||
|
||||
TEST(FileSystemTest, FileAndDirExistenceCheck) {
|
||||
auto file_path = "./www/dir/index.html";
|
||||
auto dir_path = "./www/dir";
|
||||
|
||||
EXPECT_TRUE(detail::is_file(file_path));
|
||||
EXPECT_FALSE(detail::is_dir(file_path));
|
||||
|
||||
EXPECT_FALSE(detail::is_file(dir_path));
|
||||
EXPECT_TRUE(detail::is_dir(dir_path));
|
||||
}
|
||||
|
||||
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/test", [&](const Request &/*req*/, Response &res) {
|
||||
EXPECT_EQ(res.status, 400);
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user