mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ecfd84c171 | |||
| b5b2a1d1c8 | |||
| 17ba303889 | |||
| 802743264c | |||
| 9dbaed75ef | |||
| c551e97297 | |||
| 4ff7a1c858 | |||
| 082acacd45 | |||
| 52163ed982 | |||
| af7a69bcf6 | |||
| 145fc8b021 | |||
| af73377611 | |||
| a3f5569196 | |||
| a636a094bf | |||
| cb85e573de | |||
| 120405beac | |||
| ceff2c1154 | |||
| 0c08c378d7 | |||
| 9a0571513e | |||
| cfb56c0b78 | |||
| 083fe43ad3 | |||
| 9e36247665 | |||
| fd03483237 | |||
| d5409ab541 | |||
| 0b875e0747 | |||
| 292f9a6c55 | |||
| 7c303bb871 | |||
| ea850cbfa7 | |||
| e6ff3d7ac2 | |||
| e1ab5a604b | |||
| 696c02f2bc | |||
| a183dba9fc | |||
| d37373a983 | |||
| b2bf172393 | |||
| 1729aa8c1f | |||
| aabd0634ae | |||
| de5a255ac6 | |||
| b6c55c6030 | |||
| 28dcf379e8 | |||
| 91e79e9a63 | |||
| 27879c4874 | |||
| 08a0452fb2 | |||
| 3a1f379e75 | |||
| fd8da4d8e4 | |||
| 365cbe37fa | |||
| 4a7aae5469 | |||
| fd324e1412 | |||
| 366eda72dc | |||
| c216dc94d2 | |||
| 61893a00a4 |
@@ -40,7 +40,7 @@ jobs:
|
||||
clang-format --version
|
||||
cd test && make style_check
|
||||
|
||||
ubuntu:
|
||||
build-error-check-on-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
@@ -53,10 +53,28 @@ jobs:
|
||||
- arch_flags: -m32
|
||||
arch_suffix: :i386
|
||||
name: (32-bit)
|
||||
- arch_flags:
|
||||
arch_suffix:
|
||||
name: (64-bit)
|
||||
name: ubuntu ${{ matrix.config.name }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install libraries
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
|
||||
libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
|
||||
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
|
||||
libzstd-dev${{ matrix.config.arch_suffix }}
|
||||
- name: build and run tests (expect failure)
|
||||
run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
|
||||
continue-on-error: true
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Proxy Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-proxy:
|
||||
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:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libssl-dev zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev netcat-openbsd
|
||||
|
||||
- name: Run proxy tests
|
||||
run: |
|
||||
cd test && make proxy
|
||||
+1
-1
@@ -42,4 +42,4 @@ ipch
|
||||
*.pyc
|
||||
.*
|
||||
!/.gitattributes
|
||||
!/.travis.yml
|
||||
!/.github
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v18.1.8 # 最新バージョンを使用
|
||||
hooks:
|
||||
- id: clang-format
|
||||
files: \.(cpp|cc|h)$
|
||||
args: [-i] # インプレースで修正
|
||||
@@ -10,6 +10,7 @@
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_REQUIRE_ZSTD (default off)
|
||||
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
|
||||
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_TEST (default off)
|
||||
@@ -49,6 +50,7 @@
|
||||
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
|
||||
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
|
||||
* HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN - a bool for if support of loading system certs from the Apple Keychain is enabled.
|
||||
* HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO - a bool for if nonblocking getaddrinfo is enabled.
|
||||
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
|
||||
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
|
||||
* HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
|
||||
@@ -104,6 +106,7 @@ option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system certs from the Apple Keychain." ON)
|
||||
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
||||
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
||||
# Defaults to static library
|
||||
@@ -117,6 +120,7 @@ endif()
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN ${HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN})
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
@@ -269,6 +273,7 @@ target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_CERTS_FROM_MACOSX_KEYCHAIN}>>:CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN>
|
||||
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
|
||||
)
|
||||
|
||||
# CMake configuration files installation directory
|
||||
|
||||
@@ -5,7 +5,7 @@ cpp-httplib
|
||||
|
||||
A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
||||
|
||||
It's extremely easy to setup. Just include the **httplib.h** file in your code!
|
||||
It's extremely easy to set up. Just include the **httplib.h** file in your code!
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want.
|
||||
@@ -85,6 +85,49 @@ cli.enable_server_hostname_verification(false);
|
||||
> [!NOTE]
|
||||
> When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
|
||||
|
||||
### SSL Error Handling
|
||||
|
||||
When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields:
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "path/to/httplib.h"
|
||||
|
||||
httplib::Client cli("https://example.com");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (!res) {
|
||||
// Check the error type
|
||||
auto err = res.error();
|
||||
|
||||
switch (err) {
|
||||
case httplib::Error::SSLConnection:
|
||||
std::cout << "SSL connection failed, SSL error: "
|
||||
<< res->ssl_error() << std::endl;
|
||||
break;
|
||||
|
||||
case httplib::Error::SSLLoadingCerts:
|
||||
std::cout << "SSL cert loading failed, OpenSSL error: "
|
||||
<< std::hex << res->ssl_openssl_error() << std::endl;
|
||||
break;
|
||||
|
||||
case httplib::Error::SSLServerVerification:
|
||||
std::cout << "SSL verification failed, X509 error: "
|
||||
<< res->ssl_openssl_error() << std::endl;
|
||||
break;
|
||||
|
||||
case httplib::Error::SSLServerHostnameVerification:
|
||||
std::cout << "SSL hostname verification failed, X509 error: "
|
||||
<< res->ssl_openssl_error() << std::endl;
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Server
|
||||
------
|
||||
|
||||
@@ -187,7 +230,7 @@ svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
|
||||
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
|
||||
```
|
||||
|
||||
The followings are built-in mappings:
|
||||
The following are built-in mappings:
|
||||
|
||||
| Extension | MIME Type | Extension | MIME Type |
|
||||
| :--------- | :-------------------------- | :--------- | :-------------------------- |
|
||||
@@ -201,7 +244,7 @@ The followings are built-in mappings:
|
||||
| bmp | image/bmp | 7z | application/x-7z-compressed |
|
||||
| gif | image/gif | atom | application/atom+xml |
|
||||
| png | image/png | pdf | application/pdf |
|
||||
| svg | image/svg+xml | mjs, js | application/javascript |
|
||||
| svg | image/svg+xml | mjs, js | text/javascript |
|
||||
| webp | image/webp | json | application/json |
|
||||
| ico | image/x-icon | rss | application/rss+xml |
|
||||
| tif | image/tiff | tar | application/x-tar |
|
||||
@@ -232,6 +275,18 @@ svr.set_logger([](const auto& req, const auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
You can also set a pre-compression logger to capture request/response data before compression is applied. This is useful for debugging and monitoring purposes when you need to see the original, uncompressed response content:
|
||||
|
||||
```cpp
|
||||
svr.set_pre_compression_logger([](const auto& req, const auto& res) {
|
||||
// Log before compression - res.body contains uncompressed content
|
||||
// Content-Encoding header is not yet set
|
||||
your_pre_compression_logger(req, res);
|
||||
});
|
||||
```
|
||||
|
||||
The pre-compression logger is only called when compression would be applied. For responses without compression, only the regular logger is called.
|
||||
|
||||
### Error handler
|
||||
|
||||
```cpp
|
||||
@@ -285,16 +340,96 @@ svr.set_post_routing_handler([](const auto& req, auto& res) {
|
||||
});
|
||||
```
|
||||
|
||||
### 'multipart/form-data' POST data
|
||||
### Pre request handler
|
||||
|
||||
```cpp
|
||||
svr.Post("/multipart", [&](const auto& req, auto& res) {
|
||||
auto size = req.files.size();
|
||||
auto ret = req.has_file("name1");
|
||||
const auto& file = req.get_file_value("name1");
|
||||
// file.filename;
|
||||
// file.content_type;
|
||||
// file.content;
|
||||
svr.set_pre_request_handler([](const auto& req, auto& res) {
|
||||
if (req.matched_route == "/user/:user") {
|
||||
auto user = req.path_params.at("user");
|
||||
if (user != "john") {
|
||||
res.status = StatusCode::Forbidden_403;
|
||||
res.set_content("error", "text/html");
|
||||
return Server::HandlerResponse::Handled;
|
||||
}
|
||||
}
|
||||
return Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
```
|
||||
|
||||
### Form data handling
|
||||
|
||||
#### URL-encoded form data ('application/x-www-form-urlencoded')
|
||||
|
||||
```cpp
|
||||
svr.Post("/form", [&](const auto& req, auto& res) {
|
||||
// URL query parameters and form-encoded data are accessible via req.params
|
||||
std::string username = req.get_param_value("username");
|
||||
std::string password = req.get_param_value("password");
|
||||
|
||||
// Handle multiple values with same name
|
||||
auto interests = req.get_param_values("interests");
|
||||
|
||||
// Check existence
|
||||
if (req.has_param("newsletter")) {
|
||||
// Handle newsletter subscription
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 'multipart/form-data' POST data
|
||||
|
||||
```cpp
|
||||
svr.Post("/multipart", [&](const Request& req, Response& res) {
|
||||
// Access text fields (from form inputs without files)
|
||||
std::string username = req.form.get_field("username");
|
||||
std::string bio = req.form.get_field("bio");
|
||||
|
||||
// Access uploaded files
|
||||
if (req.form.has_file("avatar")) {
|
||||
const auto& file = req.form.get_file("avatar");
|
||||
std::cout << "Uploaded file: " << file.filename
|
||||
<< " (" << file.content_type << ") - "
|
||||
<< file.content.size() << " bytes" << std::endl;
|
||||
|
||||
// Access additional headers if needed
|
||||
for (const auto& header : file.headers) {
|
||||
std::cout << "Header: " << header.first << " = " << header.second << std::endl;
|
||||
}
|
||||
|
||||
// Save to disk
|
||||
std::ofstream ofs(file.filename, std::ios::binary);
|
||||
ofs << file.content;
|
||||
}
|
||||
|
||||
// Handle multiple values with same name
|
||||
auto tags = req.form.get_fields("tags"); // e.g., multiple checkboxes
|
||||
for (const auto& tag : tags) {
|
||||
std::cout << "Tag: " << tag << std::endl;
|
||||
}
|
||||
|
||||
auto documents = req.form.get_files("documents"); // multiple file upload
|
||||
for (const auto& doc : documents) {
|
||||
std::cout << "Document: " << doc.filename
|
||||
<< " (" << doc.content.size() << " bytes)" << std::endl;
|
||||
}
|
||||
|
||||
// Check existence before accessing
|
||||
if (req.form.has_field("newsletter")) {
|
||||
std::cout << "Newsletter subscription: " << req.form.get_field("newsletter") << std::endl;
|
||||
}
|
||||
|
||||
// Get counts for validation
|
||||
if (req.form.get_field_count("tags") > 5) {
|
||||
res.status = StatusCode::BadRequest_400;
|
||||
res.set_content("Too many tags", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
// Summary
|
||||
std::cout << "Received " << req.form.fields.size() << " text fields and "
|
||||
<< req.form.files.size() << " files" << std::endl;
|
||||
|
||||
res.set_content("Upload successful", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
@@ -305,16 +440,29 @@ svr.Post("/content_receiver",
|
||||
[&](const Request &req, Response &res, const ContentReader &content_reader) {
|
||||
if (req.is_multipart_form_data()) {
|
||||
// NOTE: `content_reader` is blocking until every form data field is read
|
||||
MultipartFormDataItems files;
|
||||
// This approach allows streaming processing of large files
|
||||
std::vector<FormData> items;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
[&](const FormData &item) {
|
||||
items.push_back(item);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
items.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Process the received items
|
||||
for (const auto& item : items) {
|
||||
if (item.filename.empty()) {
|
||||
// Text field
|
||||
std::cout << "Field: " << item.name << " = " << item.content << std::endl;
|
||||
} else {
|
||||
// File
|
||||
std::cout << "File: " << item.name << " (" << item.filename << ") - "
|
||||
<< item.content.size() << " bytes" << std::endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::string body;
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
@@ -435,7 +583,7 @@ svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
|
||||
### Keep-Alive connection
|
||||
|
||||
```cpp
|
||||
svr.set_keep_alive_max_count(2); // Default is 5
|
||||
svr.set_keep_alive_max_count(2); // Default is 100
|
||||
svr.set_keep_alive_timeout(10); // Default is 5
|
||||
```
|
||||
|
||||
@@ -562,9 +710,11 @@ enum Error {
|
||||
SSLConnection,
|
||||
SSLLoadingCerts,
|
||||
SSLServerVerification,
|
||||
SSLServerHostnameVerification,
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
ProxyConnection,
|
||||
};
|
||||
```
|
||||
|
||||
@@ -618,7 +768,7 @@ auto res = cli.Post("/post", params);
|
||||
### POST with Multipart Form Data
|
||||
|
||||
```c++
|
||||
httplib::MultipartFormDataItems items = {
|
||||
httplib::UploadFormDataItems items = {
|
||||
{ "text1", "text default", "", "" },
|
||||
{ "text2", "aωb", "", "" },
|
||||
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
|
||||
@@ -655,8 +805,8 @@ 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
|
||||
// This method works the same as curl's `--max-time` option
|
||||
cli.set_max_timeout(5000); // 5 seconds
|
||||
```
|
||||
|
||||
### Receive content with a content receiver
|
||||
@@ -820,6 +970,26 @@ res->status; // 200
|
||||
cli.set_interface("eth0"); // Interface name, IP address or host name
|
||||
```
|
||||
|
||||
### Automatic Path Encoding
|
||||
|
||||
The client automatically encodes special characters in URL paths by default:
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
|
||||
// Automatic path encoding (default behavior)
|
||||
cli.set_path_encode(true);
|
||||
auto res = cli.Get("/path with spaces/file.txt"); // Automatically encodes spaces
|
||||
|
||||
// Disable automatic path encoding
|
||||
cli.set_path_encode(false);
|
||||
auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths
|
||||
```
|
||||
|
||||
- `set_path_encode(bool on)` - Controls automatic encoding of special characters in URL paths
|
||||
- `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters
|
||||
- `false`: Sends paths as-is without encoding (useful for pre-encoded URLs)
|
||||
|
||||
Compression
|
||||
-----------
|
||||
|
||||
@@ -830,6 +1000,7 @@ The server can apply compression to the following MIME type contents:
|
||||
* application/javascript
|
||||
* application/json
|
||||
* application/xml
|
||||
* application/protobuf
|
||||
* application/xhtml+xml
|
||||
|
||||
### Zlib Support
|
||||
@@ -841,13 +1012,18 @@ The server can apply compression to the following MIME type contents:
|
||||
Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked.
|
||||
Please see https://github.com/google/brotli for more detail.
|
||||
|
||||
### Zstd Support
|
||||
|
||||
Zstd compression is available with `CPPHTTPLIB_ZSTD_SUPPORT`. Necessary libraries should be linked.
|
||||
Please see https://github.com/facebook/zstd for more detail.
|
||||
|
||||
### Default `Accept-Encoding` value
|
||||
|
||||
The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same.
|
||||
|
||||
```c++
|
||||
res = cli.Get("/resource/foo");
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "br, gzip, deflate, zstd"}});
|
||||
```
|
||||
|
||||
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
|
||||
@@ -887,7 +1063,34 @@ httplib::Client cli("./my-socket.sock");
|
||||
cli.set_address_family(AF_UNIX);
|
||||
```
|
||||
|
||||
"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
|
||||
"my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path.
|
||||
|
||||
|
||||
URI Encoding/Decoding Utilities
|
||||
-------------------------------
|
||||
|
||||
cpp-httplib provides utility functions for URI encoding and decoding:
|
||||
|
||||
```cpp
|
||||
#include <httplib.h>
|
||||
|
||||
std::string url = "https://example.com/search?q=hello world";
|
||||
std::string encoded = httplib::encode_uri(url);
|
||||
std::string decoded = httplib::decode_uri(encoded);
|
||||
|
||||
std::string param = "hello world";
|
||||
std::string encoded_component = httplib::encode_uri_component(param);
|
||||
std::string decoded_component = httplib::decode_uri_component(encoded_component);
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
- `encode_uri(const std::string &value)` - Encodes a full URI, preserving reserved characters like `://`, `?`, `&`, `=`
|
||||
- `decode_uri(const std::string &value)` - Decodes a URI-encoded string
|
||||
- `encode_uri_component(const std::string &value)` - Encodes a URI component (query parameter, path segment), encoding all reserved characters
|
||||
- `decode_uri_component(const std::string &value)` - Decodes a URI component
|
||||
|
||||
Use `encode_uri()` for full URLs and `encode_uri_component()` for individual query parameters or path segments.
|
||||
|
||||
|
||||
Split httplib.h into .h and .cc
|
||||
|
||||
+2
-32
@@ -20,36 +20,6 @@ run : server
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
|
||||
# cpp-httplib v0.19.0
|
||||
bench-v19: server-v19
|
||||
@echo "---------------------\n cpp-httplib v0.19.0\n---------------------\n"
|
||||
@./server-v19 & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor-v19: server-v19
|
||||
@./server-v19 & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run-v19 : server-v19
|
||||
@./server-v19
|
||||
|
||||
server-v19 : cpp-httplib-v19/main.cpp cpp-httplib-v19/httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-v19/main.cpp
|
||||
|
||||
# cpp-httplib v0.18.0
|
||||
bench-v18: server-v18
|
||||
@echo "---------------------\n cpp-httplib v0.18.0\n---------------------\n"
|
||||
@./server-v18 & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor-v18: server-v18
|
||||
@./server-v18 & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run-v18 : server-v18
|
||||
@./server-v18
|
||||
|
||||
server-v18 : cpp-httplib-v18/main.cpp cpp-httplib-v18/httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib-v18/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@echo "-------------\n Crow v1.2.0\n-------------\n"
|
||||
@@ -66,9 +36,9 @@ server-crow : crow/main.cpp
|
||||
@g++ -o $@ $(CXXFLAGS) crow/main.cpp
|
||||
|
||||
# misc
|
||||
build: server server-v18 server-v19 server-crow
|
||||
build: server server-crow
|
||||
|
||||
bench-all: bench-crow bench bench-v19 bench-v18
|
||||
bench-all: bench-crow bench
|
||||
|
||||
issue:
|
||||
bombardier -c 10 -d 30s localhost:8080
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
#include "./httplib.h"
|
||||
using namespace httplib;
|
||||
|
||||
int main() {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request &, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
#include "./httplib.h"
|
||||
using namespace httplib;
|
||||
|
||||
int main() {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request &, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
rm -f httplib.h
|
||||
wget https://raw.githubusercontent.com/yhirose/cpp-httplib/v$1/httplib.h
|
||||
@@ -7,6 +7,7 @@ set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@)
|
||||
set(HTTPLIB_IS_USING_BROTLI @HTTPLIB_IS_USING_BROTLI@)
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO @HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO@)
|
||||
set(HTTPLIB_VERSION @PROJECT_VERSION@)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
+5
-2
@@ -18,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
@@ -56,9 +56,12 @@ one_time_request : one_time_request.cc ../httplib.h Makefile
|
||||
server_and_client : server_and_client.cc ../httplib.h Makefile
|
||||
$(CXX) -o server_and_client $(CXXFLAGS) server_and_client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
accept_header : accept_header.cc ../httplib.h Makefile
|
||||
$(CXX) -o accept_header $(CXXFLAGS) accept_header.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.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 one_time_request server_and_client *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client accept_header *.pem
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
using namespace httplib;
|
||||
|
||||
// Example usage of parse_accept_header function
|
||||
std::cout << "=== Accept Header Parser Example ===" << std::endl;
|
||||
|
||||
// Example 1: Simple Accept header
|
||||
std::string accept1 = "text/html,application/json,text/plain";
|
||||
std::vector<std::string> result1;
|
||||
if (detail::parse_accept_header(accept1, result1)) {
|
||||
std::cout << "\nExample 1: " << accept1 << std::endl;
|
||||
std::cout << "Parsed order:" << std::endl;
|
||||
for (size_t i = 0; i < result1.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << result1[i] << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\nExample 1: Failed to parse Accept header" << std::endl;
|
||||
}
|
||||
|
||||
// Example 2: Accept header with quality values
|
||||
std::string accept2 = "text/html;q=0.9,application/json;q=1.0,text/plain;q=0.8";
|
||||
std::vector<std::string> result2;
|
||||
if (detail::parse_accept_header(accept2, result2)) {
|
||||
std::cout << "\nExample 2: " << accept2 << std::endl;
|
||||
std::cout << "Parsed order (sorted by priority):" << std::endl;
|
||||
for (size_t i = 0; i < result2.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << result2[i] << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\nExample 2: Failed to parse Accept header" << std::endl;
|
||||
}
|
||||
|
||||
// Example 3: Browser-like Accept header
|
||||
std::string accept3 = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
|
||||
std::vector<std::string> result3;
|
||||
if (detail::parse_accept_header(accept3, result3)) {
|
||||
std::cout << "\nExample 3: " << accept3 << std::endl;
|
||||
std::cout << "Parsed order:" << std::endl;
|
||||
for (size_t i = 0; i < result3.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << result3[i] << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\nExample 3: Failed to parse Accept header" << std::endl;
|
||||
}
|
||||
|
||||
// Example 4: Invalid Accept header examples
|
||||
std::cout << "\n=== Invalid Accept Header Examples ===" << std::endl;
|
||||
|
||||
std::vector<std::string> invalid_examples = {
|
||||
"text/html;q=1.5,application/json", // q > 1.0
|
||||
"text/html;q=-0.1,application/json", // q < 0.0
|
||||
"text/html;q=invalid,application/json", // invalid q value
|
||||
"invalidtype,application/json", // invalid media type
|
||||
",application/json" // empty entry
|
||||
};
|
||||
|
||||
for (const auto& invalid_accept : invalid_examples) {
|
||||
std::vector<std::string> temp_result;
|
||||
std::cout << "\nTesting invalid: " << invalid_accept << std::endl;
|
||||
if (detail::parse_accept_header(invalid_accept, temp_result)) {
|
||||
std::cout << " Unexpectedly succeeded!" << std::endl;
|
||||
} else {
|
||||
std::cout << " Correctly rejected as invalid" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 4: Server usage example
|
||||
std::cout << "\n=== Server Usage Example ===" << std::endl;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/api/data", [](const Request& req, Response& res) {
|
||||
// Get Accept header
|
||||
auto accept_header = req.get_header_value("Accept");
|
||||
if (accept_header.empty()) {
|
||||
accept_header = "*/*"; // Default if no Accept header
|
||||
}
|
||||
|
||||
// Parse accept header to get preferred content types
|
||||
std::vector<std::string> preferred_types;
|
||||
if (!detail::parse_accept_header(accept_header, preferred_types)) {
|
||||
// Invalid Accept header
|
||||
res.status = 400; // Bad Request
|
||||
res.set_content("Invalid Accept header", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Client Accept header: " << accept_header << std::endl;
|
||||
std::cout << "Preferred types in order:" << std::endl;
|
||||
for (size_t i = 0; i < preferred_types.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << preferred_types[i] << std::endl;
|
||||
}
|
||||
|
||||
// Choose response format based on client preference
|
||||
std::string response_content;
|
||||
std::string content_type;
|
||||
|
||||
for (const auto& type : preferred_types) {
|
||||
if (type == "application/json" || type == "application/*" || type == "*/*") {
|
||||
response_content = "{\"message\": \"Hello, World!\", \"data\": [1, 2, 3]}";
|
||||
content_type = "application/json";
|
||||
break;
|
||||
} else if (type == "text/html" || type == "text/*") {
|
||||
response_content = "<html><body><h1>Hello, World!</h1><p>Data: 1, 2, 3</p></body></html>";
|
||||
content_type = "text/html";
|
||||
break;
|
||||
} else if (type == "text/plain") {
|
||||
response_content = "Hello, World!\nData: 1, 2, 3";
|
||||
content_type = "text/plain";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (response_content.empty()) {
|
||||
// No supported content type found
|
||||
res.status = 406; // Not Acceptable
|
||||
res.set_content("No acceptable content type found", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
res.set_content(response_content, content_type);
|
||||
std::cout << "Responding with: " << content_type << std::endl;
|
||||
});
|
||||
|
||||
std::cout << "Server configured. You can test it with:" << std::endl;
|
||||
std::cout << " curl -H \"Accept: application/json\" http://localhost:8080/api/data" << std::endl;
|
||||
std::cout << " curl -H \"Accept: text/html\" http://localhost:8080/api/data" << std::endl;
|
||||
std::cout << " curl -H \"Accept: text/plain\" http://localhost:8080/api/data" << std::endl;
|
||||
std::cout << " curl -H \"Accept: text/html;q=0.9,application/json;q=1.0\" http://localhost:8080/api/data" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
+17
-4
@@ -27,13 +27,26 @@ string dump_headers(const Headers &headers) {
|
||||
return s;
|
||||
}
|
||||
|
||||
string dump_multipart_files(const MultipartFormDataMap &files) {
|
||||
string dump_multipart_formdata(const MultipartFormData &form) {
|
||||
string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
s += "--------------------------------\n";
|
||||
|
||||
for (const auto &x : files) {
|
||||
for (const auto &x : form.fields) {
|
||||
const auto &name = x.first;
|
||||
const auto &field = x.second;
|
||||
|
||||
snprintf(buf, sizeof(buf), "name: %s\n", name.c_str());
|
||||
s += buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), "text length: %zu\n", field.content.size());
|
||||
s += buf;
|
||||
|
||||
s += "----------------\n";
|
||||
}
|
||||
|
||||
for (const auto &x : form.files) {
|
||||
const auto &name = x.first;
|
||||
const auto &file = x.second;
|
||||
|
||||
@@ -77,7 +90,7 @@ string log(const Request &req, const Response &res) {
|
||||
s += buf;
|
||||
|
||||
s += dump_headers(req.headers);
|
||||
s += dump_multipart_files(req.files);
|
||||
s += dump_multipart_formdata(req.form);
|
||||
|
||||
s += "--------------------------------\n";
|
||||
|
||||
@@ -101,7 +114,7 @@ int main(int argc, const char **argv) {
|
||||
#endif
|
||||
|
||||
svr.Post("/multipart", [](const Request &req, Response &res) {
|
||||
auto body = dump_headers(req.headers) + dump_multipart_files(req.files);
|
||||
auto body = dump_headers(req.headers) + dump_multipart_formdata(req.form);
|
||||
|
||||
res.set_content(body, "text/plain");
|
||||
});
|
||||
|
||||
+2
-2
@@ -37,8 +37,8 @@ int main(void) {
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const Request &req, Response &res) {
|
||||
auto image_file = req.get_file_value("image_file");
|
||||
auto text_file = req.get_file_value("text_file");
|
||||
const auto &image_file = req.form.get_file("image_file");
|
||||
const auto &text_file = req.form.get_file("text_file");
|
||||
|
||||
cout << "image file length: " << image_file.content.length() << endl
|
||||
<< "image file name: " << image_file.filename << endl
|
||||
|
||||
+20
-4
@@ -13,14 +13,15 @@ project(
|
||||
'b_lto=true',
|
||||
'warning_level=3'
|
||||
],
|
||||
meson_version: '>=0.62.0'
|
||||
meson_version: '>=0.63.0'
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
if version == 'undefined'
|
||||
cxx = meson.get_compiler('cpp')
|
||||
version = cxx.get_define('CPPHTTPLIB_VERSION',
|
||||
prefix: '#include <httplib.h>',
|
||||
include_directories: include_directories('.')).strip('"')
|
||||
@@ -65,6 +66,21 @@ if brotli_found_all
|
||||
args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
|
||||
endif
|
||||
|
||||
async_ns_opt = get_option('cpp-httplib_non_blocking_getaddrinfo')
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
async_ns_dep = cxx.find_library('ws2_32', required: async_ns_opt)
|
||||
elif host_machine.system() == 'darwin'
|
||||
async_ns_dep = dependency('appleframeworks', modules: ['CFNetwork'], required: async_ns_opt)
|
||||
else
|
||||
async_ns_dep = cxx.find_library('anl', required: async_ns_opt)
|
||||
endif
|
||||
|
||||
if async_ns_dep.found()
|
||||
deps += async_ns_dep
|
||||
args += '-DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO'
|
||||
endif
|
||||
|
||||
cpp_httplib_dep = dependency('', required: false)
|
||||
|
||||
if get_option('cpp-httplib_compile')
|
||||
@@ -87,7 +103,7 @@ if get_option('cpp-httplib_compile')
|
||||
soversion: version.split('.')[0] + '.' + version.split('.')[1],
|
||||
install: true
|
||||
)
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1], version: version)
|
||||
|
||||
import('pkgconfig').generate(
|
||||
lib,
|
||||
@@ -98,7 +114,7 @@ if get_option('cpp-httplib_compile')
|
||||
)
|
||||
else
|
||||
install_headers('httplib.h')
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.')
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.', version: version)
|
||||
|
||||
import('pkgconfig').generate(
|
||||
name: 'cpp-httplib',
|
||||
|
||||
+16
-6
@@ -2,9 +2,19 @@
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('cpp-httplib_macosx_keychain', type: 'feature', value: 'auto', description: 'Enable loading certs from the Keychain on Apple devices')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
||||
option('openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||
option('zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('macosx_keychain', type: 'feature', value: 'auto', description: 'Enable loading certs from the Keychain on Apple devices')
|
||||
option('non_blocking_getaddrinfo', type: 'feature', value: 'auto', description: 'Enable asynchronous name lookup')
|
||||
option('compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('test', type: 'boolean', value: false, description: 'Build tests')
|
||||
|
||||
# Old option names
|
||||
option('cpp-httplib_openssl', type: 'feature', deprecated: 'openssl')
|
||||
option('cpp-httplib_zlib', type: 'feature', deprecated: 'zlib')
|
||||
option('cpp-httplib_brotli', type: 'feature', deprecated: 'brotli')
|
||||
option('cpp-httplib_macosx_keychain', type: 'feature', deprecated: 'macosx_keychain')
|
||||
option('cpp-httplib_non_blocking_getaddrinfo', type: 'feature', deprecated: 'non_blocking_getaddrinfo')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, deprecated: 'compile')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, deprecated: 'test')
|
||||
|
||||
+32
-5
@@ -1,5 +1,5 @@
|
||||
CXX = clang++
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
|
||||
|
||||
PREFIX ?= $(shell brew --prefix)
|
||||
|
||||
@@ -9,7 +9,7 @@ OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPEN
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework CoreFoundation -framework Security
|
||||
OPENSSL_SUPPORT += -DCPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN -framework Security
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -21,7 +21,18 @@ BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_
|
||||
ZSTD_DIR = $(PREFIX)/opt/zstd
|
||||
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd
|
||||
|
||||
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) -pthread -lcurl
|
||||
LIBS = -lpthread -lcurl
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
LIBS += -framework CoreFoundation -framework CFNetwork
|
||||
endif
|
||||
ifneq ($(UNAME_S), Darwin)
|
||||
LIBS += -lanl
|
||||
endif
|
||||
endif
|
||||
|
||||
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
||||
|
||||
# By default, use standalone_fuzz_target_runner.
|
||||
# This runner does no fuzzing, but simply executes the inputs
|
||||
@@ -40,7 +51,23 @@ all : test test_split
|
||||
./test
|
||||
|
||||
proxy : test_proxy
|
||||
./test_proxy
|
||||
@echo "Starting proxy server..."
|
||||
cd proxy && \
|
||||
docker compose up -d
|
||||
@echo "Waiting for proxy to be ready..."
|
||||
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
||||
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
||||
@sleep 5
|
||||
@echo "Checking proxy server status..."
|
||||
@cd proxy && docker compose ps
|
||||
@echo "Checking proxy server logs..."
|
||||
@cd proxy && docker compose logs --tail=20
|
||||
@echo "Running proxy tests..."
|
||||
./test_proxy; \
|
||||
exit_code=$$?; \
|
||||
echo "Stopping proxy server..."; \
|
||||
cd proxy && docker compose down; \
|
||||
exit $$exit_code
|
||||
|
||||
test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS)
|
||||
@@ -86,7 +113,7 @@ fuzz_test: server_fuzzer
|
||||
|
||||
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
||||
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
||||
@file $@
|
||||
|
||||
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
|
||||
|
||||
@@ -20,7 +20,7 @@ all : server_fuzzer
|
||||
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
||||
server_fuzzer : server_fuzzer.cc ../../httplib.h
|
||||
# $(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
|
||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
|
||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||
zip -q -r server_fuzzer_seed_corpus.zip corpus
|
||||
|
||||
clean:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM centos:7
|
||||
FROM alpine:latest
|
||||
|
||||
ARG auth="basic"
|
||||
ARG port="3128"
|
||||
|
||||
RUN yum install -y squid
|
||||
RUN apk update && apk add --no-cache squid
|
||||
|
||||
COPY ./${auth}_squid.conf /etc/squid/squid.conf
|
||||
COPY ./${auth}_passwd /etc/squid/passwd
|
||||
|
||||
@@ -27,7 +27,7 @@ acl Safe_ports port 591 # filemaker
|
||||
acl Safe_ports port 777 # multiling http
|
||||
acl CONNECT method CONNECT
|
||||
|
||||
auth_param basic program /usr/lib64/squid/basic_ncsa_auth /etc/squid/passwd
|
||||
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
|
||||
auth_param basic realm proxy
|
||||
acl authenticated proxy_auth REQUIRED
|
||||
http_access allow authenticated
|
||||
|
||||
@@ -27,7 +27,7 @@ acl Safe_ports port 591 # filemaker
|
||||
acl Safe_ports port 777 # multiling http
|
||||
acl CONNECT method CONNECT
|
||||
|
||||
auth_param digest program /usr/lib64/squid/digest_file_auth /etc/squid/passwd
|
||||
auth_param digest program /usr/lib/squid/digest_file_auth /etc/squid/passwd
|
||||
auth_param digest realm proxy
|
||||
acl authenticated proxy_auth REQUIRED
|
||||
http_access allow authenticated
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
squid_basic:
|
||||
image: squid_basic
|
||||
|
||||
+2174
-274
File diff suppressed because it is too large
Load Diff
+19
-9
@@ -1,3 +1,4 @@
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <gtest/gtest.h>
|
||||
#include <httplib.h>
|
||||
@@ -5,6 +6,14 @@
|
||||
using namespace std;
|
||||
using namespace httplib;
|
||||
|
||||
std::string normalizeJson(const std::string &json) {
|
||||
std::string result;
|
||||
for (char c : json) {
|
||||
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { result += c; }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T> void ProxyTest(T &cli, bool basic) {
|
||||
cli.set_proxy("localhost", basic ? 3128 : 3129);
|
||||
auto res = cli.Get("/httpbin/get");
|
||||
@@ -81,7 +90,7 @@ TEST(RedirectTest, YouTubeNoSSLBasic) {
|
||||
RedirectProxyText(cli, "/", true);
|
||||
}
|
||||
|
||||
TEST(RedirectTest, DISABLED_YouTubeNoSSLDigest) {
|
||||
TEST(RedirectTest, YouTubeNoSSLDigest) {
|
||||
Client cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", false);
|
||||
}
|
||||
@@ -92,6 +101,7 @@ TEST(RedirectTest, YouTubeSSLBasic) {
|
||||
}
|
||||
|
||||
TEST(RedirectTest, YouTubeSSLDigest) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
SSLClient cli("youtube.com");
|
||||
RedirectProxyText(cli, "/", false);
|
||||
}
|
||||
@@ -113,8 +123,8 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
|
||||
auto res = cli.Get("/basic-auth/hello/world",
|
||||
{make_basic_authentication_header("hello", "world")});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
|
||||
normalizeJson(res->body));
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
@@ -122,8 +132,8 @@ template <typename T> void BaseAuthTestFromHTTPWatch(T &cli) {
|
||||
cli.set_basic_auth("hello", "world");
|
||||
auto res = cli.Get("/basic-auth/hello/world");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
|
||||
normalizeJson(res->body));
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
@@ -179,8 +189,8 @@ template <typename T> void DigestAuthTestFromHTTPWatch(T &cli) {
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
|
||||
normalizeJson(res->body));
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
@@ -249,8 +259,8 @@ template <typename T> void KeepAliveTest(T &cli, bool basic) {
|
||||
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
|
||||
res->body);
|
||||
EXPECT_EQ(normalizeJson("{\"authenticated\":true,\"user\":\"hello\"}\n"),
|
||||
normalizeJson(res->body));
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user