mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03cf43ebaa | |||
| 3c4b96024f | |||
| d74e4a7c9c | |||
| bfa2f735f2 | |||
| b6ab8435d7 | |||
| 39a64fb4e7 | |||
| d7c14b6f3a | |||
| 1880693aef | |||
| dd20342825 | |||
| a268d65c4f | |||
| b397c768e4 | |||
| 8e22a7676a | |||
| 8a7c536ad5 | |||
| 8aad481c69 | |||
| 5814e121df | |||
| 7adbccbaf7 | |||
| eb10c22db1 | |||
| 708f860e3a | |||
| eb30f15363 | |||
| 4941d5b56b | |||
| 9bbb4741b4 | |||
| 282f2feb77 | |||
| 60a1f00618 | |||
| 9104054ca5 | |||
| d69f144a99 | |||
| 929dfbd348 | |||
| 3047183fd9 | |||
| ef5e4044f1 |
@@ -0,0 +1,65 @@
|
||||
# SPDX-FileCopyrightText: 2025 Andrea Pappacoda <andrea@pappacoda.it>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: abidiff
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
|
||||
jobs:
|
||||
abi:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
container:
|
||||
image: debian:testing
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apt -y --update install --no-install-recommends
|
||||
abigail-tools
|
||||
ca-certificates
|
||||
g++
|
||||
git
|
||||
libbrotli-dev
|
||||
libssl-dev
|
||||
meson
|
||||
pkg-config
|
||||
python3
|
||||
zlib1g-dev
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: current
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: previous
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout previous
|
||||
working-directory: previous
|
||||
run: |
|
||||
git switch master
|
||||
git describe --tags --abbrev=0 master | xargs git checkout
|
||||
|
||||
- name: Build current
|
||||
working-directory: current
|
||||
run: |
|
||||
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
|
||||
ninja -C build
|
||||
|
||||
- name: Build previous
|
||||
working-directory: previous
|
||||
run: |
|
||||
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
|
||||
ninja -C build
|
||||
|
||||
- name: Run abidiff
|
||||
run: abidiff
|
||||
--headers-dir1 previous/build
|
||||
--headers-dir2 current/build
|
||||
previous/build/libcpp-httplib.so
|
||||
current/build/libcpp-httplib.so
|
||||
@@ -5,6 +5,7 @@ on: [push, pull_request]
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -17,6 +18,7 @@ jobs:
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -27,6 +29,7 @@ jobs:
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
@@ -55,7 +58,7 @@ jobs:
|
||||
run: ctest --output-on-failure --test-dir build -C Release
|
||||
|
||||
- name: Configure CMake without SSL
|
||||
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=OFF -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build without SSL
|
||||
run: cmake --build build-no-ssl --config Release
|
||||
- name: Run tests without SSL
|
||||
|
||||
@@ -23,6 +23,7 @@ test/test.xcodeproj/*/xcuser*
|
||||
test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
test/_build_*
|
||||
work/
|
||||
benchmark/server*
|
||||
|
||||
|
||||
@@ -39,10 +39,10 @@ svr.listen("0.0.0.0", 8080);
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
// HTTP
|
||||
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
|
||||
httplib::Client cli("http://yhirose.github.io");
|
||||
|
||||
// HTTPS
|
||||
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
|
||||
httplib::Client cli("https://yhirose.github.io");
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
res->status;
|
||||
@@ -654,6 +654,9 @@ res = cli.Options("/resource/foo");
|
||||
cli.set_connection_timeout(0, 300000); // 300 milliseconds
|
||||
cli.set_read_timeout(5, 0); // 5 seconds
|
||||
cli.set_write_timeout(5, 0); // 5 seconds
|
||||
|
||||
// This method works the same as curl's `--max-timeout` option
|
||||
svr.set_max_timeout(5000); // 5 seconds
|
||||
```
|
||||
|
||||
### Receive content with a content receiver
|
||||
@@ -931,9 +934,6 @@ From Docker Hub
|
||||
|
||||
```bash
|
||||
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/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 ..."
|
||||
@@ -966,12 +966,12 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
|
||||
> cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
|
||||
|
||||
> [!NOTE]
|
||||
> Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
|
||||
> Windows 8 or lower, Visual Studio 2015 or lower, and Cygwin and MSYS2 including MinGW are neither supported nor tested.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2024 Yuji Hirose)
|
||||
MIT license (© 2025 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// server_and_client.cc
|
||||
//
|
||||
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.18.5"
|
||||
#define CPPHTTPLIB_VERSION "0.19.0"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -66,6 +66,10 @@
|
||||
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
|
||||
#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
|
||||
#endif
|
||||
|
||||
#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
|
||||
#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
|
||||
#endif
|
||||
@@ -218,7 +222,9 @@ using socket_t = SOCKET;
|
||||
#include <csignal>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#ifndef __VMS
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
@@ -662,6 +668,8 @@ struct Request {
|
||||
ContentProvider content_provider_;
|
||||
bool is_chunked_content_provider_ = false;
|
||||
size_t authorization_count_ = 0;
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time_ =
|
||||
std::chrono::steady_clock::time_point::min();
|
||||
};
|
||||
|
||||
struct Response {
|
||||
@@ -735,6 +743,8 @@ public:
|
||||
virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
|
||||
virtual socket_t socket() const = 0;
|
||||
|
||||
virtual time_t duration() const = 0;
|
||||
|
||||
ssize_t write(const char *ptr);
|
||||
ssize_t write(const std::string &s);
|
||||
};
|
||||
@@ -1421,6 +1431,10 @@ public:
|
||||
template <class Rep, class Period>
|
||||
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_max_timeout(time_t msec);
|
||||
template <class Rep, class Period>
|
||||
void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_basic_auth(const std::string &username, const std::string &password);
|
||||
void set_bearer_token_auth(const std::string &token);
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -1529,6 +1543,7 @@ protected:
|
||||
time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
|
||||
time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
|
||||
time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
|
||||
time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND;
|
||||
|
||||
std::string basic_auth_username_;
|
||||
std::string basic_auth_password_;
|
||||
@@ -1583,9 +1598,6 @@ private:
|
||||
bool send_(Request &req, Response &res, Error &error);
|
||||
Result send_(Request &&req);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool is_ssl_peer_could_be_closed(SSL *ssl) const;
|
||||
#endif
|
||||
socket_t create_client_socket(Error &error) const;
|
||||
bool read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) const;
|
||||
@@ -1611,8 +1623,10 @@ private:
|
||||
|
||||
std::string adjust_host_string(const std::string &host) const;
|
||||
|
||||
virtual bool process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback);
|
||||
virtual bool
|
||||
process_socket(const Socket &socket,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
std::function<bool(Stream &strm)> callback);
|
||||
virtual bool is_ssl() const;
|
||||
};
|
||||
|
||||
@@ -1854,6 +1868,10 @@ public:
|
||||
template <class Rep, class Period>
|
||||
void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_max_timeout(time_t msec);
|
||||
template <class Rep, class Period>
|
||||
void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
|
||||
|
||||
void set_basic_auth(const std::string &username, const std::string &password);
|
||||
void set_bearer_token_auth(const std::string &token);
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -1971,12 +1989,16 @@ private:
|
||||
void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
|
||||
void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
|
||||
|
||||
bool process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) override;
|
||||
bool
|
||||
process_socket(const Socket &socket,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
std::function<bool(Stream &strm)> callback) override;
|
||||
bool is_ssl() const override;
|
||||
|
||||
bool connect_with_proxy(Socket &sock, Response &res, bool &success,
|
||||
Error &error);
|
||||
bool connect_with_proxy(
|
||||
Socket &sock,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
Response &res, bool &success, Error &error);
|
||||
bool initialize_ssl(Socket &socket, Error &error);
|
||||
|
||||
bool load_certs();
|
||||
@@ -2238,6 +2260,14 @@ inline void ClientImpl::set_write_timeout(
|
||||
duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void ClientImpl::set_max_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
auto msec =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
|
||||
set_max_timeout(msec);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void Client::set_connection_timeout(
|
||||
const std::chrono::duration<Rep, Period> &duration) {
|
||||
@@ -2256,6 +2286,12 @@ Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
cli_->set_write_timeout(duration);
|
||||
}
|
||||
|
||||
template <class Rep, class Period>
|
||||
inline void
|
||||
Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||
cli_->set_max_timeout(duration);
|
||||
}
|
||||
|
||||
/*
|
||||
* Forward declarations and types that will be part of the .h file if split into
|
||||
* .h + .cc.
|
||||
@@ -2330,10 +2366,12 @@ void split(const char *b, const char *e, char d,
|
||||
void split(const char *b, const char *e, char d, size_t m,
|
||||
std::function<void(const char *, const char *)> fn);
|
||||
|
||||
bool process_client_socket(socket_t sock, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
std::function<bool(Stream &)> callback);
|
||||
bool process_client_socket(
|
||||
socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
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,
|
||||
@@ -2381,6 +2419,7 @@ public:
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override;
|
||||
socket_t socket() const override;
|
||||
time_t duration() const override;
|
||||
|
||||
const std::string &get_buffer() const;
|
||||
|
||||
@@ -2547,7 +2586,7 @@ inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
|
||||
inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
|
||||
|
||||
inline bool is_field_content(const std::string &s) {
|
||||
if (s.empty()) { return false; }
|
||||
if (s.empty()) { return true; }
|
||||
|
||||
if (s.size() == 1) {
|
||||
return is_field_vchar(s[0]);
|
||||
@@ -3171,60 +3210,43 @@ inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
|
||||
});
|
||||
}
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
template <bool Read>
|
||||
inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
pfd_read.events = POLLIN;
|
||||
struct pollfd pfd;
|
||||
pfd.fd = sock;
|
||||
pfd.events = (Read ? POLLIN : POLLOUT);
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
return handle_EINTR([&]() { return poll(&pfd, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
fd_set fds, *rfds, *wfds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
rfds = (Read ? &fds : nullptr);
|
||||
wfds = (Read ? nullptr : &fds);
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
|
||||
return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
return select_impl<true>(sock, sec, 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;
|
||||
pfd_read.events = POLLOUT;
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); });
|
||||
#else
|
||||
#ifndef _WIN32
|
||||
if (sock >= FD_SETSIZE) { return -1; }
|
||||
#endif
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return handle_EINTR([&]() {
|
||||
return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
|
||||
});
|
||||
#endif
|
||||
return select_impl<false>(sock, sec, usec);
|
||||
}
|
||||
|
||||
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
|
||||
@@ -3298,7 +3320,10 @@ inline bool is_socket_alive(socket_t sock) {
|
||||
class SocketStream final : public Stream {
|
||||
public:
|
||||
SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec);
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec = 0,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time =
|
||||
std::chrono::steady_clock::time_point::min());
|
||||
~SocketStream() override;
|
||||
|
||||
bool is_readable() const override;
|
||||
@@ -3308,6 +3333,7 @@ public:
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override;
|
||||
socket_t socket() const override;
|
||||
time_t duration() const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -3315,6 +3341,8 @@ private:
|
||||
time_t read_timeout_usec_;
|
||||
time_t write_timeout_sec_;
|
||||
time_t write_timeout_usec_;
|
||||
time_t max_timeout_msec_;
|
||||
const std::chrono::time_point<std::chrono::steady_clock> start_time;
|
||||
|
||||
std::vector<char> read_buff_;
|
||||
size_t read_buff_off_ = 0;
|
||||
@@ -3326,9 +3354,12 @@ private:
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
class SSLSocketStream final : public Stream {
|
||||
public:
|
||||
SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec);
|
||||
SSLSocketStream(
|
||||
socket_t sock, SSL *ssl, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, time_t max_timeout_msec = 0,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time =
|
||||
std::chrono::steady_clock::time_point::min());
|
||||
~SSLSocketStream() override;
|
||||
|
||||
bool is_readable() const override;
|
||||
@@ -3338,6 +3369,7 @@ public:
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override;
|
||||
socket_t socket() const override;
|
||||
time_t duration() const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -3346,6 +3378,8 @@ private:
|
||||
time_t read_timeout_usec_;
|
||||
time_t write_timeout_sec_;
|
||||
time_t write_timeout_usec_;
|
||||
time_t max_timeout_msec_;
|
||||
const std::chrono::time_point<std::chrono::steady_clock> start_time;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -3416,13 +3450,15 @@ process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
});
|
||||
}
|
||||
|
||||
inline bool process_client_socket(socket_t sock, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec,
|
||||
time_t write_timeout_sec,
|
||||
time_t write_timeout_usec,
|
||||
std::function<bool(Stream &)> callback) {
|
||||
inline bool process_client_socket(
|
||||
socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
std::function<bool(Stream &)> callback) {
|
||||
SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec);
|
||||
write_timeout_sec, write_timeout_usec, max_timeout_msec,
|
||||
start_time);
|
||||
return callback(strm);
|
||||
}
|
||||
|
||||
@@ -4212,22 +4248,21 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
if (!key_len) { return false; }
|
||||
|
||||
auto key = std::string(beg, key_end);
|
||||
auto val = case_ignore::equal(key, "Location")
|
||||
? std::string(p, end)
|
||||
: decode_url(std::string(p, end), false);
|
||||
// auto val = (case_ignore::equal(key, "Location") ||
|
||||
// case_ignore::equal(key, "Referer"))
|
||||
// ? std::string(p, end)
|
||||
// : decode_url(std::string(p, end), false);
|
||||
auto val = std::string(p, end);
|
||||
|
||||
// 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; }
|
||||
if (!detail::fields::is_field_value(val)) { return false; }
|
||||
|
||||
if (case_ignore::equal(key, "Location") ||
|
||||
case_ignore::equal(key, "Referer")) {
|
||||
fn(key, val);
|
||||
} else {
|
||||
fn(key, decode_url(val, false));
|
||||
}
|
||||
|
||||
fn(key, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4263,7 +4298,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
|
||||
|
||||
if (!parse_header(line_reader.ptr(), end,
|
||||
[&](const std::string &key, std::string &val) {
|
||||
[&](const std::string &key, const std::string &val) {
|
||||
headers.emplace(key, val);
|
||||
})) {
|
||||
return false;
|
||||
@@ -4312,7 +4347,7 @@ inline bool read_content_without_length(Stream &strm,
|
||||
uint64_t r = 0;
|
||||
for (;;) {
|
||||
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
|
||||
if (n <= 0) { return true; }
|
||||
if (n <= 0) { return false; }
|
||||
|
||||
if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; }
|
||||
r += static_cast<uint64_t>(n);
|
||||
@@ -4456,9 +4491,9 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
ret = read_content_without_length(strm, out);
|
||||
} else {
|
||||
auto is_invalid_value = false;
|
||||
auto len = get_header_value_u64(x.headers, "Content-Length",
|
||||
std::numeric_limits<uint64_t>::max(),
|
||||
0, is_invalid_value);
|
||||
auto len = get_header_value_u64(
|
||||
x.headers, "Content-Length",
|
||||
(std::numeric_limits<uint64_t>::max)(), 0, is_invalid_value);
|
||||
|
||||
if (is_invalid_value) {
|
||||
ret = false;
|
||||
@@ -5380,10 +5415,14 @@ write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
|
||||
|
||||
inline bool expect_content(const Request &req) {
|
||||
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
|
||||
req.method == "PRI" || req.method == "DELETE") {
|
||||
req.method == "DELETE") {
|
||||
return true;
|
||||
}
|
||||
// TODO: check if Content-Length is set
|
||||
if (req.has_header("Content-Length") &&
|
||||
req.get_header_value_u64("Content-Length") > 0) {
|
||||
return true;
|
||||
}
|
||||
if (is_chunked_transfer_encoding(req.headers)) { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5428,9 +5467,76 @@ inline std::string SHA_256(const std::string &s) {
|
||||
inline std::string SHA_512(const std::string &s) {
|
||||
return message_digest(s, EVP_sha512());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
const Request &req, const std::map<std::string, std::string> &auth,
|
||||
size_t cnonce_count, const std::string &cnonce, const std::string &username,
|
||||
const std::string &password, bool is_proxy = false) {
|
||||
std::string nc;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
|
||||
nc = ss.str();
|
||||
}
|
||||
|
||||
std::string qop;
|
||||
if (auth.find("qop") != auth.end()) {
|
||||
qop = auth.at("qop");
|
||||
if (qop.find("auth-int") != std::string::npos) {
|
||||
qop = "auth-int";
|
||||
} else if (qop.find("auth") != std::string::npos) {
|
||||
qop = "auth";
|
||||
} else {
|
||||
qop.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string algo = "MD5";
|
||||
if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
|
||||
|
||||
std::string response;
|
||||
{
|
||||
auto H = algo == "SHA-256" ? detail::SHA_256
|
||||
: algo == "SHA-512" ? detail::SHA_512
|
||||
: detail::MD5;
|
||||
|
||||
auto A1 = username + ":" + auth.at("realm") + ":" + password;
|
||||
|
||||
auto A2 = req.method + ":" + req.path;
|
||||
if (qop == "auth-int") { A2 += ":" + H(req.body); }
|
||||
|
||||
if (qop.empty()) {
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
|
||||
} else {
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
|
||||
":" + qop + ":" + H(A2));
|
||||
}
|
||||
}
|
||||
|
||||
auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
|
||||
|
||||
auto field = "Digest username=\"" + username + "\", realm=\"" +
|
||||
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
|
||||
"\", uri=\"" + req.path + "\", algorithm=" + algo +
|
||||
(qop.empty() ? ", response=\""
|
||||
: ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
|
||||
cnonce + "\", response=\"") +
|
||||
response + "\"" +
|
||||
(opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
|
||||
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
return std::make_pair(key, field);
|
||||
}
|
||||
|
||||
inline bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) {
|
||||
detail::set_nonblocking(sock, true);
|
||||
auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
|
||||
|
||||
char buf[1];
|
||||
return !SSL_peek(ssl, buf, 1) &&
|
||||
SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// NOTE: This code came up with the following stackoverflow post:
|
||||
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
|
||||
@@ -5569,68 +5675,6 @@ public:
|
||||
static WSInit wsinit_;
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
const Request &req, const std::map<std::string, std::string> &auth,
|
||||
size_t cnonce_count, const std::string &cnonce, const std::string &username,
|
||||
const std::string &password, bool is_proxy = false) {
|
||||
std::string nc;
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
|
||||
nc = ss.str();
|
||||
}
|
||||
|
||||
std::string qop;
|
||||
if (auth.find("qop") != auth.end()) {
|
||||
qop = auth.at("qop");
|
||||
if (qop.find("auth-int") != std::string::npos) {
|
||||
qop = "auth-int";
|
||||
} else if (qop.find("auth") != std::string::npos) {
|
||||
qop = "auth";
|
||||
} else {
|
||||
qop.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::string algo = "MD5";
|
||||
if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
|
||||
|
||||
std::string response;
|
||||
{
|
||||
auto H = algo == "SHA-256" ? detail::SHA_256
|
||||
: algo == "SHA-512" ? detail::SHA_512
|
||||
: detail::MD5;
|
||||
|
||||
auto A1 = username + ":" + auth.at("realm") + ":" + password;
|
||||
|
||||
auto A2 = req.method + ":" + req.path;
|
||||
if (qop == "auth-int") { A2 += ":" + H(req.body); }
|
||||
|
||||
if (qop.empty()) {
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
|
||||
} else {
|
||||
response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
|
||||
":" + qop + ":" + H(A2));
|
||||
}
|
||||
}
|
||||
|
||||
auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
|
||||
|
||||
auto field = "Digest username=\"" + username + "\", realm=\"" +
|
||||
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
|
||||
"\", uri=\"" + req.path + "\", algorithm=" + algo +
|
||||
(qop.empty() ? ", response=\""
|
||||
: ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
|
||||
cnonce + "\", response=\"") +
|
||||
response + "\"" +
|
||||
(opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
|
||||
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
return std::make_pair(key, field);
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool parse_www_authenticate(const Response &res,
|
||||
std::map<std::string, std::string> &auth,
|
||||
bool is_proxy) {
|
||||
@@ -5949,20 +5993,45 @@ inline ssize_t Stream::write(const std::string &s) {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void calc_actual_timeout(time_t max_timeout_msec,
|
||||
time_t duration_msec, time_t timeout_sec,
|
||||
time_t timeout_usec, time_t &actual_timeout_sec,
|
||||
time_t &actual_timeout_usec) {
|
||||
auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
|
||||
|
||||
auto actual_timeout_msec =
|
||||
std::min(max_timeout_msec - duration_msec, timeout_msec);
|
||||
|
||||
actual_timeout_sec = actual_timeout_msec / 1000;
|
||||
actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
|
||||
}
|
||||
|
||||
// Socket stream implementation
|
||||
inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec,
|
||||
time_t write_timeout_sec,
|
||||
time_t write_timeout_usec)
|
||||
inline SocketStream::SocketStream(
|
||||
socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time)
|
||||
: sock_(sock), read_timeout_sec_(read_timeout_sec),
|
||||
read_timeout_usec_(read_timeout_usec),
|
||||
write_timeout_sec_(write_timeout_sec),
|
||||
write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {}
|
||||
write_timeout_usec_(write_timeout_usec),
|
||||
max_timeout_msec_(max_timeout_msec), start_time(start_time),
|
||||
read_buff_(read_buff_size_, 0) {}
|
||||
|
||||
inline SocketStream::~SocketStream() = default;
|
||||
|
||||
inline bool SocketStream::is_readable() const {
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
if (max_timeout_msec_ <= 0) {
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
time_t read_timeout_sec;
|
||||
time_t read_timeout_usec;
|
||||
calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
|
||||
read_timeout_usec_, read_timeout_sec, read_timeout_usec);
|
||||
|
||||
return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_writable() const {
|
||||
@@ -6039,6 +6108,12 @@ inline void SocketStream::get_local_ip_and_port(std::string &ip,
|
||||
|
||||
inline socket_t SocketStream::socket() const { return sock_; }
|
||||
|
||||
inline time_t SocketStream::duration() const {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start_time)
|
||||
.count();
|
||||
}
|
||||
|
||||
// Buffer stream implementation
|
||||
inline bool BufferStream::is_readable() const { return true; }
|
||||
|
||||
@@ -6067,6 +6142,8 @@ inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
|
||||
|
||||
inline socket_t BufferStream::socket() const { return 0; }
|
||||
|
||||
inline time_t BufferStream::duration() const { return 0; }
|
||||
|
||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
|
||||
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) {
|
||||
@@ -7157,14 +7234,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Check if the request URI doesn't exceed the limit
|
||||
if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
|
||||
Headers dummy;
|
||||
detail::read_headers(strm, dummy);
|
||||
res.status = StatusCode::UriTooLong_414;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// Request line and headers
|
||||
if (!parse_request_line(line_reader.ptr(), req) ||
|
||||
!detail::read_headers(strm, req.headers)) {
|
||||
@@ -7172,6 +7241,14 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// Check if the request URI doesn't exceed the limit
|
||||
if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
|
||||
Headers dummy;
|
||||
detail::read_headers(strm, dummy);
|
||||
res.status = StatusCode::UriTooLong_414;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
if (req.get_header_value("Connection") == "close") {
|
||||
connection_closed = true;
|
||||
}
|
||||
@@ -7363,6 +7440,7 @@ inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
|
||||
read_timeout_usec_ = rhs.read_timeout_usec_;
|
||||
write_timeout_sec_ = rhs.write_timeout_sec_;
|
||||
write_timeout_usec_ = rhs.write_timeout_usec_;
|
||||
max_timeout_msec_ = rhs.max_timeout_msec_;
|
||||
basic_auth_username_ = rhs.basic_auth_username_;
|
||||
basic_auth_password_ = rhs.basic_auth_password_;
|
||||
bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
|
||||
@@ -7509,18 +7587,6 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const {
|
||||
detail::set_nonblocking(socket_.sock, true);
|
||||
auto se = detail::scope_exit(
|
||||
[&]() { detail::set_nonblocking(socket_.sock, false); });
|
||||
|
||||
char buf[1];
|
||||
return !SSL_peek(ssl, buf, 1) &&
|
||||
SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
@@ -7535,7 +7601,9 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_alive && is_ssl()) {
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; }
|
||||
if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
|
||||
is_alive = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7560,7 +7628,8 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
auto &scli = static_cast<SSLClient &>(*this);
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
auto success = false;
|
||||
if (!scli.connect_with_proxy(socket_, res, success, error)) {
|
||||
if (!scli.connect_with_proxy(socket_, req.start_time_, res, success,
|
||||
error)) {
|
||||
return success;
|
||||
}
|
||||
}
|
||||
@@ -7606,7 +7675,7 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
}
|
||||
});
|
||||
|
||||
ret = process_socket(socket_, [&](Stream &strm) {
|
||||
ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
|
||||
return handle_request(strm, req, res, close_connection, error);
|
||||
});
|
||||
|
||||
@@ -8015,6 +8084,9 @@ inline Result ClientImpl::send_with_content_provider(
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
req.progress = progress;
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
auto error = Error::Success;
|
||||
|
||||
@@ -8041,7 +8113,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
if (is_ssl()) {
|
||||
auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
|
||||
if (!is_proxy_enabled) {
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) {
|
||||
if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
|
||||
error = Error::SSLPeerCouldBeClosed_;
|
||||
return false;
|
||||
}
|
||||
@@ -8166,12 +8238,14 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
|
||||
};
|
||||
}
|
||||
|
||||
inline bool
|
||||
ClientImpl::process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) {
|
||||
inline bool ClientImpl::process_socket(
|
||||
const Socket &socket,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
std::function<bool(Stream &strm)> callback) {
|
||||
return detail::process_client_socket(
|
||||
socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
|
||||
write_timeout_usec_, std::move(callback));
|
||||
write_timeout_usec_, max_timeout_msec_, start_time,
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
inline bool ClientImpl::is_ssl() const { return false; }
|
||||
@@ -8195,6 +8269,9 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
|
||||
req.path = path;
|
||||
req.headers = headers;
|
||||
req.progress = std::move(progress);
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
return send_(std::move(req));
|
||||
}
|
||||
@@ -8260,6 +8337,9 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
|
||||
return content_receiver(data, data_length);
|
||||
};
|
||||
req.progress = std::move(progress);
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
return send_(std::move(req));
|
||||
}
|
||||
@@ -8305,6 +8385,9 @@ inline Result ClientImpl::Head(const std::string &path,
|
||||
req.method = "HEAD";
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
return send_(std::move(req));
|
||||
}
|
||||
@@ -8722,6 +8805,9 @@ inline Result ClientImpl::Delete(const std::string &path,
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
req.progress = progress;
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
|
||||
req.body.assign(body, content_length);
|
||||
@@ -8769,6 +8855,9 @@ inline Result ClientImpl::Options(const std::string &path,
|
||||
req.method = "OPTIONS";
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
return send_(std::move(req));
|
||||
}
|
||||
@@ -8822,6 +8911,10 @@ inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
|
||||
write_timeout_usec_ = usec;
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_max_timeout(time_t msec) {
|
||||
max_timeout_msec_ = msec;
|
||||
}
|
||||
|
||||
inline void ClientImpl::set_basic_auth(const std::string &username,
|
||||
const std::string &password) {
|
||||
basic_auth_username_ = username;
|
||||
@@ -9060,12 +9153,14 @@ inline bool process_server_socket_ssl(
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool
|
||||
process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec,
|
||||
time_t read_timeout_usec, time_t write_timeout_sec,
|
||||
time_t write_timeout_usec, T callback) {
|
||||
inline bool process_client_socket_ssl(
|
||||
SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
|
||||
write_timeout_sec, write_timeout_usec);
|
||||
write_timeout_sec, write_timeout_usec,
|
||||
max_timeout_msec, start_time);
|
||||
return callback(strm);
|
||||
}
|
||||
|
||||
@@ -9078,27 +9173,37 @@ public:
|
||||
};
|
||||
|
||||
// SSL socket stream implementation
|
||||
inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl,
|
||||
time_t read_timeout_sec,
|
||||
time_t read_timeout_usec,
|
||||
time_t write_timeout_sec,
|
||||
time_t write_timeout_usec)
|
||||
inline SSLSocketStream::SSLSocketStream(
|
||||
socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec,
|
||||
time_t write_timeout_sec, time_t write_timeout_usec,
|
||||
time_t max_timeout_msec,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time)
|
||||
: sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
|
||||
read_timeout_usec_(read_timeout_usec),
|
||||
write_timeout_sec_(write_timeout_sec),
|
||||
write_timeout_usec_(write_timeout_usec) {
|
||||
write_timeout_usec_(write_timeout_usec),
|
||||
max_timeout_msec_(max_timeout_msec), start_time(start_time) {
|
||||
SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
|
||||
}
|
||||
|
||||
inline SSLSocketStream::~SSLSocketStream() = default;
|
||||
|
||||
inline bool SSLSocketStream::is_readable() const {
|
||||
return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
if (max_timeout_msec_ <= 0) {
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
time_t read_timeout_sec;
|
||||
time_t read_timeout_usec;
|
||||
calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
|
||||
read_timeout_usec_, read_timeout_sec, read_timeout_usec);
|
||||
|
||||
return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
|
||||
}
|
||||
|
||||
inline bool SSLSocketStream::is_writable() const {
|
||||
return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
|
||||
is_socket_alive(sock_);
|
||||
is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_);
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
@@ -9129,8 +9234,9 @@ inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
@@ -9176,6 +9282,12 @@ inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
|
||||
|
||||
inline socket_t SSLSocketStream::socket() const { return sock_; }
|
||||
|
||||
inline time_t SSLSocketStream::duration() const {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start_time)
|
||||
.count();
|
||||
}
|
||||
|
||||
static SSLInit sslinit_;
|
||||
|
||||
} // namespace detail
|
||||
@@ -9416,16 +9528,22 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
|
||||
}
|
||||
|
||||
// Assumes that socket_mutex_ is locked and that there are no requests in flight
|
||||
inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
bool &success, Error &error) {
|
||||
inline bool SSLClient::connect_with_proxy(
|
||||
Socket &socket,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
Response &res, bool &success, Error &error) {
|
||||
success = true;
|
||||
Response proxy_res;
|
||||
if (!detail::process_client_socket(
|
||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||
write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
|
||||
start_time, [&](Stream &strm) {
|
||||
Request req2;
|
||||
req2.method = "CONNECT";
|
||||
req2.path = host_and_port_;
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req2.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
return process_request(strm, req2, proxy_res, false, error);
|
||||
})) {
|
||||
// Thread-safe to close everything because we are assuming there are no
|
||||
@@ -9445,7 +9563,8 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
proxy_res = Response();
|
||||
if (!detail::process_client_socket(
|
||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||
write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
|
||||
start_time, [&](Stream &strm) {
|
||||
Request req3;
|
||||
req3.method = "CONNECT";
|
||||
req3.path = host_and_port_;
|
||||
@@ -9453,6 +9572,9 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||
req3, auth, 1, detail::random_string(10),
|
||||
proxy_digest_auth_username_, proxy_digest_auth_password_,
|
||||
true));
|
||||
if (max_timeout_msec_ > 0) {
|
||||
req3.start_time_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
return process_request(strm, req3, proxy_res, false, error);
|
||||
})) {
|
||||
// Thread-safe to close everything because we are assuming there are
|
||||
@@ -9608,13 +9730,15 @@ inline void SSLClient::shutdown_ssl_impl(Socket &socket,
|
||||
assert(socket.ssl == nullptr);
|
||||
}
|
||||
|
||||
inline bool
|
||||
SSLClient::process_socket(const Socket &socket,
|
||||
std::function<bool(Stream &strm)> callback) {
|
||||
inline bool SSLClient::process_socket(
|
||||
const Socket &socket,
|
||||
std::chrono::time_point<std::chrono::steady_clock> start_time,
|
||||
std::function<bool(Stream &strm)> callback) {
|
||||
assert(socket.ssl);
|
||||
return detail::process_client_socket_ssl(
|
||||
socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||
write_timeout_sec_, write_timeout_usec_, std::move(callback));
|
||||
write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
inline bool SSLClient::is_ssl() const { return true; }
|
||||
|
||||
+4
-1
@@ -42,6 +42,9 @@ test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
||||
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
||||
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
|
||||
|
||||
check_abi:
|
||||
@./check-shared-library-abi-compatibility.sh
|
||||
|
||||
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
|
||||
|
||||
@@ -66,5 +69,5 @@ cert.pem:
|
||||
./gen-certs.sh
|
||||
|
||||
clean:
|
||||
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
|
||||
rm -rf test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build*
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
|
||||
socket_t socket() const override { return 0; }
|
||||
|
||||
time_t duration() const override { return 0; };
|
||||
|
||||
private:
|
||||
const uint8_t *data_;
|
||||
size_t size_;
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 build_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BUILD_DIR=$1
|
||||
|
||||
# Make the build directory
|
||||
rm -rf $BUILD_DIR
|
||||
mkdir -p $BUILD_DIR/out
|
||||
|
||||
cd $BUILD_DIR
|
||||
|
||||
# Build the version
|
||||
git checkout $BUILD_DIR -q
|
||||
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_CXX_FLAGS="-g -Og" \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DHTTPLIB_COMPILE=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=./out \
|
||||
../..
|
||||
|
||||
cmake --build . --target install
|
||||
cmake --build . --target clean
|
||||
|
||||
+322
-8
@@ -162,7 +162,8 @@ TEST(SocketStream, is_writable_UNIX) {
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
return detail::process_client_socket(
|
||||
fd, 0, 0, 0, 0, 0, std::chrono::steady_clock::time_point::min(), func);
|
||||
};
|
||||
asSocketStream(fds[0], [&](Stream &s0) {
|
||||
EXPECT_EQ(s0.socket(), fds[0]);
|
||||
@@ -206,7 +207,8 @@ TEST(SocketStream, is_writable_INET) {
|
||||
|
||||
const auto asSocketStream = [&](socket_t fd,
|
||||
std::function<bool(Stream &)> func) {
|
||||
return detail::process_client_socket(fd, 0, 0, 0, 0, func);
|
||||
return detail::process_client_socket(
|
||||
fd, 0, 0, 0, 0, 0, std::chrono::steady_clock::time_point::min(), func);
|
||||
};
|
||||
asSocketStream(disconnected_svr_sock, [&](Stream &ss) {
|
||||
EXPECT_EQ(ss.socket(), disconnected_svr_sock);
|
||||
@@ -1863,6 +1865,32 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PathUrlEncodeTest, IncludePercentEncodingLF) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request &req, Response &) {
|
||||
EXPECT_EQ("\x0A", req.get_param_value("something"));
|
||||
});
|
||||
|
||||
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.set_url_encode(false);
|
||||
|
||||
auto res = cli.Get("/?something=%0A");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BindServerTest, DISABLED_BindDualStack) {
|
||||
Server svr;
|
||||
|
||||
@@ -2951,6 +2979,11 @@ protected:
|
||||
res.status = 401;
|
||||
res.set_header("WWW-Authenticate", "Basic realm=123456");
|
||||
})
|
||||
.Delete("/issue609",
|
||||
[](const httplib::Request &, httplib::Response &res,
|
||||
const httplib::ContentReader &) {
|
||||
res.set_content("ok", "text/plain");
|
||||
})
|
||||
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
|
||||
.Get("/compress",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
@@ -3508,7 +3541,7 @@ TEST_F(ServerTest, LongRequest) {
|
||||
|
||||
TEST_F(ServerTest, TooLongRequest) {
|
||||
std::string request;
|
||||
for (size_t i = 0; i < 545; i++) {
|
||||
for (size_t i = 0; i < 546; i++) {
|
||||
request += "/TooLongRequest";
|
||||
}
|
||||
request += "_NG";
|
||||
@@ -3519,6 +3552,17 @@ TEST_F(ServerTest, TooLongRequest) {
|
||||
EXPECT_EQ(StatusCode::UriTooLong_414, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, AlmostTooLongRequest) {
|
||||
// test for #2046 - URI length check shouldn't include other content on req line
|
||||
// URI is max URI length, minus 14 other chars in req line (GET, space, leading /, space, HTTP/1.1)
|
||||
std::string request = "/" + string(CPPHTTPLIB_REQUEST_URI_MAX_LENGTH - 14, 'A');
|
||||
|
||||
auto res = cli_.Get(request.c_str());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, LongHeader) {
|
||||
Request req;
|
||||
req.method = "GET";
|
||||
@@ -4022,6 +4066,13 @@ TEST_F(ServerTest, Issue1772) {
|
||||
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, Issue609) {
|
||||
auto res = cli_.Delete("/issue609");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(std::string("ok"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedChunked) {
|
||||
auto res = cli_.Get("/streamed-chunked");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -4866,7 +4917,8 @@ static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
if (client_sock == INVALID_SOCKET) { return false; }
|
||||
|
||||
auto ret = detail::process_client_socket(
|
||||
client_sock, read_timeout_sec, 0, 0, 0, [&](Stream &strm) {
|
||||
client_sock, read_timeout_sec, 0, 0, 0, 0,
|
||||
std::chrono::steady_clock::time_point::min(), [&](Stream &strm) {
|
||||
if (req.size() !=
|
||||
static_cast<size_t>(strm.write(req.data(), req.size()))) {
|
||||
return false;
|
||||
@@ -4910,8 +4962,10 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
"Connection: close\r\n"
|
||||
"\r\n";
|
||||
|
||||
ASSERT_TRUE(send_request(5, req));
|
||||
EXPECT_EQ(header_value, "\v bar \x1B");
|
||||
std::string res;
|
||||
ASSERT_TRUE(send_request(5, req, &res));
|
||||
EXPECT_EQ(header_value, "");
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", res.substr(0, 24));
|
||||
}
|
||||
|
||||
// Sends a raw request and verifies that there isn't a crash or exception.
|
||||
@@ -5069,6 +5123,14 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidFieldValueContains_LF) {
|
||||
std::string out;
|
||||
std::string request(
|
||||
"GET /header_field_value_check HTTP/1.1\r\nTest: [\n\n\n]\r\n\r\n", 55);
|
||||
test_raw_request(request, &out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, EmptyFieldValue) {
|
||||
std::string out;
|
||||
|
||||
@@ -6153,7 +6215,7 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, Issue2004) {
|
||||
TEST(SSLClientTest, Issue2004_Online) {
|
||||
Client client("https://google.com");
|
||||
client.set_follow_location(true);
|
||||
|
||||
@@ -7958,7 +8020,7 @@ TEST(InvalidHeaderCharsTest, is_field_value) {
|
||||
EXPECT_FALSE(detail::fields::is_field_value(" example_token"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value("example_token "));
|
||||
EXPECT_TRUE(detail::fields::is_field_value("token@123"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value(""));
|
||||
EXPECT_TRUE(detail::fields::is_field_value(""));
|
||||
EXPECT_FALSE(detail::fields::is_field_value("example\rtoken"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value("example\ntoken"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1)));
|
||||
@@ -8142,3 +8204,255 @@ TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(MaxTimeoutTest, ContentStream) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/stream", [&](const Request &, Response &res) {
|
||||
auto data = new std::string("01234567890123456789");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), "text/plain",
|
||||
[&, data](size_t offset, size_t length, DataSink &sink) {
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true;
|
||||
},
|
||||
[data](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete data;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/stream_without_length", [&](const Request &, Response &res) {
|
||||
auto i = new size_t(0);
|
||||
|
||||
res.set_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t, DataSink &sink) {
|
||||
if (*i < 5) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.write("abcd", 4);
|
||||
(*i)++;
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete i;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/chunked", [&](const Request &, Response &res) {
|
||||
auto i = new size_t(0);
|
||||
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t, DataSink &sink) {
|
||||
if (*i < 5) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.os << "abcd";
|
||||
(*i)++;
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete i;
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
const time_t timeout = 2000;
|
||||
const time_t threshold = 200;
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
cli.set_max_timeout(std::chrono::milliseconds(timeout));
|
||||
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/stream");
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/stream_without_length");
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/chunked", [&](const char *data, size_t data_length) {
|
||||
EXPECT_EQ("abcd", string(data, data_length));
|
||||
return true;
|
||||
});
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(MaxTimeoutTest, ContentStreamSSL) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Get("/stream", [&](const Request &, Response &res) {
|
||||
auto data = new std::string("01234567890123456789");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), "text/plain",
|
||||
[&, data](size_t offset, size_t length, DataSink &sink) {
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
return true;
|
||||
},
|
||||
[data](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete data;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/stream_without_length", [&](const Request &, Response &res) {
|
||||
auto i = new size_t(0);
|
||||
|
||||
res.set_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t, DataSink &sink) {
|
||||
if (*i < 5) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.write("abcd", 4);
|
||||
(*i)++;
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete i;
|
||||
});
|
||||
});
|
||||
|
||||
svr.Get("/chunked", [&](const Request &, Response &res) {
|
||||
auto i = new size_t(0);
|
||||
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t, DataSink &sink) {
|
||||
if (*i < 5) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
sink.os << "abcd";
|
||||
(*i)++;
|
||||
} else {
|
||||
sink.done();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_FALSE(success);
|
||||
delete i;
|
||||
});
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
const time_t timeout = 2000;
|
||||
const time_t threshold = 1000; // SSL_shutdown is slow...
|
||||
|
||||
SSLClient cli("localhost", PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_max_timeout(std::chrono::milliseconds(timeout));
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/stream");
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/stream_without_length");
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
auto res = cli.Get("/chunked", [&](const char *data, size_t data_length) {
|
||||
EXPECT_EQ("abcd", string(data, data_length));
|
||||
return true;
|
||||
});
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - start)
|
||||
.count();
|
||||
|
||||
ASSERT_FALSE(res);
|
||||
EXPECT_EQ(Error::Read, res.error());
|
||||
EXPECT_TRUE(timeout <= elapsed && elapsed < timeout + threshold);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user