├── .codecov.yml ├── .drone.star ├── .drone ├── drone.bat └── drone.sh ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.adoc ├── build └── Jamfile ├── cmake ├── config.cmake.in └── toolchains │ ├── clang.cmake │ ├── common.cmake │ ├── gcc.cmake │ └── msvc.cmake ├── doc ├── .gitignore ├── antora.yml ├── build_antora.bat ├── build_antora.sh ├── local-playbook.yml ├── modules │ └── ROOT │ │ ├── nav.adoc │ │ └── pages │ │ └── index.adoc ├── mrdocs.yml ├── package-lock.json └── package.json ├── example ├── CMakeLists.txt ├── Jamfile ├── client │ ├── CMakeLists.txt │ ├── Jamfile │ ├── burl │ │ ├── CMakeLists.txt │ │ ├── Jamfile │ │ ├── any_iostream.cpp │ │ ├── any_iostream.hpp │ │ ├── any_stream.hpp │ │ ├── base64.cpp │ │ ├── base64.hpp │ │ ├── connect.cpp │ │ ├── connect.hpp │ │ ├── cookie.cpp │ │ ├── cookie.hpp │ │ ├── error.cpp │ │ ├── error.hpp │ │ ├── glob.cpp │ │ ├── glob.hpp │ │ ├── main.cpp │ │ ├── message.cpp │ │ ├── message.hpp │ │ ├── mime_type.cpp │ │ ├── mime_type.hpp │ │ ├── multipart_form.cpp │ │ ├── multipart_form.hpp │ │ ├── options.cpp │ │ ├── options.hpp │ │ ├── progress_meter.cpp │ │ ├── progress_meter.hpp │ │ ├── request.hpp │ │ ├── task_group.hpp │ │ ├── utils.cpp │ │ └── utils.hpp │ └── visit │ │ ├── CMakeLists.txt │ │ ├── Jamfile │ │ └── main.cpp └── server │ ├── CMakeLists.txt │ ├── Jamfile │ ├── acceptor.hpp │ ├── fixed_array.hpp │ ├── main.cpp │ ├── server.cpp │ └── server.hpp ├── include └── boost │ ├── http_io.hpp │ └── http_io │ ├── buffer.hpp │ ├── client.hpp │ ├── detail │ ├── config.hpp │ └── except.hpp │ ├── impl │ ├── read.hpp │ └── write.hpp │ ├── read.hpp │ └── write.hpp ├── index.html ├── meta ├── explicit-failures-markup.xml └── libraries.json ├── src └── detail │ └── except.cpp └── test ├── CMakeLists.txt ├── Jamfile ├── cmake_test ├── CMakeLists.txt └── main.cpp └── unit ├── CMakeLists.txt ├── Jamfile ├── beast.cpp ├── buffer.cpp ├── client.cpp ├── read.cpp ├── sandbox.cpp └── write.cpp /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 - 2021 Alexander Grund 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at http://boost.org/LICENSE_1_0.txt) 4 | # 5 | # Sample codecov configuration file. Edit as required 6 | 7 | codecov: 8 | max_report_age: off 9 | require_ci_to_pass: yes 10 | notify: 11 | # Increase this if you have multiple coverage collection jobs 12 | after_n_builds: 1 13 | wait_for_ci: yes 14 | 15 | # Change how pull request comments look 16 | comment: 17 | layout: "reach,diff,flags,files,footer" 18 | 19 | # Ignore specific files or folders. Glob patterns are supported. 20 | # See https://docs.codecov.com/docs/ignoring-paths 21 | ignore: 22 | - extra/* 23 | - extra/**/* 24 | - test/* 25 | - test/**/* 26 | -------------------------------------------------------------------------------- /.drone.star: -------------------------------------------------------------------------------- 1 | # Use, modification, and distribution are 2 | # subject to the Boost Software License, Version 1.0. (See accompanying 3 | # file LICENSE.txt) 4 | # 5 | # Copyright Rene Rivera 2020. 6 | # Copyright Alan de Freitas 2022. 7 | 8 | # For Drone CI we use the Starlark scripting language to reduce duplication. 9 | # As the yaml syntax for Drone CI is rather limited. 10 | # 11 | # 12 | 13 | def main(ctx): 14 | return generate( 15 | # Compilers 16 | [ 17 | 'gcc >=5.0', 18 | 'clang >=3.8', 19 | 'msvc >=14.1', 20 | 'arm64-gcc latest', 21 | 's390x-gcc latest', 22 | # 'freebsd-gcc latest', 23 | 'apple-clang *', 24 | 'arm64-clang latest', 25 | 's390x-clang latest', 26 | # 'x86-msvc latest' 27 | ], 28 | # Standards 29 | '>=11', 30 | packages=['zlib1g', 'zlib1g-dev']) 31 | 32 | # from https://github.com/cppalliance/ci-automation 33 | load("@ci_automation//ci/drone/:functions.star", "linux_cxx", "windows_cxx", "osx_cxx", "freebsd_cxx", "generate") 34 | -------------------------------------------------------------------------------- /.drone/drone.bat: -------------------------------------------------------------------------------- 1 | 2 | @ECHO ON 3 | setlocal enabledelayedexpansion 4 | 5 | set TRAVIS_OS_NAME=windows 6 | 7 | IF "!DRONE_BRANCH!" == "" ( 8 | for /F %%i in ("!GITHUB_REF!") do @set TRAVIS_BRANCH=%%~nxi 9 | ) else ( 10 | SET TRAVIS_BRANCH=!DRONE_BRANCH! 11 | ) 12 | 13 | if "%DRONE_JOB_BUILDTYPE%" == "boost" ( 14 | 15 | echo "Running boost job" 16 | echo '==================================> INSTALL' 17 | REM there seems to be some problem with b2 bootstrap on Windows 18 | REM when CXX env variable is set 19 | SET "CXX=" 20 | 21 | git clone https://github.com/boostorg/boost-ci.git boost-ci-cloned --depth 1 22 | cp -prf boost-ci-cloned/ci . 23 | rm -rf boost-ci-cloned 24 | REM source ci/travis/install.sh 25 | REM The contents of install.sh below: 26 | 27 | for /F %%i in ("%DRONE_REPO%") do @set SELF=%%~nxi 28 | SET BOOST_CI_TARGET_BRANCH=!TRAVIS_BRANCH! 29 | SET BOOST_CI_SRC_FOLDER=%cd% 30 | if "%BOOST_BRANCH%" == "" ( 31 | SET BOOST_BRANCH=develop 32 | if "%BOOST_CI_TARGET_BRANCH%" == "master" set BOOST_BRANCH=master 33 | ) 34 | 35 | call ci\common_install.bat 36 | 37 | echo '==================================> ZLIB' 38 | git clone --branch v1.2.13 https://github.com/madler/zlib.git !BOOST_ROOT!\zlib-src --depth 1 39 | set ZLIB_SOURCE=!BOOST_ROOT!\zlib-src 40 | 41 | REM Customizations 42 | cd 43 | pushd !BOOST_ROOT!\libs 44 | git clone https://github.com/CPPAlliance/buffers -b !BOOST_BRANCH! --depth 1 45 | popd 46 | pushd !BOOST_ROOT!\libs 47 | git clone https://github.com/CPPAlliance/http_proto -b !BOOST_BRANCH! --depth 1 48 | popd 49 | 50 | echo '==================================> COMPILE' 51 | 52 | REM set B2_TARGETS=libs/!SELF!/test libs/!SELF!/example 53 | set B2_TARGETS=libs/!SELF!/test 54 | call !BOOST_ROOT!\libs\!SELF!\ci\build.bat 55 | 56 | ) 57 | -------------------------------------------------------------------------------- /.drone/drone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2020 Rene Rivera, Sam Darwin 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE.txt or copy at http://boost.org/LICENSE_1_0.txt) 6 | 7 | set -xe 8 | 9 | export DRONE_BUILD_DIR=$(pwd) 10 | export VCS_COMMIT_ID=$DRONE_COMMIT 11 | export GIT_COMMIT=$DRONE_COMMIT 12 | export REPO_NAME=$DRONE_REPO 13 | export USER=$(whoami) 14 | export CC=${CC:-gcc} 15 | export PATH=~/.local/bin:/usr/local/bin:$PATH 16 | export TRAVIS_BUILD_DIR=$(pwd) 17 | export TRAVIS_BRANCH=$DRONE_BRANCH 18 | export TRAVIS_EVENT_TYPE=$DRONE_BUILD_EVENT 19 | 20 | common_install () { 21 | if [ -z "$SELF" ]; then 22 | export SELF=`basename $REPO_NAME` 23 | fi 24 | 25 | git clone https://github.com/boostorg/boost-ci.git boost-ci-cloned --depth 1 26 | [ "$SELF" == "boost-ci" ] || cp -prf boost-ci-cloned/ci . 27 | rm -rf boost-ci-cloned 28 | 29 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then 30 | unset -f cd 31 | fi 32 | 33 | export BOOST_CI_TARGET_BRANCH="$TRAVIS_BRANCH" 34 | export BOOST_CI_SRC_FOLDER=$(pwd) 35 | 36 | . ./ci/common_install.sh 37 | 38 | if [ ! -d "$BOOST_ROOT/libs/buffers" ]; then 39 | pushd $BOOST_ROOT/libs 40 | git clone https://github.com/CPPAlliance/buffers -b $BOOST_BRANCH --depth 1 41 | popd 42 | fi 43 | 44 | if [ ! -d "$BOOST_ROOT/libs/http_proto" ]; then 45 | pushd $BOOST_ROOT/libs 46 | git clone https://github.com/CPPAlliance/http_proto -b $BOOST_BRANCH --depth 1 47 | popd 48 | fi 49 | } 50 | 51 | if [[ $(uname) == "Linux" && "$B2_ASAN" == "1" ]]; then 52 | echo 0 | sudo tee /proc/sys/kernel/randomize_va_space > /dev/null 53 | fi 54 | 55 | if [ "$DRONE_JOB_BUILDTYPE" == "boost" ]; then 56 | 57 | echo '==================================> INSTALL' 58 | 59 | common_install 60 | 61 | echo '==================================> SCRIPT' 62 | 63 | . $BOOST_ROOT/libs/$SELF/ci/build.sh 64 | 65 | elif [ "$DRONE_JOB_BUILDTYPE" == "codecov" ]; then 66 | 67 | echo '==================================> INSTALL' 68 | 69 | common_install 70 | 71 | echo '==================================> SCRIPT' 72 | 73 | cd $BOOST_ROOT/libs/$SELF 74 | ci/travis/codecov.sh 75 | 76 | elif [ "$DRONE_JOB_BUILDTYPE" == "valgrind" ]; then 77 | 78 | echo '==================================> INSTALL' 79 | 80 | common_install 81 | 82 | echo '==================================> SCRIPT' 83 | 84 | cd $BOOST_ROOT/libs/$SELF 85 | ci/travis/valgrind.sh 86 | 87 | elif [ "$DRONE_JOB_BUILDTYPE" == "coverity" ]; then 88 | 89 | echo '==================================> INSTALL' 90 | 91 | common_install 92 | 93 | echo '==================================> SCRIPT' 94 | 95 | if [ -n "${COVERITY_SCAN_NOTIFICATION_EMAIL}" -a \( "$TRAVIS_BRANCH" = "develop" -o "$TRAVIS_BRANCH" = "master" \) -a \( "$DRONE_BUILD_EVENT" = "push" -o "$DRONE_BUILD_EVENT" = "cron" \) ] ; then 96 | cd $BOOST_ROOT/libs/$SELF 97 | ci/travis/coverity.sh 98 | fi 99 | 100 | elif [ "$DRONE_JOB_BUILDTYPE" == "cmake1" ]; then 101 | 102 | set -xe 103 | 104 | echo '==================================> INSTALL' 105 | 106 | # already in the image 107 | # pip install --user cmake 108 | 109 | echo '==================================> SCRIPT' 110 | 111 | export SELF=`basename $REPO_NAME` 112 | BOOST_BRANCH=develop && [ "$DRONE_BRANCH" == "master" ] && BOOST_BRANCH=master || true 113 | echo BOOST_BRANCH: $BOOST_BRANCH 114 | cd .. 115 | git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root 116 | cd boost-root 117 | 118 | mkdir -p libs/$SELF 119 | cp -r $DRONE_BUILD_DIR/* libs/$SELF 120 | # git submodule update --init tools/boostdep 121 | git submodule update --init --recursive 122 | 123 | # Customizations 124 | if [ ! -d "$BOOST_ROOT/libs/buffers" ]; then 125 | pushd $BOOST_ROOT/libs 126 | git clone https://github.com/CPPAlliance/buffers -b $BOOST_BRANCH --depth 1 127 | popd 128 | fi 129 | 130 | if [ ! -d "$BOOST_ROOT/libs/http_proto" ]; then 131 | pushd $BOOST_ROOT/libs 132 | git clone https://github.com/CPPAlliance/http_proto -b $BOOST_BRANCH --depth 1 133 | popd 134 | fi 135 | 136 | cd libs/$SELF 137 | mkdir __build__ && cd __build__ 138 | cmake -DCMAKE_INSTALL_PREFIX=~/.local .. 139 | cmake --build . --target install 140 | 141 | elif [ "$DRONE_JOB_BUILDTYPE" == "cmake-superproject" ]; then 142 | 143 | echo '==================================> INSTALL' 144 | 145 | common_install 146 | 147 | echo '==================================> COMPILE' 148 | 149 | # Warnings as errors -Werror not building. Remove for now: 150 | # export CXXFLAGS="-Wall -Wextra -Werror" 151 | export CXXFLAGS="-Wall -Wextra" 152 | export CMAKE_OPTIONS=${CMAKE_OPTIONS:--DBUILD_TESTING=ON} 153 | export CMAKE_SHARED_LIBS=${CMAKE_SHARED_LIBS:-1} 154 | 155 | mkdir __build_static 156 | cd __build_static 157 | cmake -DBOOST_ENABLE_CMAKE=1 -DBoost_VERBOSE=1 ${CMAKE_OPTIONS} \ 158 | -DBOOST_INCLUDE_LIBRARIES=$SELF .. 159 | cmake --build . 160 | ctest --output-on-failure -R boost_$SELF 161 | 162 | cd .. 163 | 164 | if [ "$CMAKE_SHARED_LIBS" = 1 ]; then 165 | 166 | mkdir __build_shared 167 | cd __build_shared 168 | cmake -DBOOST_ENABLE_CMAKE=1 -DBoost_VERBOSE=1 ${CMAKE_OPTIONS} \ 169 | -DBOOST_INCLUDE_LIBRARIES=$SELF -DBUILD_SHARED_LIBS=ON .. 170 | cmake --build . 171 | ctest --output-on-failure -R boost_$SELF 172 | 173 | fi 174 | 175 | fi 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /bin64 3 | /_build* 4 | temp 5 | 6 | # Emacs 7 | *# 8 | 9 | # Vim 10 | *~ 11 | 12 | # Visual Studio 13 | /.vs 14 | /out 15 | 16 | # Visual Studio Code 17 | /.vscode 18 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | # Copyright (c) 2021 Dmitry Arkhipov (grisumbras@gmail.com) 4 | # Copyright (c) 2022 Alan de Freitas (alandefreitas@gmail.com) 5 | # Copyright (c) 2025 Mohammad Nejati 6 | # 7 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | # 10 | # Official repository: https://github.com/cppalliance/http_io 11 | # 12 | 13 | #------------------------------------------------- 14 | # 15 | # Project 16 | # 17 | #------------------------------------------------- 18 | cmake_minimum_required(VERSION 3.8...3.20) 19 | set(BOOST_HTTP_IO_VERSION 1) 20 | if (BOOST_SUPERPROJECT_VERSION) 21 | set(BOOST_HTTP_IO_VERSION ${BOOST_SUPERPROJECT_VERSION}) 22 | endif () 23 | project(boost_http_io VERSION "${BOOST_HTTP_IO_VERSION}" LANGUAGES CXX) 24 | set(BOOST_HTTP_IO_IS_ROOT OFF) 25 | if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 26 | set(BOOST_HTTP_IO_IS_ROOT ON) 27 | endif () 28 | set(__ignore__ ${CMAKE_C_COMPILER}) 29 | 30 | #------------------------------------------------- 31 | # 32 | # Options 33 | # 34 | #------------------------------------------------- 35 | if (BOOST_HTTP_IO_IS_ROOT) 36 | include(CTest) 37 | endif () 38 | option(BOOST_HTTP_IO_BUILD_TESTS "Build boost::http_io tests" ${BUILD_TESTING}) 39 | option(BOOST_HTTP_IO_BUILD_EXAMPLES "Build boost::http_io examples" ${BOOST_HTTP_IO_IS_ROOT}) 40 | 41 | 42 | # Check if environment variable BOOST_SRC_DIR is set 43 | if (NOT DEFINED BOOST_SRC_DIR AND DEFINED ENV{BOOST_SRC_DIR}) 44 | set(DEFAULT_BOOST_SRC_DIR "$ENV{BOOST_SRC_DIR}") 45 | else () 46 | set(DEFAULT_BOOST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../..") 47 | endif () 48 | set(BOOST_SRC_DIR ${DEFAULT_BOOST_SRC_DIR} CACHE STRING "Boost source dir to use when running CMake from this directory") 49 | 50 | #------------------------------------------------- 51 | # 52 | # Boost modules 53 | # 54 | #------------------------------------------------- 55 | # The boost super-project requires one explicit dependency per-line. 56 | set(BOOST_HTTP_IO_DEPENDENCIES 57 | Boost::asio 58 | Boost::assert 59 | Boost::config 60 | Boost::http_proto 61 | Boost::system 62 | Boost::throw_exception 63 | ) 64 | 65 | foreach (BOOST_HTTP_IO_DEPENDENCY ${BOOST_HTTP_IO_DEPENDENCIES}) 66 | if (BOOST_HTTP_IO_DEPENDENCY MATCHES "^[ ]*Boost::([A-Za-z0-9_]+)[ ]*$") 67 | list(APPEND BOOST_HTTP_IO_INCLUDE_LIBRARIES ${CMAKE_MATCH_1}) 68 | endif () 69 | endforeach () 70 | # Conditional dependencies 71 | if (BOOST_HTTP_IO_BUILD_TESTS) 72 | set(BOOST_HTTP_IO_UNIT_TEST_LIBRARIES beast url) 73 | endif () 74 | if (BOOST_HTTP_IO_BUILD_EXAMPLES) 75 | set(BOOST_HTTP_IO_EXAMPLE_LIBRARIES program_options scope url) 76 | endif () 77 | # Complete dependency list 78 | set(BOOST_INCLUDE_LIBRARIES ${BOOST_HTTP_IO_INCLUDE_LIBRARIES} ${BOOST_HTTP_IO_UNIT_TEST_LIBRARIES} ${BOOST_HTTP_IO_EXAMPLE_LIBRARIES}) 79 | set(BOOST_EXCLUDE_LIBRARIES http_io) 80 | 81 | #------------------------------------------------- 82 | # 83 | # Add Boost Subdirectory 84 | # 85 | #------------------------------------------------- 86 | if (BOOST_HTTP_IO_IS_ROOT) 87 | set(CMAKE_FOLDER Dependencies) 88 | # Find absolute BOOST_SRC_DIR 89 | if (NOT IS_ABSOLUTE ${BOOST_SRC_DIR}) 90 | set(BOOST_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${BOOST_SRC_DIR}") 91 | endif () 92 | 93 | # Validate BOOST_SRC_DIR 94 | set(BOOST_SRC_DIR_IS_VALID ON) 95 | foreach (F "CMakeLists.txt" "Jamroot" "boost-build.jam" "bootstrap.sh" "libs") 96 | if (NOT EXISTS "${BOOST_SRC_DIR}/${F}") 97 | message(STATUS "${BOOST_SRC_DIR}/${F} does not exist. Fallback to find_package.") 98 | set(BOOST_SRC_DIR_IS_VALID OFF) 99 | break() 100 | endif () 101 | endforeach () 102 | 103 | # Create Boost interface targets 104 | if (BOOST_SRC_DIR_IS_VALID) 105 | # From BOOST_SRC_DIR 106 | if (BUILD_SHARED_LIBS) 107 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 108 | endif () 109 | set(BOOST_EXCLUDE_LIBRARIES ${PROJECT_NAME}) 110 | set(PREV_BUILD_TESTING ${BUILD_TESTING}) 111 | set(BUILD_TESTING OFF CACHE BOOL "Build the tests." FORCE) 112 | add_subdirectory(${BOOST_SRC_DIR} Dependencies/boost EXCLUDE_FROM_ALL) 113 | set(BUILD_TESTING ${PREV_BUILD_TESTING} CACHE BOOL "Build the tests." FORCE) 114 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${BOOST_SRC_DIR}/tools/cmake/include") 115 | else () 116 | # From Boost Package 117 | find_package(Boost REQUIRED COMPONENTS buffers http_proto program_options scope url) 118 | foreach (BOOST_INCLUDE_LIBRARY ${BOOST_INCLUDE_LIBRARIES}) 119 | if (NOT TARGET Boost::${BOOST_INCLUDE_LIBRARY}) 120 | add_library(Boost::${BOOST_INCLUDE_LIBRARY} ALIAS Boost::headers) 121 | endif () 122 | endforeach () 123 | endif () 124 | unset(CMAKE_FOLDER) 125 | endif () 126 | 127 | #------------------------------------------------- 128 | # 129 | # Library 130 | # 131 | #------------------------------------------------- 132 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 133 | 134 | file(GLOB_RECURSE BOOST_HTTP_IO_HEADERS CONFIGURE_DEPENDS include/boost/http_io/*.hpp include/boost/*.natvis) 135 | file(GLOB_RECURSE BOOST_HTTP_IO_SOURCES CONFIGURE_DEPENDS src/*.cpp src/*.hpp) 136 | 137 | source_group("" FILES "include/boost/http_io.hpp" "build/Jamfile") 138 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include/boost/http_io PREFIX "include" FILES ${BOOST_HTTP_IO_HEADERS}) 139 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "src" FILES ${BOOST_HTTP_IO_SOURCES}) 140 | 141 | add_library(boost_http_io include/boost/http_io.hpp build/Jamfile ${BOOST_HTTP_IO_HEADERS} ${BOOST_HTTP_IO_SOURCES}) 142 | add_library(Boost::http_io ALIAS boost_http_io) 143 | target_compile_features(boost_http_io PUBLIC cxx_constexpr) 144 | target_include_directories(boost_http_io PUBLIC "${PROJECT_SOURCE_DIR}/include") 145 | target_link_libraries(boost_http_io PUBLIC ${BOOST_HTTP_IO_DEPENDENCIES}) 146 | find_package(Threads REQUIRED) 147 | target_link_libraries(boost_http_io PUBLIC Threads::Threads) 148 | if (MINGW) 149 | target_link_libraries(boost_http_io PUBLIC ws2_32 wsock32) 150 | endif() 151 | target_compile_definitions(boost_http_io PUBLIC BOOST_HTTP_IO_NO_LIB) 152 | target_compile_definitions(boost_http_io PRIVATE BOOST_HTTP_IO_SOURCE) 153 | if (BUILD_SHARED_LIBS) 154 | target_compile_definitions(boost_http_io PUBLIC BOOST_HTTP_IO_DYN_LINK) 155 | else () 156 | target_compile_definitions(boost_http_io PUBLIC BOOST_HTTP_IO_STATIC_LINK) 157 | endif () 158 | 159 | #------------------------------------------------- 160 | # 161 | # Tests 162 | # 163 | #------------------------------------------------- 164 | if (BOOST_HTTP_IO_BUILD_TESTS) 165 | add_subdirectory(test) 166 | endif () 167 | 168 | #------------------------------------------------- 169 | # 170 | # Examples 171 | # 172 | #------------------------------------------------- 173 | if (BOOST_HTTP_IO_BUILD_EXAMPLES) 174 | add_subdirectory(example) 175 | endif () 176 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | [width="100%",cols="7%,66%,27%",options="header",] 2 | |=== 3 | 4 | |Branch 5 | |https://github.com/cppalliance/http_io/tree/master[`master`] 6 | |https://github.com/cppalliance/http_io/tree/develop[`develop`] 7 | 8 | |https://develop.http-io.cpp.al/[Docs] 9 | |https://master.http-io.cpp.al/[image:https://img.shields.io/badge/docs-master-brightgreen.svg[Documentation]] 10 | |https://develop.http-io.cpp.al/[image:https://img.shields.io/badge/docs-develop-brightgreen.svg[Documentation]] 11 | 12 | |https://github.com/[GitHub Actions] 13 | |https://github.com/cppalliance/http_io/actions/workflows/ci.yml?query=branch%3Amaster[image:https://github.com/cppalliance/http_io/actions/workflows/ci.yml/badge.svg?branch=master[CI]] 14 | |https://github.com/cppalliance/http_io/actions/workflows/ci.yml?query=branch%3Adevelop[image:https://github.com/cppalliance/http_io/actions/workflows/ci.yml/badge.svg?branch=develop[CI]] 15 | 16 | 17 | |https://drone.io/[Drone] 18 | |https://drone.cpp.al/cppalliance/http_io/branches[image:https://drone.cpp.al/api/badges/cppalliance/http_io/status.svg?ref=refs/heads/master[Build Status]] 19 | |https://drone.cpp.al/cppalliance/http_io/branches[image:https://drone.cpp.al/api/badges/cppalliance/http_io/status.svg?ref=refs/heads/develop[Build Status]] 20 | 21 | |https://codecov.io[Codecov] 22 | |https://app.codecov.io/gh/cppalliance/http_io/tree/master[image:https://codecov.io/gh/cppalliance/http_io/branch/master/graph/badge.svg[codecov]] 23 | |https://app.codecov.io/gh/cppalliance/http_io/tree/develop[image:https://codecov.io/gh/cppalliance/http_io/branch/develop/graph/badge.svg[codecov]] 24 | 25 | |=== 26 | 27 | == boost.http_io 28 | 29 | === Visual Studio Solution 30 | 31 | ```cpp 32 | cmake -G "Visual Studio 17 2022" -A win32 -B bin -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="C:/Users/vinnie/src/boost/libs/http_io/cmake/toolchains/msvc.cmake" 33 | cmake -G "Visual Studio 17 2022" -A x64 -B bin64 -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="C:/Users/vinnie/src/boost/libs/http_io/cmake/toolchains/msvc.cmake" 34 | ``` 35 | -------------------------------------------------------------------------------- /build/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/CPPAlliance/http_io 8 | # 9 | 10 | import ../../config/checks/config : requires ; 11 | 12 | constant c11-requires : 13 | [ requires 14 | cxx11_constexpr 15 | cxx11_decltype 16 | cxx11_hdr_tuple 17 | cxx11_template_aliases 18 | cxx11_variadic_templates 19 | ] 20 | ; 21 | 22 | explicit 23 | [ searched-lib ws2_32 : : windows ] # NT 24 | [ searched-lib mswsock : : windows ] # NT 25 | ; 26 | 27 | project boost/http_io 28 | : requirements 29 | $(c11-requires) 30 | shared:BOOST_HTTP_IO_DYN_LINK=1 31 | static:BOOST_HTTP_IO_STATIC_LINK=1 32 | windows:_WIN32_WINNT=0x0601 # VFALCO? 33 | windows,gcc:ws2_32 34 | windows,gcc:mswsock 35 | windows,gcc-cygwin:__USE_W32_SOCKETS 36 | BOOST_HTTP_IO_SOURCE 37 | : usage-requirements 38 | shared:BOOST_HTTP_IO_DYN_LINK=1 39 | static:BOOST_HTTP_IO_STATIC_LINK=1 40 | windows:_WIN32_WINNT=0x0601 # VFALCO? 41 | windows,gcc:ws2_32 42 | windows,gcc:mswsock 43 | windows,gcc-cygwin:__USE_W32_SOCKETS 44 | : source-location ../src 45 | ; 46 | 47 | alias http_io_sources 48 | : 49 | detail/except.cpp 50 | ; 51 | 52 | explicit http_io_sources ; 53 | 54 | lib boost_http_io 55 | : http_io_sources 56 | : requirements 57 | /boost//http_proto 58 | : usage-requirements 59 | /boost//http_proto 60 | ; 61 | 62 | boost-install boost_http_io ; 63 | -------------------------------------------------------------------------------- /cmake/config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | set(BOOST_JSON_STANDALONE @BOOST_JSON_STANDALONE@) 4 | 5 | if(NOT BOOST_JSON_STANDALONE) 6 | include(CMakeFindDependencyMacro) 7 | find_dependency(Boost REQUIRED COMPONENTS container system) 8 | endif() 9 | 10 | include("${CMAKE_CURRENT_LIST_DIR}/boost_json-targets.cmake") 11 | -------------------------------------------------------------------------------- /cmake/toolchains/clang.cmake: -------------------------------------------------------------------------------- 1 | # Include gcc options. 2 | include(${CMAKE_CURRENT_LIST_DIR}/gcc.cmake) 3 | 4 | # Compiler options. 5 | add_compile_options(-Wrange-loop-analysis) 6 | -------------------------------------------------------------------------------- /cmake/toolchains/common.cmake: -------------------------------------------------------------------------------- 1 | # C++ standard. 2 | set(CMAKE_CXX_EXTENSIONS OFF CACHE STRING "") 3 | 4 | # Static library linkage. 5 | set(BUILD_SHARED_LIBS OFF CACHE STRING "") 6 | add_definitions(-DBOOST_ALL_STATIC_LINK=1) 7 | 8 | # Interprocedural optimization. 9 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON CACHE STRING "") 10 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL ON CACHE STRING "") 11 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON CACHE STRING "") 12 | 13 | # Compiler definitions. 14 | if(WIN32) 15 | add_definitions(-D_WIN32_WINNT=0x0601 -D_CRT_SECURE_NO_WARNINGS) 16 | endif() 17 | 18 | # Project options. 19 | set(BOOST_JSON_BUILD_BENCHMARKS ON CACHE STRING "") 20 | 21 | # Detect Boost tree. 22 | if(NOT DEFINED BOOST_JSON_IN_BOOST_TREE AND EXISTS "${CMAKE_CURRENT_LIST_DIR}/../../../../Jamroot") 23 | set(BOOST_JSON_IN_BOOST_TREE ON CACHE STRING "") 24 | endif() 25 | -------------------------------------------------------------------------------- /cmake/toolchains/gcc.cmake: -------------------------------------------------------------------------------- 1 | # Include common options. 2 | include(${CMAKE_CURRENT_LIST_DIR}/common.cmake) 3 | 4 | # Compiler options. 5 | add_compile_options(-Wall -Wextra -Wpedantic -Wno-unused-parameter) 6 | -------------------------------------------------------------------------------- /cmake/toolchains/msvc.cmake: -------------------------------------------------------------------------------- 1 | # Include common options. 2 | include(${CMAKE_CURRENT_LIST_DIR}/common.cmake) 3 | 4 | # Static runtime linkage. 5 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "") 6 | 7 | # Compiler options. 8 | add_compile_options( 9 | /permissive- # strict C++ 10 | /W4 # enable all warnings 11 | /MP # multi-processor compilation 12 | /bigobj 13 | ) 14 | if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "Win32") # 32-bit 15 | add_compile_options( 16 | /arch:SSE2 17 | ) 18 | endif() 19 | 20 | # Linker options. 21 | add_link_options( 22 | ) 23 | 24 | # Disable logos. 25 | foreach(lang C CXX ASM_MASM RC) 26 | set(CMAKE_${lang}_FLAGS_INIT "/nologo") 27 | endforeach() 28 | foreach(type EXE SHARED MODULE) 29 | set(CMAKE_${type}_LINKER_FLAGS_INIT "/nologo") 30 | endforeach() 31 | 32 | # Silence Visual Studio CMake integration warnings. 33 | set(SILENCE_VS_DEFINITIONS ${CMAKE_TOOLCHAIN_FILE} ${CMAKE_C_COMPILER}) 34 | set(SILENCE_VS_DEFINITIONS) 35 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /doc/antora.yml: -------------------------------------------------------------------------------- 1 | name: http_io 2 | version: ~ 3 | title: Boost.Http.Io 4 | start_page: index.adoc 5 | asciidoc: 6 | attributes: 7 | source-language: asciidoc@ 8 | table-caption: false 9 | nav: 10 | - modules/ROOT/nav.adoc 11 | ext: 12 | cpp-reference: 13 | config: doc/mrdocs.yml 14 | -------------------------------------------------------------------------------- /doc/build_antora.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | if "%~1"=="" ( 5 | echo No playbook supplied, using default playbook 6 | set "PLAYBOOK=local-playbook.yml" 7 | ) else ( 8 | set "PLAYBOOK=%~1" 9 | ) 10 | 11 | echo Building documentation with Antora... 12 | echo Installing npm dependencies... 13 | call npm ci 14 | 15 | echo Building docs in custom dir... 16 | call "C:\Program Files\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 17 | call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 18 | set "PATH=%PATH%;C:\Program Files\7-Zip" 19 | set "PATH=%PATH%;%CD%\node_modules\.bin" 20 | call npx antora --clean --fetch "%PLAYBOOK%" 21 | echo Done 22 | -------------------------------------------------------------------------------- /doc/build_antora.sh: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Alan de Freitas (alandefreitas@gmail.com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/boostorg/url 8 | # 9 | 10 | set -xe 11 | 12 | if [ $# -eq 0 ] 13 | then 14 | echo "No playbook supplied, using default playbook" 15 | PLAYBOOK="local-playbook.yml" 16 | else 17 | PLAYBOOK=$1 18 | fi 19 | 20 | echo "Building documentation with Antora..." 21 | echo "Installing npm dependencies..." 22 | npm ci 23 | 24 | echo "Building docs in custom dir..." 25 | PATH="$(pwd)/node_modules/.bin:${PATH}" 26 | export PATH 27 | npx antora --clean --fetch "$PLAYBOOK" 28 | echo "Done" 29 | 30 | -------------------------------------------------------------------------------- /doc/local-playbook.yml: -------------------------------------------------------------------------------- 1 | site: 2 | title: Boost.Http.Io 3 | url: https://antora.cppalliance.org/develop/lib/doc 4 | start_page: http_io::index.adoc 5 | robots: allow 6 | keys: 7 | repo_url: 'https://github.com/cppalliance/http_io' 8 | 9 | content: 10 | sources: 11 | - url: .. 12 | start_path: doc 13 | edit_url: 'https://github.com/cppalliance/http_io/edit/{refname}/{path}' 14 | 15 | ui: 16 | bundle: 17 | url: https://github.com/boostorg/website-v2-docs/releases/download/ui-master/ui-bundle.zip 18 | snapshot: true 19 | 20 | antora: 21 | extensions: 22 | - require: '@antora/lunr-extension' # https://gitlab.com/antora/antora-lunr-extension 23 | index_latest_only: true 24 | - require: '@cppalliance/antora-cpp-tagfiles-extension' 25 | cpp-tagfiles: 26 | using-namespaces: 27 | - 'boost::' 28 | - require: '@cppalliance/antora-cpp-reference-extension' 29 | dependencies: 30 | - name: 'boost' 31 | repo: 'https://github.com/boostorg/boost.git' 32 | tag: 'develop' 33 | variable: 'BOOST_SRC_DIR' 34 | system-env: 'BOOST_SRC_DIR' 35 | 36 | asciidoc: 37 | attributes: 38 | # Enable pagination 39 | page-pagination: '' 40 | extensions: 41 | - '@cppalliance/asciidoctor-boost-links' 42 | - '@asciidoctor/tabs' 43 | -------------------------------------------------------------------------------- /doc/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:reference:boost/http_io.adoc[Reference] 2 | -------------------------------------------------------------------------------- /doc/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) 3 | // Copyright (c) 2024 Mohammad Nejati 4 | // 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Official repository: https://github.com/cppalliance/buffers 9 | // 10 | 11 | = Boost.Http.Io 12 | 13 | Boost.Http.Io is a portable, low-level C++ library which implements the HTTP/1 14 | protocol using Boost.Asio networking. 15 | 16 | Boost.Http.Io offers these features: 17 | 18 | * Require only C++11 19 | * Fast compilation, few templates 20 | 21 | == Requirements 22 | 23 | * Requires Boost and a compiler supporting at least C++11 24 | * Aliases for standard types use their Boost equivalents 25 | * Link to a built static or dynamic Boost library, or use header-only (see below) 26 | 27 | == Tested Compilers 28 | 29 | Boost.Buffers has been tested with the following compilers: 30 | 31 | * clang: 3.8, 4, 5, 6, 7, 8, 9, 10, 11, 12 32 | * gcc: 4.8, 4.9, 5, 6, 7, 8, 9, 10, 11 33 | * msvc: 14.1, 14.2, 14.3 34 | 35 | == Quality Assurance 36 | 37 | The development infrastructure for the library includes these per-commit analyses: 38 | 39 | * Coverage reports 40 | * Compilation and tests on Drone.io and GitHub Actions 41 | 42 | == Security Review (Bishop Fox) 43 | 44 | TBA 45 | -------------------------------------------------------------------------------- /doc/mrdocs.yml: -------------------------------------------------------------------------------- 1 | # Input 2 | source-root: .. 3 | # Directories that contain documented source files 4 | input: 5 | - ../include 6 | # Patterns to filter out the source-files in the directories 7 | file-patterns: 8 | - '*.hpp' 9 | 10 | # Filters 11 | include-symbols: 12 | - 'boost::http_io::**' 13 | implementation-defined: 14 | - 'boost::http_io::detail' 15 | - 'boost::http_io::*::detail' 16 | inaccessible-members: never 17 | inaccessible-bases: never 18 | 19 | # Generator 20 | generate: adoc 21 | base-url: https://www.github.com/cppalliance/http_io/blob/develop/include/ 22 | 23 | # Style 24 | verbose: true 25 | multipage: true 26 | use-system-libc: true 27 | 28 | cmake: '-DCMAKE_CXX_STANDARD=20 -DBOOST_HTTP_IO_BUILD_TESTS=OFF -DBOOST_HTTP_IO_BUILD_EXAMPLES=OFF' 29 | -------------------------------------------------------------------------------- /doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@antora/cli": "3.1.3", 4 | "@antora/site-generator": "3.1.3", 5 | "antora": "3.1.3" 6 | }, 7 | "dependencies": { 8 | "@cppalliance/antora-cpp-reference-extension": "^0.0.6", 9 | "@cppalliance/antora-cpp-tagfiles-extension": "^0.0.4", 10 | "@cppalliance/asciidoctor-boost-links": "^0.0.2", 11 | "@antora/expand-path-helper": "^2.0.0", 12 | "@antora/lunr-extension": "^1.0.0-alpha.8", 13 | "@asciidoctor/tabs": "^1.0.0-beta.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | # Copyright (c) 2024 Mohammad Nejati 4 | # 5 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Official repository: https://github.com/cppalliance/http_io 9 | # 10 | 11 | add_subdirectory(client) 12 | add_subdirectory(server) 13 | -------------------------------------------------------------------------------- /example/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) 3 | # Copyright (c) 2024 Mohammad Nejati 4 | # 5 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Official repository: https://github.com/cppalliance/http_io 9 | # 10 | 11 | build-project client ; 12 | build-project server ; 13 | -------------------------------------------------------------------------------- /example/client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024 Mohammad Nejati 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | add_subdirectory(burl) 11 | add_subdirectory(visit) 12 | -------------------------------------------------------------------------------- /example/client/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024 Mohammad Nejati 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | build-project burl ; 11 | build-project visit ; 12 | -------------------------------------------------------------------------------- /example/client/burl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024 Mohammad Nejati 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | if (CMAKE_CXX_STANDARD EQUAL 20) 11 | file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp 12 | CMakeLists.txt 13 | Jamfile) 14 | 15 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) 16 | 17 | add_executable(http_io_example_client_burl ${PFILES}) 18 | 19 | target_compile_definitions(http_io_example_client_burl 20 | PRIVATE BOOST_ASIO_NO_DEPRECATED) 21 | 22 | set_property(TARGET http_io_example_client_burl 23 | PROPERTY FOLDER "examples") 24 | 25 | find_package(OpenSSL REQUIRED) 26 | find_package(ZLIB) 27 | 28 | target_link_libraries(http_io_example_client_burl 29 | boost_http_io 30 | boost_program_options 31 | boost_scope 32 | OpenSSL::SSL 33 | OpenSSL::Crypto) 34 | 35 | if (WIN32) 36 | target_link_libraries(http_io_example_client_burl 37 | crypt32) 38 | endif() 39 | 40 | if (ZLIB_FOUND) 41 | target_link_libraries(http_io_example_client_burl 42 | boost_http_proto_zlib) 43 | endif() 44 | endif() 45 | -------------------------------------------------------------------------------- /example/client/burl/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024 Mohammad Nejati 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | import ../../../../config/checks/config : requires ; 11 | 12 | using openssl ; 13 | import ac ; 14 | 15 | project 16 | : requirements 17 | $(c11-requires) 18 | [ requires 19 | cxx20_hdr_concepts 20 | ] 21 | /boost/http_proto//boost_http_proto 22 | [ ac.check-library /boost/http_proto//boost_http_proto_zlib : /boost/http_proto//boost_http_proto_zlib : ] 23 | /boost/http_io//boost_http_io 24 | /boost/program_options//boost_program_options 25 | /boost/scope//scope 26 | /openssl//ssl/shared 27 | /openssl//crypto/shared 28 | windows:crypt32 29 | . 30 | ; 31 | 32 | exe burl : 33 | [ glob *.cpp ] 34 | ; 35 | -------------------------------------------------------------------------------- /example/client/burl/any_iostream.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "any_iostream.hpp" 11 | 12 | #ifdef _MSC_VER 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | namespace 19 | { 20 | bool 21 | is_tty(FILE* stream) 22 | { 23 | #ifdef _MSC_VER 24 | return _isatty(_fileno(stream)); 25 | #else 26 | return isatty(fileno(stream)); 27 | #endif 28 | } 29 | } // namespace 30 | 31 | any_ostream::any_ostream(core::string_view path, bool append) 32 | : any_ostream{ fs::path{ path.begin(), path.end() }, append } 33 | { 34 | } 35 | 36 | any_ostream::any_ostream(fs::path path, bool append) 37 | { 38 | if(path == "-") 39 | { 40 | stream_.emplace(&std::cout); 41 | is_tty_ = ::is_tty(stdout); 42 | } 43 | else if(path == "%") 44 | { 45 | stream_.emplace(&std::cerr); 46 | is_tty_ = ::is_tty(stderr); 47 | } 48 | else 49 | { 50 | auto& f = stream_.emplace(); 51 | f.exceptions(std::ofstream::badbit); 52 | if(append) 53 | f.open(path, std::ofstream::app); 54 | else 55 | f.open(path); 56 | if(!f.is_open()) 57 | throw std::runtime_error{ "Couldn't open file" }; 58 | } 59 | } 60 | 61 | bool 62 | any_ostream::is_tty() const noexcept 63 | { 64 | return is_tty_; 65 | } 66 | 67 | void 68 | any_ostream::close() 69 | { 70 | if(auto* s = std::get_if(&stream_)) 71 | s->close(); 72 | } 73 | 74 | any_ostream:: 75 | operator std::ostream&() 76 | { 77 | if(auto* s = std::get_if(&stream_)) 78 | return *s; 79 | return *std::get(stream_); 80 | } 81 | 82 | // ----------------------------------------------------------------------------- 83 | 84 | any_istream::any_istream(core::string_view path) 85 | : any_istream{ fs::path{ path.begin(), path.end() } } 86 | { 87 | } 88 | 89 | any_istream::any_istream(fs::path path) 90 | { 91 | if(path == "-") 92 | { 93 | stream_.emplace(&std::cin); 94 | } 95 | else 96 | { 97 | auto& f = stream_.emplace(); 98 | f.exceptions(std::ifstream::badbit); 99 | f.open(path); 100 | if(!f.is_open()) 101 | throw std::runtime_error{ "Couldn't open file" }; 102 | } 103 | } 104 | 105 | void 106 | any_istream::append_to(std::string& s) 107 | { 108 | s.append( 109 | std::istreambuf_iterator{ static_cast(*this) }, 110 | {}); 111 | } 112 | 113 | any_istream:: 114 | operator std::istream&() 115 | { 116 | if(auto* s = std::get_if(&stream_)) 117 | return *s; 118 | return *std::get(stream_); 119 | } 120 | -------------------------------------------------------------------------------- /example/client/burl/any_iostream.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_ANY_IOSTREAM_HPP 11 | #define BURL_ANY_IOSTREAM_HPP 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace fs = std::filesystem; 21 | namespace core = boost::core; 22 | 23 | class any_ostream 24 | { 25 | std::variant stream_; 26 | bool is_tty_ = false; 27 | 28 | public: 29 | any_ostream(core::string_view path, bool append = false); 30 | 31 | any_ostream(fs::path path, bool append = false); 32 | 33 | bool 34 | is_tty() const noexcept; 35 | 36 | void 37 | close(); 38 | 39 | operator std::ostream&(); 40 | 41 | template 42 | std::ostream& 43 | operator<<(const T& data) 44 | { 45 | static_cast(*this) << data; 46 | return *this; 47 | } 48 | }; 49 | 50 | class any_istream 51 | { 52 | std::variant stream_; 53 | 54 | public: 55 | any_istream(core::string_view path); 56 | 57 | any_istream(fs::path path); 58 | 59 | void 60 | append_to(std::string& s); 61 | 62 | operator std::istream&(); 63 | 64 | template 65 | std::istream& 66 | operator>>(T& data) 67 | { 68 | static_cast(*this) >> data; 69 | return *this; 70 | } 71 | }; 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /example/client/burl/any_stream.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_ANY_STREAM_HPP 11 | #define BURL_ANY_STREAM_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace asio = boost::asio; 24 | namespace buffers = boost::buffers; 25 | namespace http_proto = boost::http_proto; 26 | using error_code = boost::system::error_code; 27 | 28 | class any_stream 29 | { 30 | public: 31 | using const_buffers_type = http_proto::serializer::const_buffers_type; 32 | using mutable_buffers_type = http_proto::parser::mutable_buffers_type; 33 | using executor_type = asio::any_io_executor; 34 | 35 | template 36 | any_stream(Stream stream) 37 | : rd_timer{ stream.get_executor() } 38 | , wr_timer{ stream.get_executor() } 39 | { 40 | class impl : public base 41 | { 42 | Stream stream_; 43 | 44 | public: 45 | impl(Stream stream) 46 | : stream_{ std::move(stream) } 47 | { 48 | } 49 | 50 | virtual asio::any_io_executor 51 | get_executor() override 52 | { 53 | return stream_.get_executor(); 54 | } 55 | 56 | virtual void 57 | async_write_some( 58 | const buffers::const_buffer_subspan& buffers, 59 | asio::any_completion_handler 60 | handler) override 61 | { 62 | stream_.async_write_some(buffers, std::move(handler)); 63 | } 64 | 65 | virtual void 66 | async_read_some( 67 | const buffers::mutable_buffer_subspan& buffers, 68 | asio::any_completion_handler 69 | handler) override 70 | { 71 | stream_.async_read_some(buffers, std::move(handler)); 72 | } 73 | 74 | virtual void 75 | async_shutdown( 76 | asio::any_completion_handler handler) override 77 | { 78 | if constexpr(requires { typename Stream::handshake_type; }) 79 | { 80 | stream_.async_shutdown(std::move(handler)); 81 | } 82 | else 83 | { 84 | asio::async_compose( 85 | [this, 86 | init = false](auto&& self, error_code ec = {}) mutable 87 | { 88 | if(std::exchange(init, true)) 89 | return self.complete(ec); 90 | 91 | stream_.close(ec); 92 | asio::async_immediate( 93 | stream_.get_executor(), 94 | asio::append(std::move(self), ec)); 95 | }, 96 | handler, 97 | stream_); 98 | } 99 | } 100 | }; 101 | 102 | stream_ = std::make_unique(std::move(stream)); 103 | } 104 | 105 | executor_type 106 | get_executor() const 107 | { 108 | return stream_->get_executor(); 109 | } 110 | 111 | void 112 | read_limit(std::size_t bytes_per_second) noexcept 113 | { 114 | rd_limit_ = bytes_per_second; 115 | if(rd_remain_ > bytes_per_second) 116 | rd_remain_ = bytes_per_second; 117 | } 118 | 119 | void 120 | write_limit(std::size_t bytes_per_second) noexcept 121 | { 122 | wr_limit_ = bytes_per_second; 123 | if(wr_remain_ > bytes_per_second) 124 | wr_remain_ = bytes_per_second; 125 | } 126 | 127 | template< 128 | typename CompletionToken = 129 | asio::default_completion_token_t> 130 | auto 131 | async_write_some( 132 | const const_buffers_type& buffers, 133 | CompletionToken&& token = CompletionToken{}) 134 | { 135 | return asio:: 136 | async_compose( 137 | [this, c = asio::coroutine{}, buffers]( 138 | auto&& self, error_code ec = {}, std::size_t n = {}) mutable 139 | { 140 | BOOST_ASIO_CORO_REENTER(c) 141 | { 142 | self.reset_cancellation_state( 143 | asio::enable_total_cancellation{}); 144 | if(wr_remain_ == 0) 145 | { 146 | wr_timer.expires_after(std::chrono::seconds{ 1 }); 147 | BOOST_ASIO_CORO_YIELD 148 | wr_timer.async_wait(std::move(self)); 149 | if(ec) 150 | return self.complete(ec, n); 151 | wr_remain_ = wr_limit_; 152 | } 153 | BOOST_ASIO_CORO_YIELD 154 | stream_->async_write_some( 155 | buffers::prefix(buffers, wr_remain_), 156 | std::move(self)); 157 | wr_remain_ -= n; 158 | self.complete(ec, n); 159 | } 160 | }, 161 | token, 162 | get_executor()); 163 | } 164 | 165 | template< 166 | typename CompletionToken = 167 | asio::default_completion_token_t> 168 | auto 169 | async_read_some( 170 | const mutable_buffers_type& buffers, 171 | CompletionToken&& token = CompletionToken{}) 172 | { 173 | return asio:: 174 | async_compose( 175 | [this, c = asio::coroutine{}, buffers]( 176 | auto&& self, error_code ec = {}, std::size_t n = {}) mutable 177 | { 178 | BOOST_ASIO_CORO_REENTER(c) 179 | { 180 | self.reset_cancellation_state( 181 | asio::enable_total_cancellation{}); 182 | if(rd_remain_ == 0) 183 | { 184 | rd_timer.expires_after(std::chrono::seconds{ 1 }); 185 | BOOST_ASIO_CORO_YIELD 186 | rd_timer.async_wait(std::move(self)); 187 | if(ec) 188 | return self.complete(ec, n); 189 | rd_remain_ = rd_limit_; 190 | } 191 | BOOST_ASIO_CORO_YIELD 192 | stream_->async_read_some( 193 | buffers::prefix(buffers, rd_remain_), 194 | std::move(self)); 195 | rd_remain_ -= n; 196 | self.complete(ec, n); 197 | } 198 | }, 199 | token, 200 | get_executor()); 201 | } 202 | 203 | template< 204 | typename CompletionToken = 205 | asio::default_completion_token_t> 206 | auto 207 | async_shutdown(CompletionToken&& token = CompletionToken{}) 208 | { 209 | return asio::async_compose( 210 | [this, init = false](auto&& self, error_code ec = {}) mutable 211 | { 212 | if(std::exchange(init, true)) 213 | return self.complete(ec); 214 | stream_->async_shutdown(std::move(self)); 215 | }, 216 | token, 217 | get_executor()); 218 | } 219 | 220 | private: 221 | struct base 222 | { 223 | virtual asio::any_io_executor 224 | get_executor() = 0; 225 | 226 | virtual void 227 | async_write_some( 228 | const buffers::const_buffer_subspan&, 229 | asio::any_completion_handler) = 0; 230 | 231 | virtual void 232 | async_read_some( 233 | const buffers::mutable_buffer_subspan&, 234 | asio::any_completion_handler) = 0; 235 | 236 | virtual void async_shutdown( 237 | asio::any_completion_handler) = 0; 238 | 239 | virtual ~base() = default; 240 | }; 241 | 242 | std::unique_ptr stream_; 243 | asio::steady_timer rd_timer; 244 | asio::steady_timer wr_timer; 245 | std::size_t rd_remain_ = (std::numeric_limits::max)(); 246 | std::size_t wr_remain_ = (std::numeric_limits::max)(); 247 | std::size_t rd_limit_ = (std::numeric_limits::max)(); 248 | std::size_t wr_limit_ = (std::numeric_limits::max)(); 249 | }; 250 | 251 | #endif 252 | -------------------------------------------------------------------------------- /example/client/burl/base64.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "base64.hpp" 11 | 12 | void 13 | base64_encode(std::string& dest, core::string_view src) 14 | { 15 | // Adapted from Boost.Beast project 16 | char const* in = static_cast(src.data()); 17 | static char constexpr tab[] = { 18 | "ABCDEFGHIJKLMNOP" 19 | "QRSTUVWXYZabcdef" 20 | "ghijklmnopqrstuv" 21 | "wxyz0123456789+/" 22 | }; 23 | 24 | for(auto n = src.size() / 3; n--;) 25 | { 26 | dest.append({ 27 | tab[(in[0] & 0xfc) >> 2], 28 | tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)], 29 | tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)], 30 | tab[in[2] & 0x3f] }); 31 | in += 3; 32 | } 33 | 34 | switch(src.size() % 3) 35 | { 36 | case 2: 37 | dest.append({ 38 | tab[ (in[0] & 0xfc) >> 2], 39 | tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)], 40 | tab[ (in[1] & 0x0f) << 2], 41 | '=' }); 42 | break; 43 | case 1: 44 | dest.append({ 45 | tab[ (in[0] & 0xfc) >> 2], 46 | tab[((in[0] & 0x03) << 4)], 47 | '=', 48 | '=' }); 49 | break; 50 | case 0: 51 | break; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/client/burl/base64.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_BASE64_HPP 11 | #define BURL_BASE64_HPP 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace core = boost::core; 18 | 19 | void 20 | base64_encode(std::string& dest, core::string_view src); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /example/client/burl/connect.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "connect.hpp" 11 | #include "base64.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace core = boost::core; 23 | namespace http_io = boost::http_io; 24 | using error_code = boost::system::error_code; 25 | using system_error = boost::system::system_error; 26 | 27 | namespace 28 | { 29 | core::string_view 30 | effective_port(const urls::url_view& url) 31 | { 32 | if(url.has_port()) 33 | return url.port(); 34 | 35 | if(url.scheme() == "https") 36 | return "443"; 37 | 38 | if(url.scheme() == "http") 39 | return "80"; 40 | 41 | if(url.scheme() == "socks5") 42 | return "1080"; 43 | 44 | throw std::runtime_error{ "Unsupported scheme" }; 45 | } 46 | 47 | asio::awaitable 48 | connect_socks5_proxy( 49 | asio::ip::tcp::socket& stream, 50 | const urls::url_view& url, 51 | const urls::url_view& proxy) 52 | { 53 | auto executor = co_await asio::this_coro::executor; 54 | auto resolver = asio::ip::tcp::resolver{ executor }; 55 | auto rresults = 56 | co_await resolver.async_resolve(proxy.host(), effective_port(proxy)); 57 | 58 | // Connect to the proxy server 59 | co_await asio::async_connect(stream, rresults); 60 | 61 | // Greeting request 62 | if(proxy.has_userinfo()) 63 | { 64 | std::uint8_t greeting_req[4] = { 0x05, 0x02, 0x00, 0x02 }; 65 | co_await asio::async_write(stream, asio::buffer(greeting_req)); 66 | } 67 | else 68 | { 69 | std::uint8_t greeting_req[3] = { 0x05, 0x01, 0x00 }; 70 | co_await asio::async_write(stream, asio::buffer(greeting_req)); 71 | } 72 | 73 | // Greeting response 74 | std::uint8_t greeting_resp[2]; 75 | co_await asio::async_read(stream, asio::buffer(greeting_resp)); 76 | 77 | if(greeting_resp[0] != 0x05) 78 | throw std::runtime_error{ "SOCKS5 invalid version" }; 79 | 80 | switch(greeting_resp[1]) 81 | { 82 | case 0x00: // No Authentication 83 | break; 84 | case 0x02: // Username/password 85 | { 86 | // Authentication request 87 | auto auth_req = std::string{ 0x01 }; 88 | 89 | auto user = proxy.encoded_user(); 90 | auth_req.push_back(static_cast(user.decoded_size())); 91 | user.decode({}, urls::string_token::append_to(auth_req)); 92 | 93 | auto pass = proxy.encoded_password(); 94 | auth_req.push_back(static_cast(pass.decoded_size())); 95 | pass.decode({}, urls::string_token::append_to(auth_req)); 96 | 97 | co_await asio::async_write(stream, asio::buffer(auth_req)); 98 | 99 | // Authentication response 100 | std::uint8_t auth_resp[2]; 101 | co_await asio::async_read(stream, asio::buffer(auth_resp)); 102 | 103 | if(auth_resp[1] != 0x00) 104 | throw std::runtime_error{ "SOCKS5 authentication failed" }; 105 | break; 106 | } 107 | default: 108 | throw std::runtime_error{ 109 | "SOCKS5 no acceptable authentication method" 110 | }; 111 | } 112 | 113 | // Connection request 114 | auto conn_req = std::string{ 0x05, 0x01, 0x00, 0x03 }; 115 | auto host = url.encoded_host(); 116 | conn_req.push_back(static_cast(host.decoded_size())); 117 | host.decode({}, urls::string_token::append_to(conn_req)); 118 | 119 | auto port = static_cast(std::stoul(effective_port(url))); 120 | conn_req.push_back(static_cast((port >> 8) & 0xFF)); 121 | conn_req.push_back(static_cast(port & 0xFF)); 122 | 123 | co_await asio::async_write(stream, asio::buffer(conn_req)); 124 | 125 | // Connection response 126 | std::uint8_t conn_resp_head[5]; 127 | co_await asio::async_read(stream, asio::buffer(conn_resp_head)); 128 | 129 | if(conn_resp_head[1] != 0x00) 130 | throw std::runtime_error{ "SOCKS5 connection request failed" }; 131 | 132 | std::string conn_resp_tail; 133 | conn_resp_tail.resize( 134 | [&]() 135 | { 136 | // subtract 1 because we have pre-read one byte 137 | switch(conn_resp_head[3]) 138 | { 139 | case 0x01: 140 | return 4 + 2 - 1; // ipv4 + port 141 | case 0x03: 142 | return conn_resp_head[4] + 2 - 1; // domain name + port 143 | case 0x04: 144 | return 16 + 2 - 1; // ipv6 + port 145 | default: 146 | throw std::runtime_error{ "SOCKS5 invalid address type" }; 147 | } 148 | }()); 149 | co_await asio::async_read(stream, asio::buffer(conn_resp_tail)); 150 | } 151 | 152 | asio::awaitable 153 | connect_http_proxy( 154 | const operation_config& oc, 155 | http_proto::context& proto_ctx, 156 | asio::ip::tcp::socket& stream, 157 | const urls::url_view& url, 158 | const urls::url_view& proxy) 159 | { 160 | auto executor = co_await asio::this_coro::executor; 161 | auto resolver = asio::ip::tcp::resolver{ executor }; 162 | auto rresults = 163 | co_await resolver.async_resolve(proxy.host(), effective_port(proxy)); 164 | 165 | // Connect to the proxy server 166 | co_await asio::async_connect(stream, rresults); 167 | 168 | using field = http_proto::field; 169 | auto request = http_proto::request{}; 170 | auto host_port = [&]() 171 | { 172 | auto rs = url.encoded_host().decode(); 173 | rs.push_back(':'); 174 | rs.append(effective_port(url)); 175 | return rs; 176 | }(); 177 | 178 | request.set_method(http_proto::method::connect); 179 | request.set_target(host_port); 180 | request.set(field::host, host_port); 181 | request.set(field::proxy_connection, "keep-alive"); 182 | request.set(field::user_agent, oc.useragent.value_or("Boost.Http.Io")); 183 | 184 | if(proxy.has_userinfo()) 185 | { 186 | auto credentials = proxy.encoded_userinfo().decode(); 187 | auto basic_auth = std::string{ "Basic " }; 188 | base64_encode(basic_auth, credentials); 189 | request.set(field::proxy_authorization, basic_auth); 190 | } 191 | 192 | auto serializer = http_proto::serializer{ proto_ctx }; 193 | auto parser = http_proto::response_parser{ proto_ctx }; 194 | 195 | serializer.start(request); 196 | co_await http_io::async_write(stream, serializer); 197 | 198 | parser.reset(); 199 | parser.start(); 200 | co_await http_io::async_read_header(stream, parser); 201 | 202 | if(parser.get().status() != http_proto::status::ok) 203 | throw std::runtime_error{ "Proxy server rejected the connection" }; 204 | } 205 | 206 | template 207 | asio::awaitable> 208 | perform_tls_handshake(ssl::context& ssl_ctx, Socket socket, std::string host) 209 | { 210 | auto ssl_stream = ssl::stream{ std::move(socket), ssl_ctx }; 211 | 212 | if(!SSL_set_tlsext_host_name(ssl_stream.native_handle(), host.c_str())) 213 | throw system_error{ static_cast(::ERR_get_error()), 214 | asio::error::get_ssl_category() }; 215 | 216 | co_await ssl_stream.async_handshake(ssl::stream_base::client); 217 | co_return ssl_stream; 218 | } 219 | } // namespace 220 | 221 | asio::awaitable 222 | connect( 223 | const operation_config& oc, 224 | ssl::context& ssl_ctx, 225 | http_proto::context& proto_ctx, 226 | any_stream& stream, 227 | urls::url url) 228 | { 229 | auto org_host = url.host(); 230 | auto executor = co_await asio::this_coro::executor; 231 | 232 | if(!oc.unix_socket_path.empty()) 233 | { 234 | auto socket = asio::local::stream_protocol::socket{ executor }; 235 | auto path = oc.unix_socket_path.string(); 236 | auto endpoint = asio::local::stream_protocol::endpoint{ path }; 237 | co_await socket.async_connect(endpoint); 238 | 239 | if(url.scheme_id() == urls::scheme::https) 240 | { 241 | stream = co_await perform_tls_handshake( 242 | ssl_ctx, std::move(socket), org_host); 243 | co_return; 244 | } 245 | stream = std::move(socket); 246 | co_return; 247 | } 248 | 249 | auto socket = asio::ip::tcp::socket{ executor }; 250 | 251 | if(oc.connect_to) 252 | oc.connect_to(url); 253 | 254 | if(oc.resolve_to) 255 | oc.resolve_to(url); 256 | 257 | if(!oc.proxy.empty()) 258 | { 259 | if(oc.proxy.scheme() == "http") 260 | { 261 | co_await connect_http_proxy(oc, proto_ctx, socket, url, oc.proxy); 262 | } 263 | else if(oc.proxy.scheme() == "socks5") 264 | { 265 | co_await connect_socks5_proxy(socket, url, oc.proxy); 266 | } 267 | else 268 | { 269 | throw std::runtime_error( 270 | "only HTTP and SOCKS5 proxies are supported"); 271 | } 272 | } 273 | else // no proxy 274 | { 275 | auto resolver = asio::ip::tcp::resolver{ executor }; 276 | auto rresults = 277 | co_await resolver.async_resolve(url.host(), effective_port(url)); 278 | 279 | co_await asio::async_connect( 280 | socket, 281 | rresults, 282 | [&](const error_code&, const asio::ip::tcp::endpoint& next) 283 | { 284 | if(oc.ipv4 && next.address().is_v6()) 285 | return false; 286 | 287 | if(oc.ipv6 && next.address().is_v4()) 288 | return false; 289 | 290 | return true; 291 | }); 292 | } 293 | 294 | if(oc.tcp_nodelay) 295 | socket.set_option(asio::ip::tcp::no_delay{ true }); 296 | 297 | if(oc.nokeepalive) 298 | socket.set_option(asio::ip::tcp::socket::keep_alive{ false }); 299 | 300 | if(url.scheme_id() == urls::scheme::https) 301 | { 302 | stream = co_await perform_tls_handshake( 303 | ssl_ctx, std::move(socket), org_host); 304 | co_return; 305 | } 306 | stream = std::move(socket); 307 | } 308 | -------------------------------------------------------------------------------- /example/client/burl/connect.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_CONNECT_HPP 11 | #define BURL_CONNECT_HPP 12 | 13 | #include "any_stream.hpp" 14 | #include "options.hpp" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace asio = boost::asio; 22 | namespace http_proto = boost::http_proto; 23 | namespace ssl = boost::asio::ssl; 24 | namespace urls = boost::urls; 25 | 26 | asio::awaitable 27 | connect( 28 | const operation_config& oc, 29 | ssl::context& ssl_ctx, 30 | http_proto::context& proto_ctx, 31 | any_stream& stream, 32 | urls::url url); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /example/client/burl/cookie.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "cookie.hpp" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace grammar = boost::urls::grammar; 19 | 20 | namespace 21 | { 22 | struct name_chars_t 23 | { 24 | constexpr bool 25 | operator()(char c) const noexcept 26 | { 27 | // clang-format off 28 | return 29 | c > 0x20 && c != 0x7F && 30 | c != '(' && c != ')' && c != '<' && c != '>' && c != '@' && 31 | c != ',' && c != ';' && c != ':' && c != '\\' && c != '"' && 32 | c != '/' && c != '[' && c != ']' && c != '?' && c != '=' && 33 | c != '{' && c != '}'; 34 | // clang-format on 35 | } 36 | }; 37 | 38 | constexpr auto name_chars = name_chars_t{}; 39 | 40 | struct value_chars_t 41 | { 42 | constexpr bool 43 | operator()(char c) const noexcept 44 | { 45 | // clang-format off 46 | return 47 | (c == 0x21 ) || 48 | (c >= 0x23 && c <= 0x2B) || 49 | (c >= 0x2D && c <= 0x3A) || 50 | (c >= 0x3C && c <= 0x5B) || 51 | (c >= 0x5D && c <= 0x7E); 52 | // clang-format on 53 | } 54 | }; 55 | 56 | constexpr auto value_chars = value_chars_t{}; 57 | 58 | constexpr auto attr_chars = 59 | urls::grammar::all_chars - urls::grammar::lut_chars("\x1F\x7f;"); 60 | 61 | bool 62 | domain_match( 63 | core::string_view r_domain, 64 | core::string_view c_domain, 65 | bool tailmatch) noexcept 66 | { 67 | if(!tailmatch) 68 | return r_domain == c_domain; 69 | 70 | if(c_domain.starts_with('.')) 71 | c_domain.remove_prefix(1); 72 | 73 | if(r_domain.ends_with(c_domain)) 74 | { 75 | if(r_domain.size() == c_domain.size()) 76 | return true; 77 | 78 | return r_domain[r_domain.size() - c_domain.size() - 1] == '.'; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | bool 85 | path_match(core::string_view r_path, core::string_view c_path) noexcept 86 | { 87 | if(r_path.empty()) 88 | return true; 89 | 90 | if(r_path.starts_with(c_path)) 91 | { 92 | if(r_path.size() == c_path.size()) 93 | return true; 94 | 95 | if(c_path.ends_with('/')) 96 | return true; 97 | 98 | return r_path[r_path.size() - c_path.size()] == '/'; 99 | } 100 | 101 | return false; 102 | } 103 | 104 | ch::system_clock::time_point 105 | parse_date(core::string_view sv) 106 | { 107 | // TODO: There are more date formats; we need a 108 | // better parsing method. 109 | auto tm = std::tm{}; 110 | auto ss = std::stringstream{ sv }; 111 | 112 | ss >> std::get_time( 113 | &tm, 114 | sv.contains('-') ? "%a, %d-%b-%Y %H:%M:%S GMT" 115 | : "%a, %d %b %Y %H:%M:%S GMT"); 116 | 117 | return ch::system_clock::from_time_t(std::mktime(&tm)); 118 | } 119 | } // namespace 120 | 121 | boost::system::result 122 | parse_cookie(core::string_view sv) 123 | { 124 | static constexpr auto cookie_parser = grammar::tuple_rule( 125 | grammar::token_rule(name_chars), 126 | grammar::squelch(grammar::delim_rule('=')), 127 | grammar::optional_rule(grammar::token_rule(value_chars)), 128 | grammar::range_rule( 129 | grammar::tuple_rule( 130 | grammar::squelch(grammar::delim_rule(';')), 131 | grammar::squelch( 132 | grammar::optional_rule(grammar::delim_rule(' '))), 133 | grammar::token_rule(attr_chars - grammar::lut_chars('=')), 134 | grammar::squelch( 135 | grammar::optional_rule(grammar::delim_rule('='))), 136 | grammar::optional_rule(grammar::token_rule(attr_chars))))); 137 | 138 | const auto parse_rs = grammar::parse(sv, cookie_parser); 139 | 140 | if(parse_rs.has_error()) 141 | return parse_rs.error(); 142 | 143 | auto rs = cookie{}; 144 | rs.name = std::get<0>(parse_rs.value()); 145 | rs.value = std::get<1>(parse_rs.value()); 146 | 147 | for(auto&& attr : std::get<2>(parse_rs.value())) 148 | { 149 | auto name = std::get<0>(attr); 150 | auto value = std::get<1>(attr); 151 | 152 | if(grammar::ci_is_equal(name, "Expires")) 153 | { 154 | if(!value) 155 | return grammar::error::invalid; 156 | 157 | rs.expires = parse_date(*value); 158 | } 159 | else if(grammar::ci_is_equal(name, "Max-Age")) 160 | { 161 | if(!value) 162 | return grammar::error::invalid; 163 | // Convert to expiry date 164 | // TODO: replace std::stoll 165 | rs.expires = 166 | ch::system_clock::now() + ch::seconds{ std::stoll(*value) }; 167 | } 168 | else if(grammar::ci_is_equal(name, "Domain")) 169 | { 170 | if(!value) 171 | return grammar::error::invalid; 172 | 173 | rs.domain = *value; 174 | } 175 | else if(grammar::ci_is_equal(name, "Path")) 176 | { 177 | if(!value) 178 | return grammar::error::invalid; 179 | rs.path = *value; 180 | } 181 | else if(grammar::ci_is_equal(name, "SameSite")) 182 | { 183 | if(grammar::ci_is_equal(value.value_or(""), "Strict")) 184 | rs.same_site = cookie::same_site_t::strict; 185 | else if(grammar::ci_is_equal(value.value_or(""), "Lax")) 186 | rs.same_site = cookie::same_site_t::lax; 187 | else if(grammar::ci_is_equal(value.value_or(""), "None")) 188 | rs.same_site = cookie::same_site_t::none; 189 | else 190 | return grammar::error::invalid; 191 | } 192 | else if(grammar::ci_is_equal(name, "Partitioned")) 193 | { 194 | rs.partitioned = true; 195 | } 196 | else if(grammar::ci_is_equal(name, "Secure")) 197 | { 198 | rs.secure = true; 199 | } 200 | else if(grammar::ci_is_equal(name, "HttpOnly")) 201 | { 202 | rs.http_only = true; 203 | } 204 | } 205 | 206 | // "__Secure-" prefix requirements 207 | if(core::string_view{ rs.name }.starts_with("__Secure-")) 208 | { 209 | if(!rs.secure) 210 | return grammar::error::invalid; 211 | } 212 | 213 | // "__Host-" prefix requirements 214 | if(core::string_view{ rs.name }.starts_with("__Host-")) 215 | { 216 | if(!rs.secure) 217 | return grammar::error::invalid; 218 | 219 | if(!rs.path || rs.path.value() != "/") 220 | return grammar::error::invalid; 221 | 222 | if(rs.domain.has_value()) 223 | return grammar::error::invalid; 224 | } 225 | 226 | return rs; 227 | } 228 | 229 | boost::system::result 230 | parse_netscape_cookie(core::string_view sv) 231 | { 232 | static constexpr auto field_chars = 233 | grammar::all_chars - grammar::lut_chars{ "\t" }; 234 | 235 | static constexpr auto netscape_parser = grammar::tuple_rule( 236 | grammar::optional_rule(grammar::literal_rule("#HttpOnly_")), 237 | grammar::token_rule(field_chars), 238 | grammar::squelch(grammar::delim_rule('\t')), 239 | grammar::variant_rule( 240 | grammar::literal_rule("FALSE"), grammar::literal_rule("TRUE")), 241 | grammar::squelch(grammar::delim_rule('\t')), 242 | grammar::token_rule(field_chars), 243 | grammar::squelch(grammar::delim_rule('\t')), 244 | grammar::variant_rule( 245 | grammar::literal_rule("FALSE"), grammar::literal_rule("TRUE")), 246 | grammar::squelch(grammar::delim_rule('\t')), 247 | grammar::unsigned_rule(), 248 | grammar::squelch(grammar::delim_rule('\t')), 249 | grammar::token_rule(field_chars), 250 | grammar::squelch(grammar::delim_rule('\t')), 251 | grammar::token_rule(field_chars)); 252 | 253 | const auto parse_rs = grammar::parse(sv, netscape_parser); 254 | 255 | if(parse_rs.has_error()) 256 | return parse_rs.error(); 257 | 258 | auto epoch_to_expiry = [](std::uint32_t epoch) 259 | -> boost::optional 260 | { 261 | if(epoch == 0) 262 | return boost::none; 263 | 264 | return ch::system_clock::from_time_t(static_cast(epoch)); 265 | }; 266 | 267 | auto rs = cookie{}; 268 | rs.http_only = std::get<0>(*parse_rs).has_value(); 269 | rs.domain = std::get<1>(*parse_rs); 270 | rs.tailmatch = std::get<2>(*parse_rs).index(); 271 | rs.path = std::get<3>(*parse_rs); 272 | rs.secure = std::get<4>(*parse_rs).index(); 273 | rs.expires = epoch_to_expiry(std::get<5>(*parse_rs)); 274 | rs.name = std::get<6>(*parse_rs); 275 | rs.value = std::get<7>(*parse_rs); 276 | return rs; 277 | } 278 | 279 | void 280 | cookie_jar::add(const urls::url_view& url, cookie c) 281 | { 282 | if(c.domain.has_value()) 283 | { 284 | c.tailmatch = true; 285 | // TODO: Verify with the current URL and Public Suffix List 286 | } 287 | else 288 | { 289 | c.domain.emplace(url.encoded_host()); 290 | } 291 | 292 | if(!c.path.has_value()) 293 | { 294 | c.path.emplace(); 295 | auto segs = url.encoded_segments(); 296 | auto end = std::prev(segs.end(), !segs.empty()); 297 | for(auto it = segs.begin(); it != end; ++it) 298 | { 299 | c.path->push_back('/'); 300 | c.path->append(it->begin(), it->end()); 301 | } 302 | if(c.path->empty()) 303 | c.path->push_back('/'); 304 | } 305 | 306 | if(c.secure && url.scheme_id() != urls::scheme::https) 307 | return; 308 | 309 | cookies_.erase( 310 | std::remove_if( 311 | cookies_.begin(), 312 | cookies_.end(), 313 | [&](const cookie& o) 314 | { 315 | return c.name == o.name && c.path == o.path && 316 | c.domain == o.domain; 317 | }), 318 | cookies_.end()); 319 | 320 | // Check expiry date last to allow servers to remove cookies 321 | if(c.expires.has_value() && c.expires.value() < ch::system_clock::now()) 322 | return; 323 | 324 | cookies_.push_back(std::move(c)); 325 | } 326 | 327 | std::string 328 | cookie_jar::make_field(const urls::url_view& url) 329 | { 330 | const auto r_domain = url.host(); 331 | const auto r_path = url.encoded_path(); 332 | const auto r_is_secure = url.scheme_id() == urls::scheme::https; 333 | const auto now = ch::system_clock::now(); 334 | 335 | auto rs = std::string{}; 336 | for(auto it = cookies_.begin(); it != cookies_.end();) 337 | { 338 | if(it->expires.has_value() && it->expires <= now) 339 | { 340 | it = cookies_.erase(it); 341 | continue; 342 | } 343 | 344 | if(domain_match(r_domain, it->domain.value(), it->tailmatch) && 345 | path_match(r_path, it->path.value()) && 346 | (it->secure ? r_is_secure : true)) 347 | { 348 | rs.append(it->name); 349 | rs.push_back('='); 350 | if(it->value.has_value()) 351 | rs.append(*it->value); 352 | rs.append("; "); 353 | } 354 | 355 | ++it; 356 | } 357 | return rs; 358 | } 359 | 360 | void 361 | cookie_jar::clear_session_cookies() 362 | { 363 | cookies_.erase( 364 | std::remove_if( 365 | cookies_.begin(), 366 | cookies_.end(), 367 | [](const cookie& c) { return !c.expires.has_value(); }), 368 | cookies_.end()); 369 | } 370 | 371 | std::ostream& 372 | operator<<(std::ostream& os, const cookie_jar& cj) 373 | { 374 | os << "# Netscape HTTP Cookie File\n\n"; 375 | 376 | for(const auto& c : cj.cookies_) 377 | { 378 | os << (c.http_only ? "#HttpOnly_" : ""); 379 | os << c.domain.value() << '\t'; 380 | os << (c.tailmatch ? "TRUE" : "FALSE") << '\t'; 381 | os << c.path.value() << '\t'; 382 | os << (c.secure ? "TRUE" : "FALSE") << '\t'; 383 | if(c.expires) 384 | os << ch::duration_cast( 385 | c.expires.value().time_since_epoch()) 386 | .count(); 387 | else 388 | os << '0'; 389 | os << '\t'; 390 | os << c.name << '\t'; 391 | os << c.value.value_or(""); 392 | os << '\n'; 393 | } 394 | return os; 395 | } 396 | 397 | std::istream& 398 | operator>>(std::istream& is, cookie_jar& cj) 399 | { 400 | for(std::string line; getline(is, line);) 401 | { 402 | if(line.empty()) 403 | continue; 404 | 405 | // skip comments 406 | if(line.starts_with('#') && !line.starts_with("#HttpOnly_")) 407 | continue; 408 | 409 | cj.cookies_.push_back(parse_netscape_cookie(line).value()); 410 | } 411 | return is; 412 | } 413 | -------------------------------------------------------------------------------- /example/client/burl/cookie.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_COOKIES_HPP 11 | #define BURL_COOKIES_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace ch = std::chrono; 21 | namespace core = boost::core; 22 | namespace urls = boost::urls; 23 | 24 | struct cookie 25 | { 26 | enum same_site_t 27 | { 28 | strict, 29 | lax, 30 | none 31 | }; 32 | 33 | std::string name; 34 | boost::optional value; 35 | boost::optional expires; 36 | boost::optional domain; 37 | boost::optional path; 38 | boost::optional same_site; 39 | bool partitioned = false; 40 | bool secure = false; 41 | bool http_only = false; 42 | bool tailmatch = false; 43 | }; 44 | 45 | boost::system::result 46 | parse_cookie(core::string_view sv); 47 | 48 | class cookie_jar 49 | { 50 | std::list cookies_; 51 | 52 | public: 53 | void 54 | add(const urls::url_view& url, cookie c); 55 | 56 | std::string 57 | make_field(const urls::url_view& url); 58 | 59 | void 60 | clear_session_cookies(); 61 | 62 | friend 63 | std::ostream& 64 | operator<<(std::ostream& os, const cookie_jar& cj); 65 | 66 | friend 67 | std::istream& 68 | operator>>(std::istream& is, cookie_jar& cj); 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /example/client/burl/error.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2025 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "error.hpp" 11 | 12 | namespace 13 | { 14 | const boost::system::error_category& 15 | error_category() 16 | { 17 | static const struct : boost::system::error_category 18 | { 19 | const char* 20 | name() const noexcept override 21 | { 22 | return "burl"; 23 | } 24 | 25 | std::string 26 | message(int ev) const override 27 | { 28 | switch(static_cast(ev)) 29 | { 30 | case error::binary_output_to_tty: 31 | return "binary output to tty"; 32 | default: 33 | return "Unknown burl error"; 34 | } 35 | } 36 | } category; 37 | 38 | return category; 39 | }; 40 | } // namespace 41 | 42 | std::error_code 43 | make_error_code(error e) 44 | { 45 | return { static_cast(e), error_category() }; 46 | } 47 | -------------------------------------------------------------------------------- /example/client/burl/error.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2025 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_ERROR_HPP 11 | #define BURL_ERROR_HPP 12 | 13 | #include 14 | 15 | enum class error 16 | { 17 | binary_output_to_tty = 1, 18 | }; 19 | 20 | namespace boost 21 | { 22 | namespace system 23 | { 24 | template<> 25 | struct is_error_code_enum : std::true_type 26 | { 27 | }; 28 | } // namespace system 29 | } // namespace boost 30 | 31 | std::error_code 32 | make_error_code(error e); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /example/client/burl/glob.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "glob.hpp" 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace urls = boost::urls; 19 | namespace grammar = urls::grammar; 20 | 21 | namespace 22 | { 23 | using return_t = boost::optional; 24 | 25 | auto 26 | make_range_gen( 27 | std::uint8_t width, 28 | std::uint64_t low, 29 | std::uint64_t high, 30 | std::uint64_t step) 31 | { 32 | return [width, low, high, step]() mutable -> return_t 33 | { 34 | if(low > high) 35 | return boost::none; 36 | 37 | auto rs = std::to_string(std::exchange(low, low + step)); 38 | if(width > rs.size()) 39 | rs.insert(0, width - rs.size(), '0'); 40 | return rs; 41 | }; 42 | }; 43 | 44 | auto 45 | make_char_gen(char low, char high, std::uint8_t step) 46 | { 47 | return [low, high, step]() mutable -> return_t 48 | { 49 | if(low > high) 50 | return boost::none; 51 | 52 | return std::string{ std::exchange(low, low + step) }; 53 | }; 54 | }; 55 | 56 | auto 57 | make_set_gen(std::vector items) 58 | { 59 | return [items = std::move(items), i = std::size_t{}]() mutable -> return_t 60 | { 61 | if(i == items.size()) 62 | return boost::none; 63 | 64 | return items[i++]; 65 | }; 66 | }; 67 | 68 | auto 69 | make_static_gen(std::string s) 70 | { 71 | return [s = return_t{ std::move(s) }]() mutable 72 | { 73 | return std::exchange(s, boost::none); 74 | }; 75 | }; 76 | 77 | using variant_t = std::variant< 78 | decltype(make_range_gen(0, 0, 0, 0)), 79 | decltype(make_char_gen(0, 0, 0)), 80 | decltype(make_set_gen({})), 81 | decltype(make_static_gen({}))>; 82 | 83 | constexpr bool 84 | holds_static_gen(const variant_t& v) noexcept 85 | { 86 | return std::holds_alternative(v); 87 | } 88 | } // namespace 89 | 90 | std::function()> 91 | make_glob_generator(core::string_view pattern) 92 | { 93 | static constexpr auto static_rule = 94 | grammar::token_rule(grammar::all_chars - grammar::lut_chars("{[")); 95 | 96 | static constexpr auto num_range_rule = grammar::tuple_rule( 97 | grammar::squelch(grammar::delim_rule('[')), 98 | grammar::token_rule(grammar::digit_chars), 99 | grammar::squelch(grammar::delim_rule('-')), 100 | grammar::unsigned_rule(), 101 | grammar::optional_rule( 102 | grammar::tuple_rule( 103 | grammar::squelch(grammar::delim_rule(':')), 104 | grammar::unsigned_rule())), 105 | grammar::squelch(grammar::delim_rule(']'))); 106 | 107 | static constexpr auto char_range_rule = grammar::tuple_rule( 108 | grammar::squelch(grammar::delim_rule('[')), 109 | grammar::delim_rule(grammar::alpha_chars), 110 | grammar::squelch(grammar::delim_rule('-')), 111 | grammar::delim_rule(grammar::alpha_chars), 112 | grammar::optional_rule( 113 | grammar::tuple_rule( 114 | grammar::squelch(grammar::delim_rule(':')), 115 | grammar::unsigned_rule())), 116 | grammar::squelch(grammar::delim_rule(']'))); 117 | 118 | static constexpr auto set_rule = grammar::tuple_rule( 119 | grammar::squelch(grammar::delim_rule('{')), 120 | grammar::range_rule( 121 | grammar::tuple_rule( 122 | grammar::token_rule( 123 | grammar::all_chars - grammar::lut_chars(",}")), 124 | grammar::squelch( 125 | grammar::optional_rule(grammar::delim_rule(','))))), 126 | grammar::squelch(grammar::delim_rule('}'))); 127 | 128 | static constexpr auto glob_rule = grammar::range_rule( 129 | grammar::variant_rule( 130 | static_rule, num_range_rule, char_range_rule, set_rule)); 131 | 132 | const auto parse_rs = parse(pattern, glob_rule); 133 | 134 | if(parse_rs.has_error()) 135 | throw std::runtime_error{ "Bad URL pattern" }; 136 | 137 | std::vector gs; 138 | 139 | for(auto v : parse_rs.value()) 140 | { 141 | switch(v.index()) 142 | { 143 | case 0: 144 | { 145 | gs.push_back(make_static_gen(get<0>(v))); 146 | break; 147 | } 148 | case 1: 149 | { 150 | auto [low, high, step] = get<1>(v); 151 | gs.push_back(make_range_gen( 152 | static_cast(low.size()), 153 | std::stoull(low), 154 | high, 155 | step.value_or(std::uint64_t{ 1 }))); 156 | break; 157 | } 158 | case 2: 159 | { 160 | auto [low, high, step] = get<2>(v); 161 | gs.push_back(make_char_gen( 162 | low.at(0), high.at(0), step.value_or(std::uint8_t{ 1 }))); 163 | break; 164 | } 165 | case 3: 166 | { 167 | auto items = std::vector{}; 168 | for(auto s : get<3>(v)) 169 | items.push_back(s); 170 | gs.push_back(make_set_gen(std::move(items))); 171 | break; 172 | } 173 | } 174 | } 175 | 176 | using stack_t = std::vector>; 177 | using tokens_t = std::vector; 178 | return [stack = stack_t{ { {}, std::move(gs) } }, 179 | tokens = tokens_t{}]() mutable 180 | { 181 | while(!stack.empty()) 182 | { 183 | auto& [prefix, gs] = stack.back(); 184 | 185 | if(auto o = std::visit([](auto& g) { return g(); }, gs.front())) 186 | { 187 | if(gs.size() == 1) 188 | { 189 | auto tmp = tokens; 190 | if(!holds_static_gen(gs.front())) 191 | tmp.push_back(o.value()); 192 | return boost::optional( 193 | { prefix + o.value(), std::move(tmp) }); 194 | } 195 | 196 | if(!holds_static_gen(gs.front())) 197 | tokens.push_back(o.value()); 198 | 199 | stack.emplace_back( 200 | prefix + o.value(), 201 | decltype(gs){ std::next(gs.begin()), gs.end() }); 202 | } 203 | else 204 | { 205 | stack.pop_back(); 206 | if(!stack.empty() && 207 | !holds_static_gen(stack.back().second.front())) 208 | tokens.pop_back(); 209 | } 210 | } 211 | return boost::optional{}; 212 | }; 213 | } 214 | 215 | std::string 216 | glob_result::interpolate(core::string_view format) 217 | { 218 | static constexpr auto rule = grammar::range_rule( 219 | grammar::variant_rule( 220 | grammar::token_rule(grammar::all_chars - grammar::lut_chars("#")), 221 | grammar::tuple_rule( 222 | grammar::squelch(grammar::delim_rule('#')), 223 | grammar::token_rule(grammar::digit_chars)), 224 | grammar::tuple_rule(grammar::squelch(grammar::delim_rule('#'))))); 225 | 226 | const auto parse_rs = parse(format, rule); 227 | 228 | std::string rs; 229 | for(auto v : parse_rs.value()) 230 | { 231 | switch(v.index()) 232 | { 233 | case 0: 234 | { 235 | rs.append(get<0>(v)); 236 | break; 237 | } 238 | case 1: 239 | { 240 | auto index = std::stoul(get<1>(v)) - 1; 241 | if(index < tokens.size()) 242 | { 243 | rs.append(tokens[index]); 244 | } 245 | else 246 | { 247 | rs.push_back('#'); 248 | rs.append(get<1>(v)); 249 | } 250 | break; 251 | } 252 | case 2: 253 | { 254 | rs.push_back('#'); 255 | break; 256 | } 257 | } 258 | } 259 | return rs; 260 | } 261 | -------------------------------------------------------------------------------- /example/client/burl/glob.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_GLOB_HPP 11 | #define BURL_GLOB_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | namespace core = boost::core; 20 | 21 | struct glob_result 22 | { 23 | std::string result; 24 | std::vector tokens; 25 | 26 | std::string 27 | interpolate(core::string_view format); 28 | }; 29 | 30 | std::function()> 31 | make_glob_generator(core::string_view pattern); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /example/client/burl/message.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "message.hpp" 11 | #include "mime_type.hpp" 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | namespace fs = std::filesystem; 20 | using system_error = boost::system::system_error; 21 | 22 | string_body::string_body(std::string body, std::string content_type) 23 | : body_{ std::move(body) } 24 | , content_type_{ std::move(content_type) } 25 | { 26 | } 27 | 28 | http_proto::method 29 | string_body::method() const 30 | { 31 | return http_proto::method::post; 32 | } 33 | 34 | core::string_view 35 | string_body::content_type() const noexcept 36 | { 37 | return content_type_; 38 | } 39 | 40 | std::size_t 41 | string_body::content_length() const noexcept 42 | { 43 | return body_.size(); 44 | } 45 | 46 | buffers::const_buffer 47 | string_body::body() const noexcept 48 | { 49 | return { body_.data(), body_.size() }; 50 | } 51 | 52 | // ----------------------------------------------------------------------------- 53 | 54 | file_body::file_body(std::string path) 55 | : path_{ std::move(path) } 56 | { 57 | } 58 | 59 | http_proto::method 60 | file_body::method() const 61 | { 62 | return http_proto::method::put; 63 | } 64 | 65 | core::string_view 66 | file_body::content_type() const noexcept 67 | { 68 | return mime_type(path_); 69 | } 70 | 71 | std::uint64_t 72 | file_body::content_length() const 73 | { 74 | return fs::file_size(path_); 75 | } 76 | 77 | http_proto::file_body 78 | file_body::body() const 79 | { 80 | http_proto::file file; 81 | error_code ec; 82 | file.open(path_.c_str(), http_proto::file_mode::read, ec); 83 | if(ec) 84 | throw system_error{ ec }; 85 | 86 | return http_proto::file_body{ std::move(file), content_length() }; 87 | } 88 | 89 | // ----------------------------------------------------------------------------- 90 | 91 | boost::http_proto::source::results 92 | stdin_body::source::on_read(buffers::mutable_buffer mb) 93 | { 94 | std::cin.read(static_cast(mb.data()), mb.size()); 95 | 96 | return { .ec = {}, 97 | .bytes = static_cast(std::cin.gcount()), 98 | .finished = std::cin.eof() }; 99 | } 100 | 101 | http_proto::method 102 | stdin_body::method() const 103 | { 104 | return http_proto::method::put; 105 | } 106 | 107 | core::string_view 108 | stdin_body::content_type() const noexcept 109 | { 110 | return "application/octet-stream"; 111 | } 112 | 113 | boost::optional 114 | stdin_body::content_length() const 115 | { 116 | return boost::none; 117 | } 118 | 119 | stdin_body::source 120 | stdin_body::body() const 121 | { 122 | return {}; 123 | } 124 | 125 | // ----------------------------------------------------------------------------- 126 | 127 | void 128 | message::set_headers(http_proto::request& request) const 129 | { 130 | std::visit( 131 | [&](auto& f) 132 | { 133 | using field = http_proto::field; 134 | if constexpr(!std::is_same_v) 135 | { 136 | request.set_method(f.method()); 137 | request.set(field::content_type, f.content_type()); 138 | 139 | boost::optional content_length = 140 | f.content_length(); 141 | if(content_length.has_value()) 142 | { 143 | request.set_content_length(content_length.value()); 144 | if(content_length.value() >= 1024 * 1024 && 145 | request.version() == http_proto::version::http_1_1) 146 | request.set(field::expect, "100-continue"); 147 | } 148 | else 149 | { 150 | request.set_chunked(true); 151 | request.set(field::expect, "100-continue"); 152 | } 153 | } 154 | }, 155 | body_); 156 | } 157 | 158 | void 159 | message::start_serializer( 160 | http_proto::serializer& serializer, 161 | http_proto::request& request) const 162 | { 163 | std::visit( 164 | [&](auto& f) 165 | { 166 | if constexpr(!std::is_same_v) 167 | { 168 | serializer.start>( 169 | request, f.body()); 170 | } 171 | else 172 | { 173 | serializer.start(request); 174 | } 175 | }, 176 | body_); 177 | } 178 | -------------------------------------------------------------------------------- /example/client/burl/message.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_MESSAGE_HPP 11 | #define BURL_MESSAGE_HPP 12 | 13 | #include "multipart_form.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | namespace core = boost::core; 22 | 23 | class string_body 24 | { 25 | std::string body_; 26 | std::string content_type_; 27 | 28 | public: 29 | string_body(std::string body, std::string content_type); 30 | 31 | http_proto::method 32 | method() const; 33 | 34 | core::string_view 35 | content_type() const noexcept; 36 | 37 | std::size_t 38 | content_length() const noexcept; 39 | 40 | buffers::const_buffer 41 | body() const noexcept; 42 | }; 43 | 44 | class file_body 45 | { 46 | std::string path_; 47 | 48 | public: 49 | file_body(std::string path); 50 | 51 | http_proto::method 52 | method() const; 53 | 54 | core::string_view 55 | content_type() const noexcept; 56 | 57 | std::uint64_t 58 | content_length() const; 59 | 60 | http_proto::file_body 61 | body() const; 62 | }; 63 | 64 | class stdin_body 65 | { 66 | public: 67 | class source : public http_proto::source 68 | { 69 | public: 70 | results 71 | on_read(buffers::mutable_buffer mb) override; 72 | }; 73 | 74 | http_proto::method 75 | method() const; 76 | 77 | core::string_view 78 | content_type() const noexcept; 79 | 80 | boost::optional 81 | content_length() const; 82 | 83 | source 84 | body() const; 85 | }; 86 | 87 | class message 88 | { 89 | std::variant< 90 | std::monostate, 91 | string_body, 92 | multipart_form, 93 | file_body, 94 | stdin_body> 95 | body_; 96 | 97 | public: 98 | message() = default; 99 | 100 | template< 101 | class Body, 102 | std::enable_if_t, message>, int> = 0> 103 | message(Body&& body) 104 | : body_{ std::move(body) } 105 | { 106 | } 107 | 108 | void 109 | set_headers(http_proto::request& request) const; 110 | 111 | void 112 | start_serializer( 113 | http_proto::serializer& serializer, 114 | http_proto::request& request) const; 115 | }; 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /example/client/burl/mime_type.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "mime_type.hpp" 11 | 12 | #include 13 | 14 | namespace grammar = boost::urls::grammar; 15 | 16 | core::string_view 17 | mime_type(core::string_view path) noexcept 18 | { 19 | const auto ext = [&path] 20 | { 21 | const auto pos = path.rfind("."); 22 | if(pos == core::string_view::npos) 23 | return core::string_view{}; 24 | return path.substr(pos); 25 | }(); 26 | 27 | // clang-format off 28 | if(grammar::ci_is_equal(ext, ".gif")) return "image/gif"; 29 | if(grammar::ci_is_equal(ext, ".jpg")) return "image/jpeg"; 30 | if(grammar::ci_is_equal(ext, ".jpeg")) return "image/jpeg"; 31 | if(grammar::ci_is_equal(ext, ".png")) return "image/png"; 32 | if(grammar::ci_is_equal(ext, ".svg")) return "image/svg+xml"; 33 | if(grammar::ci_is_equal(ext, ".txt")) return "text/plain"; 34 | if(grammar::ci_is_equal(ext, ".htm")) return "text/html"; 35 | if(grammar::ci_is_equal(ext, ".html")) return "text/html"; 36 | if(grammar::ci_is_equal(ext, ".pdf")) return "application/pdf"; 37 | if(grammar::ci_is_equal(ext, ".xml")) return "application/xml"; 38 | // clang-format on 39 | 40 | return "application/octet-stream"; 41 | } 42 | -------------------------------------------------------------------------------- /example/client/burl/mime_type.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_MIME_TYPE_HPP 11 | #define BURL_MIME_TYPE_HPP 12 | 13 | #include 14 | 15 | namespace core = boost::core; 16 | 17 | core::string_view 18 | mime_type(core::string_view path) noexcept; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /example/client/burl/multipart_form.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "multipart_form.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | namespace core = boost::core; 21 | namespace fs = std::filesystem; 22 | using system_error = boost::system::system_error; 23 | 24 | namespace 25 | { 26 | std::array 27 | generate_boundary() 28 | { 29 | std::array rs; 30 | constexpr static char chars[] = 31 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 32 | static std::random_device rd; 33 | std::uniform_int_distribution dist{ 0, sizeof(chars) - 2 }; 34 | std::fill(rs.begin(), rs.end(), '-'); 35 | std::generate( 36 | rs.begin() + 2 + 24, rs.end() - 2, [&] { return chars[dist(rd)]; }); 37 | return rs; 38 | } 39 | 40 | std::string 41 | serialize_headers(std::vector& headers) 42 | { 43 | std::string rs; 44 | for(const auto& h : headers) 45 | { 46 | rs.append("\r\n"); 47 | rs.append(h); 48 | } 49 | return rs; 50 | } 51 | 52 | core::string_view content_disposition_ = 53 | "\r\nContent-Disposition: form-data; name=\""; 54 | core::string_view filename_ = "; filename=\""; 55 | core::string_view content_type_ = "\r\nContent-Type: "; 56 | } // namespace 57 | 58 | // ----------------------------------------------------------------------------- 59 | 60 | multipart_form::multipart_form() 61 | : storage_{ generate_boundary() } 62 | { 63 | } 64 | 65 | void 66 | multipart_form::append( 67 | bool is_file, 68 | std::string name, 69 | std::string value, 70 | boost::optional filename, 71 | boost::optional content_type, 72 | std::vector headers) 73 | { 74 | auto size = is_file ? fs::file_size(value) : value.size(); 75 | 76 | parts_.push_back( 77 | { is_file, 78 | std::move(name), 79 | std::move(value), 80 | size, 81 | std::move(filename), 82 | std::move(content_type), 83 | serialize_headers(headers) }); 84 | } 85 | 86 | http_proto::method 87 | multipart_form::method() const 88 | { 89 | return http_proto::method::post; 90 | } 91 | 92 | std::string 93 | multipart_form::content_type() const 94 | { 95 | std::string res = "multipart/form-data; boundary="; 96 | // append boundary 97 | res.append(storage_.begin() + 2, storage_.end() - 2); 98 | return res; 99 | } 100 | 101 | std::uint64_t 102 | multipart_form::content_length() const noexcept 103 | { 104 | auto rs = std::uint64_t{}; 105 | for(const auto& part : parts_) 106 | { 107 | rs += storage_.size() - 2; // --boundary 108 | rs += content_disposition_.size(); 109 | rs += part.name.size(); 110 | rs += 1; // closing double quote 111 | 112 | if(part.content_type) 113 | { 114 | rs += content_type_.size(); 115 | rs += part.content_type->size(); 116 | } 117 | 118 | if(part.filename) 119 | { 120 | rs += filename_.size(); 121 | rs += part.filename->size(); 122 | rs += 1; // closing double quote 123 | } 124 | 125 | rs += part.headers.size(); 126 | 127 | rs += 4; // after headers 128 | rs += part.size; 129 | rs += 2; // after content 130 | } 131 | rs += storage_.size(); // --boundary-- 132 | rs += 2; // 133 | return rs; 134 | } 135 | 136 | multipart_form::source 137 | multipart_form::body() const 138 | { 139 | return source{ this }; 140 | } 141 | 142 | // ----------------------------------------------------------------------------- 143 | 144 | multipart_form::source::source(const multipart_form* form) noexcept 145 | : form_{ form } 146 | { 147 | } 148 | 149 | multipart_form::source::results 150 | multipart_form::source::on_read(buffers::mutable_buffer mb) 151 | { 152 | auto rs = results{}; 153 | 154 | auto copy = [&](core::string_view sv) 155 | { 156 | auto copied = buffers::copy( 157 | mb, 158 | buffers::sans_prefix( 159 | buffers::const_buffer{ sv.data(), sv.size() }, 160 | static_cast(skip_))); 161 | 162 | mb = buffers::sans_prefix(mb, copied); 163 | rs.bytes += copied; 164 | skip_ += copied; 165 | 166 | if(skip_ != sv.size()) 167 | return false; 168 | 169 | skip_ = 0; 170 | return true; 171 | }; 172 | 173 | auto read = [&](const std::string& path, uint64_t size) 174 | { 175 | http_proto::file file; 176 | 177 | file.open(path.c_str(), http_proto::file_mode::read, rs.ec); 178 | if(rs.ec) 179 | return false; 180 | 181 | file.seek(skip_, rs.ec); 182 | if(rs.ec) 183 | return false; 184 | 185 | auto read = file.read( 186 | mb.data(), 187 | (std::min)(static_cast(mb.size()), size), 188 | rs.ec); 189 | if(rs.ec) 190 | return false; 191 | 192 | mb = buffers::sans_prefix(mb, read); 193 | rs.bytes += read; 194 | skip_ += read; 195 | 196 | if(skip_ != size) 197 | return false; 198 | 199 | skip_ = 0; 200 | return true; 201 | }; 202 | 203 | while(it_ != form_->parts_.end()) 204 | { 205 | switch(step_) 206 | { 207 | case 0: 208 | // --boundary 209 | if(!copy({ form_->storage_.data(), form_->storage_.size() - 2 })) 210 | return rs; 211 | step_ = 1; 212 | BOOST_FALLTHROUGH; 213 | case 1: 214 | if(!copy(content_disposition_)) 215 | return rs; 216 | step_ = 2; 217 | BOOST_FALLTHROUGH; 218 | case 2: 219 | if(!copy(it_->name)) 220 | return rs; 221 | step_ = 3; 222 | BOOST_FALLTHROUGH; 223 | case 3: 224 | if(!copy("\"")) 225 | return rs; 226 | step_ = 4; 227 | BOOST_FALLTHROUGH; 228 | case 4: 229 | if(!it_->filename) 230 | goto content_type; 231 | if(!copy(filename_)) 232 | return rs; 233 | step_ = 5; 234 | BOOST_FALLTHROUGH; 235 | case 5: 236 | if(!copy(it_->filename.value())) 237 | return rs; 238 | step_ = 6; 239 | BOOST_FALLTHROUGH; 240 | case 6: 241 | if(!copy("\"")) 242 | return rs; 243 | step_ = 7; 244 | BOOST_FALLTHROUGH; 245 | case 7: 246 | content_type: 247 | if(!it_->content_type) 248 | goto headers; 249 | if(!copy(content_type_)) 250 | return rs; 251 | step_ = 8; 252 | BOOST_FALLTHROUGH; 253 | case 8: 254 | if(!copy(it_->content_type.value())) 255 | return rs; 256 | step_ = 9; 257 | BOOST_FALLTHROUGH; 258 | case 9: 259 | headers: 260 | if(!copy(it_->headers)) 261 | return rs; 262 | step_ = 10; 263 | BOOST_FALLTHROUGH; 264 | case 10: 265 | if(!copy("\r\n\r\n")) 266 | return rs; 267 | step_ = 11; 268 | BOOST_FALLTHROUGH; 269 | case 11: 270 | if(it_->is_file) 271 | { 272 | if(!read(it_->value, it_->size)) 273 | return rs; 274 | } 275 | else 276 | { 277 | if(!copy(it_->value)) 278 | return rs; 279 | } 280 | step_ = 12; 281 | BOOST_FALLTHROUGH; 282 | case 12: 283 | if(!copy("\r\n")) 284 | return rs; 285 | ++it_; 286 | step_ = 0; 287 | } 288 | } 289 | 290 | switch(step_) 291 | { 292 | case 0: 293 | // --boundary-- 294 | if(!copy({ form_->storage_.data(), form_->storage_.size() })) 295 | return rs; 296 | step_ = 1; 297 | BOOST_FALLTHROUGH; 298 | case 1: 299 | if(!copy("\r\n")) 300 | return rs; 301 | } 302 | 303 | rs.finished = true; 304 | return rs; 305 | } 306 | -------------------------------------------------------------------------------- /example/client/burl/multipart_form.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_MULTIPART_FORM_HPP 11 | #define BURL_MULTIPART_FORM_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | namespace buffers = boost::buffers; 24 | namespace http_proto = boost::http_proto; 25 | using error_code = boost::system::error_code; 26 | 27 | class multipart_form 28 | { 29 | struct part 30 | { 31 | bool is_file = false; 32 | std::string name; 33 | std::string value; 34 | std::uint64_t size; 35 | boost::optional filename; 36 | boost::optional content_type; 37 | std::string headers; 38 | }; 39 | 40 | // boundary with extra "--" prefix and postfix. 41 | std::array storage_; 42 | std::vector parts_; 43 | 44 | public: 45 | class source; 46 | 47 | multipart_form(); 48 | 49 | void 50 | append( 51 | bool is_file, 52 | std::string name, 53 | std::string value, 54 | boost::optional filename = {}, 55 | boost::optional content_type = {}, 56 | std::vector headers = {}); 57 | 58 | http_proto::method 59 | method() const; 60 | 61 | std::string 62 | content_type() const; 63 | 64 | std::uint64_t 65 | content_length() const noexcept; 66 | 67 | source 68 | body() const; 69 | }; 70 | 71 | class multipart_form::source : public http_proto::source 72 | { 73 | const multipart_form* form_; 74 | std::vector::const_iterator it_{ form_->parts_.begin() }; 75 | int step_ = 0; 76 | std::uint64_t skip_ = 0; 77 | 78 | public: 79 | explicit source(const multipart_form* form) noexcept; 80 | 81 | results 82 | on_read(buffers::mutable_buffer mb) override; 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /example/client/burl/options.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_OPTIONS_HPP 11 | #define BURL_OPTIONS_HPP 12 | 13 | #include "message.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace asio = boost::asio; 27 | namespace ch = std::chrono; 28 | namespace fs = std::filesystem; 29 | namespace http_proto = boost::http_proto; 30 | namespace urls = boost::urls; 31 | 32 | struct operation_config 33 | { 34 | using duration_type = ch::steady_clock::duration; 35 | 36 | duration_type timeout = duration_type::max(); 37 | duration_type expect100timeout = ch::seconds{ 1 }; 38 | duration_type connect_timeout = duration_type::max(); 39 | boost::optional retry_delay; 40 | boost::optional retry_maxtime; 41 | bool disallow_username_in_url = false; 42 | boost::optional recvpersecond; 43 | boost::optional sendpersecond; 44 | bool encoding = false; 45 | bool create_dirs = false; 46 | std::uint64_t maxredirs = 50; 47 | std::uint64_t max_filesize = std::numeric_limits::max(); 48 | bool tcp_nodelay = true; 49 | std::uint64_t req_retry = 0; 50 | std::uint16_t parallel_max = 1; 51 | bool retry_connrefused = false; 52 | bool retry_all_errors = false; 53 | bool nokeepalive = false; 54 | bool post301 = false; 55 | bool post302 = false; 56 | bool post303 = false; 57 | std::set proto_redir{ urls::scheme::http, 58 | urls::scheme::https }; 59 | fs::path unix_socket_path; 60 | std::function connect_to; 61 | std::function resolve_to; 62 | bool http10 = false; 63 | bool ipv4 = false; 64 | bool ipv6 = false; 65 | boost::optional useragent; 66 | boost::optional userpwd; 67 | bool enable_cookies = false; 68 | std::vector cookies; 69 | std::vector cookiefiles; 70 | fs::path cookiejar; 71 | boost::optional resume_from; 72 | bool resume_from_current = false; 73 | fs::path headerfile; 74 | urls::url referer; 75 | bool autoreferer = false; 76 | bool failonerror = false; 77 | bool failearly = false; 78 | bool failwithbody = false; 79 | bool rm_partial = false; 80 | bool use_httpget = false; 81 | boost::optional request_target; 82 | http_proto::fields headers; 83 | std::vector omitheaders; 84 | bool show_headers = false; 85 | bool cookiesession = false; 86 | bool no_body = false; 87 | bool content_disposition = false; 88 | bool unrestricted_auth = false; 89 | bool followlocation = false; 90 | bool nobuffer = false; 91 | bool globoff = false; 92 | bool noprogress = false; 93 | bool skip_existing = false; 94 | bool terminal_binary_ok = false; 95 | fs::path output_dir; 96 | boost::optional range; 97 | urls::url proxy; 98 | boost::optional customrequest; 99 | std::string query; 100 | message msg; 101 | }; 102 | 103 | struct request_opt 104 | { 105 | std::string url; 106 | fs::path output; 107 | fs::path input; 108 | bool remotename = false; 109 | }; 110 | 111 | struct parse_args_result 112 | { 113 | operation_config oc; 114 | asio::ssl::context ssl_ctx; 115 | std::function()> ropt_gen; 116 | }; 117 | 118 | parse_args_result 119 | parse_args(int argc, char* argv[]); 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /example/client/burl/progress_meter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "progress_meter.hpp" 11 | 12 | #include 13 | 14 | progress_meter::progress_meter(boost::optional total) 15 | : total_{ total } 16 | { 17 | } 18 | 19 | void 20 | progress_meter::update(std::uint64_t bytes) noexcept 21 | { 22 | auto now = ch::steady_clock::now(); 23 | auto elapsed = std::min( 24 | ch::duration_cast(now - last_update_), 25 | ch::milliseconds{ 1250 }); 26 | auto shift = static_cast((elapsed / 250).count()); 27 | 28 | if(shift != 0) 29 | { 30 | last_update_ = now; 31 | 32 | std::move(window_.begin() + shift, window_.end(), window_.begin()); 33 | std::fill(window_.end() - shift, window_.end(), 0); 34 | } 35 | 36 | transfered_ += bytes; 37 | window_.back() += bytes; 38 | } 39 | 40 | std::uint64_t 41 | progress_meter::cur_rate() const 42 | { 43 | auto now = ch::steady_clock::now(); 44 | 45 | if(now - init_ < ch::seconds{ 1 }) 46 | now = last_update_ + ch::milliseconds{ 250 }; 47 | 48 | auto elapsed = std::min( 49 | ch::duration_cast(now - last_update_), 50 | ch::milliseconds{ 1250 }); 51 | auto offset = static_cast((elapsed / 250).count()); 52 | 53 | return std::accumulate( 54 | window_.begin() + offset, 55 | offset ? window_.end() : std::prev(window_.end()), 56 | std::uint64_t{ 0 }); 57 | } 58 | 59 | std::uint64_t 60 | progress_meter::avg_rate() const 61 | { 62 | auto elapsed = 63 | ch::duration_cast(ch::steady_clock::now() - init_).count(); 64 | 65 | if(elapsed == 0) 66 | return 0; 67 | 68 | return transfered_ / static_cast(elapsed); 69 | } 70 | 71 | boost::optional 72 | progress_meter::total() const 73 | { 74 | return total_; 75 | } 76 | 77 | std::uint64_t 78 | progress_meter::transfered() const 79 | { 80 | return transfered_; 81 | } 82 | 83 | boost::optional 84 | progress_meter::remaining() const 85 | { 86 | if(total_ && total_.value() >= transfered_) 87 | return total_.value() - transfered_; 88 | 89 | return boost::none; 90 | } 91 | 92 | boost::optional 93 | progress_meter::pct() const 94 | { 95 | if(total_) 96 | return static_cast(transfered_ * 100 / total_.value()); 97 | 98 | return boost::none; 99 | } 100 | 101 | boost::optional> 102 | progress_meter::eta() const 103 | { 104 | auto re = remaining(); 105 | if(!re) 106 | return boost::none; 107 | 108 | auto rate = cur_rate(); 109 | 110 | if(rate == 0) 111 | rate = avg_rate(); 112 | 113 | if(rate == 0) 114 | return boost::none; 115 | 116 | return ch::hh_mm_ss(ch::seconds{ re.value() / rate }); 117 | } 118 | -------------------------------------------------------------------------------- /example/client/burl/progress_meter.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_PROGRESS_METER_HPP 11 | #define BURL_PROGRESS_METER_HPP 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace ch = std::chrono; 21 | 22 | class progress_meter 23 | { 24 | boost::optional total_; 25 | std::uint64_t transfered_ = {}; 26 | std::array window_ = {}; 27 | ch::steady_clock::time_point init_ = ch::steady_clock::now(); 28 | ch::steady_clock::time_point last_update_ = ch::steady_clock::now(); 29 | 30 | public: 31 | progress_meter(boost::optional total); 32 | 33 | void 34 | update(std::uint64_t bytes) noexcept; 35 | 36 | std::uint64_t 37 | cur_rate() const; 38 | 39 | std::uint64_t 40 | avg_rate() const; 41 | 42 | boost::optional 43 | total() const; 44 | 45 | std::uint64_t 46 | transfered() const; 47 | 48 | boost::optional 49 | remaining() const; 50 | 51 | boost::optional 52 | pct() const; 53 | 54 | boost::optional> 55 | eta() const; 56 | }; 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /example/client/burl/request.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_REQUEST_HPP 11 | #define BURL_REQUEST_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | namespace asio = boost::asio; 27 | namespace ch = std::chrono; 28 | namespace http_io = boost::http_io; 29 | namespace http_proto = boost::http_proto; 30 | using error_code = boost::system::error_code; 31 | 32 | template 33 | class async_request_op 34 | { 35 | AsyncReadStream& stream_; 36 | http_proto::serializer& serializer_; 37 | http_proto::response_parser& parser_; 38 | ch::steady_clock::duration exp100_timeout_; 39 | asio::coroutine c; 40 | 41 | struct exp100 42 | { 43 | enum 44 | { 45 | awaiting, 46 | received, 47 | cancelled 48 | } state; 49 | asio::steady_timer timer; 50 | }; 51 | std::unique_ptr exp100_; 52 | 53 | public: 54 | async_request_op( 55 | AsyncReadStream& stream, 56 | http_proto::serializer& serializer, 57 | http_proto::response_parser& parser, 58 | ch::steady_clock::duration exp100_timeout) 59 | : stream_{ stream } 60 | , serializer_{ serializer } 61 | , parser_{ parser } 62 | , exp100_timeout_{ exp100_timeout } 63 | { 64 | } 65 | 66 | template 67 | void 68 | operator()(Self&& self, error_code ec = {}, std::size_t = {}) 69 | { 70 | using asio::deferred; 71 | 72 | BOOST_ASIO_CORO_REENTER(c) 73 | { 74 | self.reset_cancellation_state( 75 | asio::enable_total_cancellation{}); 76 | 77 | BOOST_ASIO_CORO_YIELD 78 | http_io::async_write(stream_, serializer_, std::move(self)); 79 | 80 | if(!ec) 81 | { 82 | BOOST_ASIO_CORO_YIELD 83 | http_io::async_read_header(stream_, parser_, std::move(self)); 84 | return self.complete(ec); 85 | } 86 | 87 | if(ec != http_proto::error::expect_100_continue) 88 | return self.complete(ec); 89 | 90 | // TODO: use associated allocator 91 | exp100_.reset(new exp100( 92 | exp100::awaiting, 93 | asio::steady_timer{ stream_.get_executor() })); 94 | exp100_->timer.expires_after(exp100_timeout_); 95 | 96 | BOOST_ASIO_CORO_YIELD 97 | asio::experimental::make_parallel_group( 98 | exp100_->timer.async_wait() | 99 | deferred( 100 | [&stream = stream_, 101 | &serializer = serializer_, 102 | exp100 = exp100_.get()](error_code) 103 | { 104 | return deferred 105 | .when(exp100->state != exp100::cancelled) 106 | .then(http_io::async_write(stream, serializer)) 107 | .otherwise(deferred.values( 108 | error_code{}, std::size_t{ 0 })); 109 | }), 110 | http_io::async_read_header(stream_, parser_) | 111 | deferred( 112 | [&stream = stream_, 113 | &parser = parser_, 114 | exp100 = exp100_.get()](error_code ec, std::size_t n) 115 | { 116 | exp100->timer.cancel(); 117 | if(ec || 118 | parser.get().status() != 119 | http_proto::status::continue_) 120 | { 121 | exp100->state = exp100::cancelled; 122 | } 123 | else 124 | { 125 | exp100->state = exp100::received; 126 | parser.start(); 127 | } 128 | return deferred 129 | .when(exp100->state == exp100::received) 130 | .then( 131 | http_io::async_read_header(stream, parser)) 132 | .otherwise(deferred.values(ec, n)); 133 | })) 134 | .async_wait( 135 | asio::experimental::wait_for_all(), std::move(self)); 136 | } 137 | } 138 | 139 | template 140 | void 141 | operator()( 142 | Self&& self, 143 | std::array, 144 | error_code ec1, 145 | std::size_t, 146 | error_code ec2, 147 | std::size_t) 148 | { 149 | self.complete(ec1 ? ec1 : ec2); 150 | } 151 | }; 152 | 153 | template< 154 | class AsyncReadStream, 155 | typename CompletionToken = asio::default_completion_token_t< 156 | typename AsyncReadStream::executor_type>> 157 | auto 158 | async_request( 159 | AsyncReadStream& stream, 160 | http_proto::serializer& serializer, 161 | http_proto::response_parser& parser, 162 | ch::steady_clock::duration expect100_timeout, 163 | CompletionToken&& token = CompletionToken{}) 164 | { 165 | return asio::async_compose( 166 | async_request_op{ stream, serializer, parser, expect100_timeout }, 167 | token, 168 | stream); 169 | } 170 | 171 | #endif 172 | -------------------------------------------------------------------------------- /example/client/burl/task_group.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_basic_task_group_HPP 11 | #define BURL_basic_task_group_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | namespace asio = boost::asio; 22 | using error_code = boost::system::error_code; 23 | 24 | enum class task_group_errc 25 | { 26 | cancelled = 1, 27 | closed 28 | }; 29 | 30 | template 31 | class basic_task_group 32 | { 33 | asio::steady_timer::rebind_executor::other cv_; 34 | std::uint32_t max_; 35 | std::list css_; 36 | 37 | public: 38 | typedef Executor executor_type; 39 | 40 | template 41 | struct rebind_executor 42 | { 43 | typedef basic_task_group other; 44 | }; 45 | 46 | basic_task_group(Executor exec, std::uint32_t max); 47 | 48 | basic_task_group(basic_task_group const&) = delete; 49 | basic_task_group(basic_task_group&&) = delete; 50 | 51 | void 52 | close(); 53 | 54 | void 55 | emit(asio::cancellation_type type); 56 | 57 | template 58 | auto 59 | async_adapt(T&& t, CompletionToken&& completion_token = {}); 60 | 61 | template 62 | auto 63 | async_join(CompletionToken&& completion_token = {}); 64 | }; 65 | 66 | namespace boost 67 | { 68 | namespace system 69 | { 70 | template<> 71 | struct is_error_code_enum : std::true_type 72 | { 73 | }; 74 | } // namespace system 75 | } // namespace boost 76 | 77 | inline const boost::system::error_category& 78 | task_group_category() 79 | { 80 | static const struct : boost::system::error_category 81 | { 82 | const char* 83 | name() const noexcept override 84 | { 85 | return "task_group"; 86 | } 87 | 88 | std::string 89 | message(int ev) const override 90 | { 91 | switch(static_cast(ev)) 92 | { 93 | case task_group_errc::cancelled: 94 | return "task_group cancelled"; 95 | case task_group_errc::closed: 96 | return "task_group closed"; 97 | default: 98 | return "Unknown task_group error"; 99 | } 100 | } 101 | } category; 102 | 103 | return category; 104 | }; 105 | 106 | inline std::error_code 107 | make_error_code(task_group_errc e) 108 | { 109 | return { static_cast(e), task_group_category() }; 110 | } 111 | 112 | template 113 | basic_task_group::basic_task_group(Executor exec, std::uint32_t max) 114 | : cv_{ std::move(exec), asio::steady_timer::time_point::max() } 115 | , max_{ max } 116 | { 117 | } 118 | 119 | template 120 | void 121 | basic_task_group::close() 122 | { 123 | max_ = 0; 124 | cv_.cancel(); 125 | } 126 | 127 | template 128 | void 129 | basic_task_group::emit(asio::cancellation_type type) 130 | { 131 | for(auto& cs : css_) 132 | cs.emit(type); 133 | } 134 | 135 | template 136 | template 137 | auto 138 | basic_task_group::async_adapt( 139 | T&& t, 140 | CompletionToken&& completion_token) 141 | { 142 | class remover 143 | { 144 | basic_task_group* tg_; 145 | decltype(css_)::iterator cs_; 146 | 147 | public: 148 | remover(basic_task_group* tg, decltype(css_)::iterator cs) 149 | : tg_{ tg } 150 | , cs_{ cs } 151 | { 152 | } 153 | 154 | remover(remover&& other) noexcept 155 | : tg_{ std::exchange(other.tg_, nullptr) } 156 | , cs_{ other.cs_ } 157 | { 158 | } 159 | 160 | ~remover() 161 | { 162 | if(tg_) 163 | { 164 | tg_->css_.erase(cs_); 165 | tg_->cv_.cancel(); 166 | } 167 | } 168 | }; 169 | 170 | auto adaptor = [this, t = std::move(t)]() mutable 171 | { 172 | auto cs = css_.emplace(css_.end()); 173 | 174 | return asio::bind_cancellation_slot( 175 | cs->slot(), asio::consign(std::move(t), remover{ this, cs })); 176 | }; 177 | 178 | return asio:: 179 | async_compose( 180 | [this, adaptor = std::move(adaptor), scheduled = false]( 181 | auto&& self, error_code ec = {}) mutable 182 | { 183 | if(!scheduled) 184 | self.reset_cancellation_state( 185 | asio::enable_total_cancellation()); 186 | 187 | if(ec == asio::error::operation_aborted) 188 | ec.clear(); 189 | 190 | if(!!self.cancelled()) 191 | ec = task_group_errc::cancelled; 192 | 193 | if(max_ == 0) 194 | ec = task_group_errc::closed; 195 | 196 | if(css_.size() >= max_ && !ec) 197 | { 198 | scheduled = true; 199 | return cv_.async_wait(std::move(self)); 200 | } 201 | 202 | if(!std::exchange(scheduled, true)) 203 | return asio::async_immediate( 204 | cv_.get_executor(), std::move(self)); 205 | 206 | self.complete(ec, adaptor()); 207 | }, 208 | completion_token, 209 | cv_); 210 | } 211 | 212 | template 213 | template 214 | auto 215 | basic_task_group::async_join(CompletionToken&& completion_token) 216 | { 217 | return asio::async_compose( 218 | [this, scheduled = false](auto&& self, error_code = {}) mutable 219 | { 220 | if(!scheduled) 221 | self.reset_cancellation_state( 222 | asio::enable_total_cancellation()); 223 | 224 | if(!!self.cancelled()) 225 | { 226 | emit(self.cancelled()); 227 | self.get_cancellation_state().clear(); 228 | } 229 | 230 | if(!css_.empty()) 231 | { 232 | scheduled = true; 233 | return cv_.async_wait(std::move(self)); 234 | } 235 | 236 | if(!std::exchange(scheduled, true)) 237 | return asio::async_immediate( 238 | cv_.get_executor(), std::move(self)); 239 | 240 | self.complete(); 241 | }, 242 | completion_token, 243 | cv_); 244 | } 245 | 246 | using task_group = basic_task_group; 247 | 248 | #endif 249 | -------------------------------------------------------------------------------- /example/client/burl/utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include "utils.hpp" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace grammar = boost::urls::grammar; 19 | namespace variant2 = boost::variant2; 20 | 21 | namespace 22 | { 23 | struct attr_char_t 24 | { 25 | constexpr bool 26 | operator()(char c) const noexcept 27 | { 28 | // clang-format off 29 | return grammar::alnum_chars(c) || 30 | c == '!' || c == '#' || c == '$' || c == '&' || c == '+' || 31 | c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || 32 | c == '|' || c == '~'; 33 | // clang-format on 34 | } 35 | }; 36 | 37 | constexpr attr_char_t attr_char{}; 38 | 39 | struct value_char_t 40 | { 41 | constexpr bool 42 | operator()(char c) const noexcept 43 | { 44 | return attr_char(c) || c == '%'; 45 | } 46 | }; 47 | 48 | constexpr auto value_char = value_char_t{}; 49 | 50 | struct quoted_string_t 51 | { 52 | using value_type = core::string_view; 53 | 54 | constexpr boost::system::result 55 | parse(char const*& it, char const* end) const noexcept 56 | { 57 | const auto it0 = it; 58 | 59 | if(it == end) 60 | return grammar::error::need_more; 61 | 62 | if(*it++ != '"') 63 | return grammar::error::mismatch; 64 | 65 | for(; it != end && *it != '"'; it++) 66 | { 67 | if(*it == '\\') 68 | it++; 69 | } 70 | 71 | if(*it != '"') 72 | return grammar::error::mismatch; 73 | 74 | return value_type(it0, ++it); 75 | } 76 | }; 77 | 78 | constexpr auto quoted_string = quoted_string_t{}; 79 | 80 | std::string 81 | unquote_string(core::string_view sv) 82 | { 83 | sv.remove_prefix(1); 84 | sv.remove_suffix(1); 85 | 86 | auto rs = std::string{}; 87 | for(auto it = sv.begin(); it != sv.end(); it++) 88 | { 89 | if(*it == '\\') 90 | it++; 91 | rs.push_back(*it); 92 | } 93 | return rs; 94 | } 95 | } // namespace 96 | 97 | boost::optional 98 | extract_filename_form_content_disposition(core::string_view sv) 99 | { 100 | static constexpr auto parser = grammar::range_rule( 101 | grammar::tuple_rule( 102 | grammar::squelch(grammar::optional_rule(grammar::delim_rule(';'))), 103 | grammar::squelch(grammar::optional_rule(grammar::delim_rule(' '))), 104 | grammar::token_rule(attr_char), 105 | grammar::squelch(grammar::optional_rule(grammar::delim_rule('='))), 106 | grammar::optional_rule( 107 | grammar::variant_rule( 108 | quoted_string, grammar::token_rule(value_char))))); 109 | 110 | const auto parse_rs = grammar::parse(sv, parser); 111 | 112 | if(parse_rs.has_error()) 113 | return boost::none; 114 | 115 | for(auto&& [name, value] : parse_rs.value()) 116 | { 117 | if(name == "filename" && value.has_value()) 118 | { 119 | if(value->index() == 0) 120 | { 121 | return unquote_string(variant2::get<0>(value.value())); 122 | } 123 | else 124 | { 125 | return std::string{ variant2::get<1>(value.value()) }; 126 | } 127 | } 128 | } 129 | 130 | return boost::none; 131 | } 132 | 133 | boost::system::result 134 | normalize_and_parse_url(std::string str) 135 | { 136 | static constexpr auto scheme_rule = grammar::tuple_rule( 137 | grammar::token_rule(grammar::alnum_chars + grammar::lut_chars("+-.")), 138 | grammar::delim_rule(':'), 139 | grammar::token_rule(grammar::all_chars)); 140 | 141 | const auto scheme_rs = grammar::parse(str, scheme_rule); 142 | 143 | if(scheme_rs.has_error()) 144 | str.insert(0, "http://"); 145 | 146 | auto rs = urls::parse_uri(str); 147 | 148 | if(rs.has_error()) 149 | return rs.error(); 150 | 151 | return urls::url{ rs.value() }.normalize(); 152 | } 153 | 154 | std::string 155 | format_size(std::uint64_t size, int width) 156 | { 157 | std::array units = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; 158 | 159 | if(size == 0) 160 | return "0 B"; 161 | 162 | auto order = static_cast(std::log2(size) / 10); 163 | auto scaled = static_cast(size) / std::pow(1024.0, order); 164 | auto ints = static_cast(std::log10(scaled)) + 1; 165 | auto fracs = std::max(width - ints - 1, 0); 166 | 167 | // TODO: replace ostringstream 168 | std::ostringstream os; 169 | os << std::fixed << std::setprecision(fracs) << scaled << " " 170 | << units[order]; 171 | return os.str(); 172 | } 173 | -------------------------------------------------------------------------------- /example/client/burl/utils.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2024 Mohammad Nejati 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BURL_UTILS_HPP 11 | #define BURL_UTILS_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace core = boost::core; 18 | namespace urls = boost::urls; 19 | 20 | boost::optional 21 | extract_filename_form_content_disposition(core::string_view sv); 22 | 23 | boost::system::result 24 | normalize_and_parse_url(std::string str); 25 | 26 | std::string 27 | format_size(std::uint64_t size, int width = 4); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /example/client/visit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024 Mohammad Nejati 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp 11 | CMakeLists.txt 12 | Jamfile) 13 | 14 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) 15 | 16 | add_executable(http_io_example_client_visit ${PFILES}) 17 | 18 | target_compile_definitions(http_io_example_client_visit 19 | PRIVATE BOOST_ASIO_NO_DEPRECATED) 20 | 21 | set_property(TARGET http_io_example_client_visit 22 | PROPERTY FOLDER "examples") 23 | 24 | find_package(OpenSSL REQUIRED) 25 | find_package(ZLIB) 26 | 27 | target_link_libraries(http_io_example_client_visit 28 | boost_http_io 29 | boost_program_options 30 | boost_scope 31 | OpenSSL::SSL 32 | OpenSSL::Crypto) 33 | 34 | if (WIN32) 35 | target_link_libraries(http_io_example_client_visit 36 | crypt32) 37 | endif() 38 | 39 | if (ZLIB_FOUND) 40 | target_link_libraries(http_io_example_client_visit 41 | boost_http_proto_zlib) 42 | endif() 43 | -------------------------------------------------------------------------------- /example/client/visit/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2024 Mohammad Nejati 3 | # Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) 4 | # 5 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 6 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Official repository: https://github.com/cppalliance/http_io 9 | # 10 | 11 | import ../../../../config/checks/config : requires ; 12 | 13 | using openssl ; 14 | import ac ; 15 | 16 | project 17 | : requirements 18 | $(c11-requires) 19 | /boost/http_proto//boost_http_proto 20 | [ ac.check-library /boost/http_proto//boost_http_proto_zlib : /boost/http_proto//boost_http_proto_zlib : ] 21 | /boost/http_io//boost_http_io 22 | /boost/program_options//boost_program_options 23 | /boost/scope//scope 24 | /openssl//ssl/shared 25 | /openssl//crypto/shared 26 | windows:crypt32 27 | . 28 | ; 29 | 30 | exe visit : 31 | [ glob *.cpp ] 32 | ; 33 | -------------------------------------------------------------------------------- /example/client/visit/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | struct worker 22 | { 23 | using executor_type = 24 | boost::asio::io_context::executor_type; 25 | using resolver_type = 26 | boost::asio::ip::basic_resolver< 27 | boost::asio::ip::tcp, executor_type>; 28 | using socket_type = boost::asio::basic_stream_socket< 29 | boost::asio::ip::tcp, executor_type>; 30 | 31 | socket_type sock; 32 | resolver_type resolver; 33 | boost::http_proto::response_parser pr; 34 | boost::urls::url_view url; 35 | 36 | explicit 37 | worker( 38 | executor_type ex, 39 | boost::http_proto::context& ctx) 40 | : sock(ex) 41 | , resolver(ex) 42 | , pr(ctx) 43 | { 44 | sock.open(boost::asio::ip::tcp::v4()); 45 | } 46 | 47 | void 48 | do_next() 49 | { 50 | do_visit("http://www.boost.org"); 51 | } 52 | 53 | void 54 | do_visit(boost::urls::url_view url_) 55 | { 56 | url = url_; 57 | do_resolve(); 58 | } 59 | 60 | void 61 | do_resolve() 62 | { 63 | resolver.async_resolve( 64 | url.encoded_host(), 65 | url.scheme(), 66 | [&]( 67 | boost::system::error_code ec, 68 | resolver_type::results_type results) 69 | { 70 | if(ec.failed()) 71 | { 72 | // log (target, ec.message()) 73 | auto s = ec.message(); 74 | return do_next(); 75 | } 76 | do_connect(results); 77 | }); 78 | } 79 | 80 | void 81 | do_connect( 82 | resolver_type::results_type results) 83 | { 84 | boost::asio::async_connect( 85 | sock, 86 | results.begin(), 87 | results.end(), 88 | [&]( 89 | boost::system::error_code ec, 90 | resolver_type::results_type::const_iterator it) 91 | { 92 | if(ec.failed()) 93 | { 94 | // log (target, ec.message()) 95 | return do_next(); 96 | } 97 | do_request(); 98 | }); 99 | } 100 | 101 | void 102 | do_request() 103 | { 104 | boost::http_proto::request req; 105 | auto path = url.encoded_path(); 106 | req.set_start_line( 107 | boost::http_proto::method::get, 108 | path.empty() ? "/" : path, 109 | boost::http_proto::version::http_1_1); 110 | 111 | do_shutdown(); 112 | } 113 | 114 | void 115 | do_shutdown() 116 | { 117 | boost::system::error_code ec; 118 | sock.shutdown(socket_type::shutdown_both, ec); 119 | if(ec.failed()) 120 | { 121 | // log(ec.message()) 122 | return do_next(); 123 | } 124 | do_next(); 125 | } 126 | }; 127 | 128 | int 129 | main(int argc, char* argv[]) 130 | { 131 | boost::http_proto::context ctx; 132 | boost::http_proto::parser::config_base cfg; 133 | boost::http_proto::install_parser_service(ctx, cfg); 134 | 135 | boost::asio::io_context ioc; 136 | 137 | worker w(ioc.get_executor(), ctx); 138 | 139 | w.do_next(); 140 | 141 | ioc.run(); 142 | 143 | return EXIT_SUCCESS; 144 | } 145 | -------------------------------------------------------------------------------- /example/server/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/CPPAlliance/http_io 8 | # 9 | 10 | file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp 11 | CMakeLists.txt 12 | Jamfile) 13 | 14 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) 15 | 16 | add_executable(http_io_server_example ${PFILES}) 17 | 18 | target_compile_definitions(http_io_server_example 19 | PRIVATE BOOST_ASIO_NO_DEPRECATED) 20 | 21 | set_property(TARGET http_io_server_example 22 | PROPERTY FOLDER "examples") 23 | 24 | target_link_libraries(http_io_server_example 25 | boost_http_io) 26 | -------------------------------------------------------------------------------- /example/server/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/CPPAlliance/http_proto 8 | # 9 | 10 | import testing ; 11 | import ac ; 12 | 13 | using zlib ; 14 | 15 | project 16 | : requirements 17 | $(c11-requires) 18 | #/boost/filesystem//boost_filesystem/off 19 | /boost/http_proto//boost_http_proto 20 | /boost/http_io//boost_http_io 21 | [ ac.check-library /zlib//zlib : /zlib//zlib : ] 22 | [ ac.check-library /boost/http_proto//boost_http_proto_zlib : /boost/http_proto//boost_http_proto_zlib : ] 23 | . 24 | windows:_WIN32_WINNT=0x0601 # VFALCO? 25 | ; 26 | 27 | exe server : 28 | main.cpp 29 | server.cpp 30 | ; 31 | -------------------------------------------------------------------------------- /example/server/acceptor.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/CPPAlliance/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_EXAMPLE_ACCEPTOR_HPP 11 | #define BOOST_HTTP_IO_EXAMPLE_ACCEPTOR_HPP 12 | 13 | #include "fixed_array.hpp" 14 | #include "server.hpp" 15 | #include 16 | #include 17 | #include 18 | 19 | template< class Executor > 20 | class worker; 21 | 22 | template< class Executor > 23 | class acceptor : public server::service 24 | { 25 | public: 26 | using acceptor_type = boost::asio::basic_socket_acceptor< 27 | boost::asio::ip::tcp, Executor >; 28 | using socket_type = boost::asio::basic_stream_socket< 29 | boost::asio::ip::tcp, Executor >; 30 | using executor_type = Executor; 31 | 32 | private: 33 | server& srv_; 34 | acceptor_type sock_; 35 | boost::http_proto::context& ctx_; 36 | std::size_t id_ = 0; 37 | fixed_array< worker< executor_type > > wv_; 38 | 39 | public: 40 | acceptor( 41 | server& srv, 42 | boost::asio::ip::tcp::endpoint ep, 43 | boost::http_proto::context& ctx, 44 | std::size_t num_workers, 45 | std::string const& doc_root) 46 | : srv_(srv) 47 | , sock_(srv.make_executor(), ep) 48 | , ctx_(ctx) 49 | , wv_(num_workers, srv, *this, doc_root) 50 | { 51 | } 52 | 53 | bool 54 | is_shutting_down() const noexcept 55 | { 56 | return srv_.is_shutting_down(); 57 | } 58 | 59 | std::size_t 60 | next_id() noexcept 61 | { 62 | return ++id_; 63 | } 64 | 65 | acceptor_type& 66 | socket() noexcept 67 | { 68 | return sock_; 69 | } 70 | 71 | boost::http_proto::context& 72 | context() const noexcept 73 | { 74 | return ctx_; 75 | } 76 | 77 | void 78 | run() override 79 | { 80 | for(auto& w : wv_) 81 | w.run(); 82 | } 83 | 84 | void 85 | stop() override 86 | { 87 | boost::system::error_code ec; 88 | sock_.cancel(ec); 89 | for(auto& w : wv_) 90 | w.stop(); 91 | } 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /example/server/fixed_array.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/CPPAlliance/http_io 8 | // 9 | 10 | #ifndef FIXED_ARRAY_HPP 11 | #define FIXED_ARRAY_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | template 18 | class fixed_array 19 | { 20 | #if 0 21 | //#if __cplusplus < 201703L // gcc nonconforming 22 | static_assert( 23 | alignof(T) <= 24 | alignof(std::max_align_t), 25 | "T must not be overaligned"); 26 | //#endif 27 | #endif 28 | 29 | T* t_ = nullptr; 30 | std::size_t n_ = 0; 31 | 32 | fixed_array() = default; 33 | 34 | public: 35 | using value_type = T; 36 | using reference = T&; 37 | using pointer = T*; 38 | using iterator = T*; 39 | using const_reference = T const&; 40 | using const_pointer = T const*; 41 | using const_iterator = T const*; 42 | using difference_type = std::ptrdiff_t; 43 | using size_type = std::size_t; 44 | 45 | template 46 | explicit 47 | fixed_array( 48 | std::size_t N, 49 | Args&&... args) 50 | : fixed_array() 51 | { 52 | t_ = std::allocator{}.allocate(N); 53 | while(n_ < N) 54 | { 55 | ::new(&t_[n_]) T(args...); 56 | ++n_; 57 | } 58 | } 59 | 60 | ~fixed_array() 61 | { 62 | if(! t_) 63 | return; 64 | for(auto n = n_; n--;) 65 | t_[n].~T(); 66 | std::allocator{}.deallocate(t_, n_); 67 | } 68 | 69 | std::size_t 70 | size() const noexcept 71 | { 72 | return n_; 73 | } 74 | 75 | iterator 76 | begin() noexcept 77 | { 78 | return t_; 79 | } 80 | 81 | iterator 82 | end() noexcept 83 | { 84 | return t_ + n_; 85 | } 86 | 87 | const_iterator 88 | begin() const noexcept 89 | { 90 | return const_cast< 91 | T const*>(t_); 92 | } 93 | 94 | const_iterator 95 | end() const noexcept 96 | { 97 | return const_cast< 98 | T const*>(t_) + n_; 99 | } 100 | }; 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /example/server/server.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/CPPAlliance/http_io 8 | // 9 | 10 | #include "server.hpp" 11 | #include 12 | 13 | server:: 14 | server() 15 | : ioc_(1) 16 | , sigs_(make_executor(), SIGINT, SIGTERM) 17 | , timer_(make_executor()) 18 | { 19 | } 20 | 21 | auto 22 | server:: 23 | make_executor() -> 24 | executor_type 25 | { 26 | return ioc_.get_executor(); 27 | } 28 | 29 | void 30 | server:: 31 | run() 32 | { 33 | using namespace std::placeholders; 34 | 35 | // Capture SIGINT and SIGTERM to 36 | // perform a clean shutdown 37 | sigs_.async_wait(std::bind( 38 | &server::on_signal, this, _1, _2)); 39 | 40 | for(auto& svc : v_) 41 | svc->run(); 42 | 43 | ioc_.run(); 44 | } 45 | 46 | void 47 | server:: 48 | stop() 49 | { 50 | if(is_stopped_) 51 | { 52 | // happens when there's a race with 53 | // the signal and the timer handlers 54 | return; 55 | } 56 | is_stopped_ = true; 57 | 58 | boost::system::error_code ec; 59 | sigs_.cancel(ec); // VFALCO should we use the 0-arg overload? 60 | timer_.cancel(); 61 | 62 | for(auto& svc : v_) 63 | svc->stop(); 64 | } 65 | 66 | void 67 | server:: 68 | on_signal( 69 | boost::system::error_code const& ec, int sig) 70 | { 71 | using namespace std::placeholders; 72 | 73 | if(! is_shutting_down_) 74 | { 75 | // new requests will receive HTTP 503 status 76 | is_shutting_down_ = true; 77 | 78 | // begin timed, graceful shutdown 79 | sigs_.async_wait(std::bind( 80 | &server::on_signal, this, _1, _2)); 81 | 82 | timer_.expires_after(std::chrono::seconds(30)); 83 | timer_.async_wait(std::bind( 84 | &server::on_timer, this, _1)); 85 | } 86 | else 87 | { 88 | // force a stop 89 | stop(); 90 | } 91 | } 92 | 93 | void 94 | server:: 95 | on_timer( 96 | boost::system::error_code const& ec) 97 | { 98 | if(! ec.failed()) 99 | { 100 | stop(); 101 | } 102 | else if(ec != boost::asio::error::operation_aborted) 103 | { 104 | // log? 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /example/server/server.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2022 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/CPPAlliance/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_EXAMPLE_SERVER_HPP 11 | #define BOOST_HTTP_IO_EXAMPLE_SERVER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | class server 21 | { 22 | public: 23 | using executor_type = 24 | boost::asio::io_context::executor_type; 25 | 26 | class service 27 | { 28 | public: 29 | virtual ~service() = default; 30 | virtual void run() = 0; 31 | virtual void stop() = 0; 32 | }; 33 | 34 | server(); 35 | 36 | executor_type 37 | make_executor(); 38 | 39 | template 40 | Service& 41 | make_service(Args&&... args); 42 | 43 | void run(); 44 | void stop(); 45 | 46 | bool is_shutting_down() const noexcept 47 | { 48 | return is_shutting_down_; 49 | } 50 | 51 | private: 52 | void on_signal(boost::system::error_code const&, int); 53 | void on_timer(boost::system::error_code const&); 54 | 55 | boost::asio::io_context ioc_; 56 | boost::asio::signal_set sigs_; 57 | boost::asio::basic_waitable_timer< 58 | std::chrono::steady_clock, 59 | boost::asio::wait_traits, 60 | executor_type> timer_; 61 | std::vector> v_; 62 | bool is_shutting_down_ = false; 63 | bool is_stopped_ = false; 64 | }; 65 | 66 | template 67 | Service& 68 | server:: 69 | make_service(Args&&... args) 70 | { 71 | static_assert( 72 | std::is_convertible::value, 73 | "Type requirements not met."); 74 | 75 | auto p = std::unique_ptr(new 76 | Service(std::forward(args)...)); 77 | auto& svc = *p; 78 | v_.emplace_back(std::move(p)); 79 | return svc; 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /include/boost/http_io.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_HPP 11 | #define BOOST_HTTP_IO_HPP 12 | 13 | #include 14 | #include 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/boost/http_io/buffer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_BUFFER_HPP 11 | #define BOOST_HTTP_IO_BUFFER_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace boost { 20 | namespace http_io { 21 | 22 | } // http_io 23 | } // boost 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /include/boost/http_io/client.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_CLIENT_HPP 11 | #define BOOST_HTTP_IO_CLIENT_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace boost { 18 | namespace http_io { 19 | 20 | template< 21 | class AsyncStream 22 | /*,class Derived*/ // VFALCO CRTP for things like shutdown() 23 | > 24 | class client 25 | { 26 | public: 27 | using stream_type = typename 28 | std::remove_reference::type; 29 | 30 | using executor_type = decltype( 31 | std::declval().get_executor()); 32 | 33 | template< 34 | class... Args, 35 | class = std::enable_if< 36 | std::is_constructible< 37 | AsyncStream, Args...>::value> 38 | > 39 | explicit 40 | client( 41 | Args&&... args) noexcept( 42 | std::is_nothrow_constructible< 43 | AsyncStream, Args...>::value) 44 | : stream_(std::forward(args)...) 45 | { 46 | } 47 | 48 | stream_type const& 49 | stream() const noexcept 50 | { 51 | return stream_; 52 | } 53 | 54 | stream_type& 55 | stream() noexcept 56 | { 57 | return stream_; 58 | } 59 | 60 | executor_type 61 | get_executor() const 62 | { 63 | return stream_.get_executor(); 64 | } 65 | 66 | private: 67 | AsyncStream stream_; 68 | }; 69 | 70 | } // http_io 71 | } // boost 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /include/boost/http_io/detail/config.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_DETAIL_CONFIG_HPP 11 | #define BOOST_HTTP_IO_DETAIL_CONFIG_HPP 12 | 13 | #include 14 | 15 | namespace boost { 16 | 17 | # if (defined(BOOST_HTTP_IO_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && !defined(BOOST_HTTP_IO_STATIC_LINK) 18 | # if defined(BOOST_HTTP_IO_SOURCE) 19 | # define BOOST_HTTP_IO_DECL BOOST_SYMBOL_EXPORT 20 | # define BOOST_HTTP_IO_CLASS_DECL BOOST_SYMBOL_EXPORT 21 | # define BOOST_HTTP_IO_BUILD_DLL 22 | # else 23 | # define BOOST_HTTP_IO_DECL BOOST_SYMBOL_IMPORT 24 | # define BOOST_HTTP_IO_CLASS_DECL BOOST_SYMBOL_IMPORT 25 | # endif 26 | # endif // shared lib 27 | # ifndef BOOST_HTTP_IO_DECL 28 | # define BOOST_HTTP_IO_DECL 29 | # endif 30 | # if !defined(BOOST_HTTP_IO_SOURCE) && !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_HTTP_IO_NO_LIB) 31 | # define BOOST_LIB_NAME boost_json 32 | # if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_HTTP_IO_DYN_LINK) 33 | # define BOOST_DYN_LINK 34 | # endif 35 | # include 36 | # endif 37 | 38 | /* 39 | // lift grammar into our namespace 40 | namespace urls { 41 | namespace grammar {} 42 | } 43 | namespace http_proto { 44 | namespace grammar = ::boost::urls::grammar; 45 | } // http_proto 46 | */ 47 | 48 | namespace http_proto {} 49 | namespace http_io { 50 | namespace http_proto = ::boost::http_proto; 51 | } // http_io 52 | } // boost 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/boost/http_io/detail/except.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/CPPAlliance/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_DETAIL_EXCEPT_HPP 11 | #define BOOST_HTTP_IO_DETAIL_EXCEPT_HPP 12 | 13 | #include 14 | 15 | namespace boost { 16 | namespace http_io { 17 | namespace detail { 18 | 19 | BOOST_HTTP_IO_DECL void BOOST_NORETURN throw_logic_error( 20 | source_location const& loc = BOOST_CURRENT_LOCATION); 21 | 22 | } // detail 23 | } // http_io 24 | } // boost 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /include/boost/http_io/impl/read.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_IMPL_READ_HPP 11 | #define BOOST_HTTP_IO_IMPL_READ_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace boost { 24 | namespace http_io { 25 | 26 | namespace detail { 27 | 28 | template 29 | class read_header_op 30 | : public asio::coroutine 31 | { 32 | AsyncStream& stream_; 33 | http_proto::parser& pr_; 34 | std::size_t total_bytes_ = 0; 35 | 36 | public: 37 | read_header_op( 38 | AsyncStream& s, 39 | http_proto::parser& pr) noexcept 40 | : stream_(s) 41 | , pr_(pr) 42 | { 43 | } 44 | 45 | template 46 | void 47 | operator()( 48 | Self& self, 49 | system::error_code ec = {}, 50 | std::size_t bytes_transferred = 0) 51 | { 52 | BOOST_ASIO_CORO_REENTER(*this) 53 | { 54 | if(pr_.got_header()) 55 | { 56 | BOOST_ASIO_CORO_YIELD 57 | { 58 | BOOST_ASIO_HANDLER_LOCATION(( 59 | __FILE__, __LINE__, 60 | "post")); 61 | asio::post( 62 | stream_.get_executor(), 63 | asio::append( 64 | std::move(self), 65 | ec, 66 | 0)); 67 | } 68 | goto upcall; 69 | } 70 | for(;;) 71 | { 72 | BOOST_ASIO_CORO_YIELD 73 | { 74 | BOOST_ASIO_HANDLER_LOCATION(( 75 | __FILE__, __LINE__, 76 | "async_read_some")); 77 | stream_.async_read_some( 78 | pr_.prepare(), 79 | std::move(self)); 80 | } 81 | pr_.commit(bytes_transferred); 82 | total_bytes_ += bytes_transferred; 83 | if(ec == asio::error::eof) 84 | { 85 | BOOST_ASSERT( 86 | bytes_transferred == 0); 87 | pr_.commit_eof(); 88 | ec = {}; 89 | } 90 | else if(ec.failed()) 91 | { 92 | goto upcall; 93 | } 94 | pr_.parse(ec); 95 | if(ec != http_proto::condition::need_more_input) 96 | break; 97 | if(pr_.got_header()) 98 | { 99 | ec = {}; // override possible need_more_input 100 | break; 101 | } 102 | } 103 | 104 | upcall: 105 | self.complete(ec, total_bytes_); 106 | } 107 | } 108 | }; 109 | 110 | //------------------------------------------------ 111 | 112 | template 113 | class read_body_op 114 | : public asio::coroutine 115 | { 116 | AsyncStream& stream_; 117 | http_proto::parser& pr_; 118 | std::size_t total_bytes_ = 0; 119 | bool some_; 120 | 121 | public: 122 | read_body_op( 123 | AsyncStream& s, 124 | http_proto::parser& pr, 125 | bool some) 126 | : stream_(s) 127 | , pr_(pr) 128 | , some_(some) 129 | { 130 | } 131 | 132 | template 133 | void 134 | operator()( 135 | Self& self, 136 | system::error_code ec = {}, 137 | std::size_t bytes_transferred = 0) 138 | { 139 | BOOST_ASIO_CORO_REENTER(*this) 140 | { 141 | pr_.parse(ec); 142 | if(ec != http_proto::condition::need_more_input) 143 | { 144 | BOOST_ASIO_CORO_YIELD 145 | { 146 | BOOST_ASIO_HANDLER_LOCATION(( 147 | __FILE__, __LINE__, 148 | "post")); 149 | asio::post( 150 | stream_.get_executor(), 151 | asio::append( 152 | std::move(self), 153 | ec, 154 | 0)); 155 | } 156 | goto upcall; 157 | } 158 | for(;;) 159 | { 160 | BOOST_ASIO_CORO_YIELD 161 | { 162 | BOOST_ASIO_HANDLER_LOCATION(( 163 | __FILE__, __LINE__, 164 | "async_read_some")); 165 | stream_.async_read_some( 166 | pr_.prepare(), 167 | std::move(self)); 168 | } 169 | pr_.commit(bytes_transferred); 170 | total_bytes_ += bytes_transferred; 171 | if(ec == asio::error::eof) 172 | { 173 | BOOST_ASSERT( 174 | bytes_transferred == 0); 175 | pr_.commit_eof(); 176 | ec = {}; 177 | } 178 | else if(ec.failed()) 179 | { 180 | goto upcall; 181 | } 182 | pr_.parse(ec); 183 | if(! ec.failed()) 184 | { 185 | BOOST_ASSERT( 186 | pr_.is_complete()); 187 | break; 188 | } 189 | if(ec != http_proto::condition::need_more_input) 190 | break; 191 | if(some_) 192 | { 193 | ec = {}; 194 | break; 195 | } 196 | } 197 | 198 | upcall: 199 | self.complete(ec, total_bytes_); 200 | } 201 | } 202 | }; 203 | 204 | } // detail 205 | 206 | //------------------------------------------------ 207 | 208 | template< 209 | class AsyncReadStream, 210 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 211 | void(system::error_code, std::size_t)) CompletionToken> 212 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 213 | void (system::error_code, std::size_t)) 214 | async_read_header( 215 | AsyncReadStream& s, 216 | http_proto::parser& pr, 217 | CompletionToken&& token) 218 | { 219 | return asio::async_compose< 220 | CompletionToken, 221 | void(system::error_code, std::size_t)>( 222 | detail::read_header_op< 223 | AsyncReadStream>{s, pr}, 224 | token, 225 | s); 226 | } 227 | 228 | template< 229 | class AsyncReadStream, 230 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 231 | void(system::error_code, std::size_t)) CompletionToken> 232 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 233 | void (system::error_code, std::size_t)) 234 | async_read_some( 235 | AsyncReadStream& s, 236 | http_proto::parser& pr, 237 | CompletionToken&& token) 238 | { 239 | // header must be read first! 240 | if(! pr.got_header()) 241 | detail::throw_logic_error(); 242 | 243 | return asio::async_compose< 244 | CompletionToken, 245 | void(system::error_code, std::size_t)>( 246 | detail::read_body_op< 247 | AsyncReadStream>{s, pr, true}, 248 | token, 249 | s); 250 | } 251 | 252 | template< 253 | class AsyncReadStream, 254 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 255 | void(system::error_code, std::size_t)) CompletionToken> 256 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 257 | void (system::error_code, std::size_t)) 258 | async_read( 259 | AsyncReadStream& s, 260 | http_proto::parser& pr, 261 | CompletionToken&& token) 262 | { 263 | // header must be read first! 264 | if(! pr.got_header()) 265 | detail::throw_logic_error(); 266 | 267 | return asio::async_compose< 268 | CompletionToken, 269 | void(system::error_code, std::size_t)>( 270 | detail::read_body_op< 271 | AsyncReadStream>{s, pr, false}, 272 | token, 273 | s); 274 | } 275 | 276 | } // http_io 277 | } // boost 278 | 279 | #endif 280 | -------------------------------------------------------------------------------- /include/boost/http_io/impl/write.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_IMPL_WRITE_HPP 11 | #define BOOST_HTTP_IO_IMPL_WRITE_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace boost { 23 | namespace http_io { 24 | 25 | namespace detail { 26 | 27 | template 28 | class write_some_op 29 | : public asio::coroutine 30 | { 31 | using buffers_type = 32 | http_proto::serializer::const_buffers_type; 33 | 34 | WriteStream& dest_; 35 | http_proto::serializer& sr_; 36 | 37 | public: 38 | write_some_op( 39 | WriteStream& dest, 40 | http_proto::serializer& sr) noexcept 41 | : dest_(dest) 42 | , sr_(sr) 43 | { 44 | } 45 | 46 | template 47 | void 48 | operator()(Self& self) 49 | { 50 | (*this)(self, {}, 0, true); 51 | } 52 | 53 | template 54 | void 55 | operator()( 56 | Self& self, 57 | system::error_code ec, 58 | std::size_t bytes_transferred, 59 | bool do_post = false) 60 | { 61 | system::result rv; 62 | 63 | BOOST_ASIO_CORO_REENTER(*this) 64 | { 65 | rv = sr_.prepare(); 66 | if(! rv) 67 | { 68 | ec = rv.error(); 69 | if(! do_post) 70 | goto upcall; 71 | BOOST_ASIO_CORO_YIELD 72 | { 73 | BOOST_ASIO_HANDLER_LOCATION(( 74 | __FILE__, __LINE__, 75 | "http_io::write_some_op")); 76 | asio::post( 77 | dest_.get_executor(), 78 | asio::append( 79 | std::move(self), 80 | ec, 81 | bytes_transferred)); 82 | } 83 | goto upcall; 84 | } 85 | 86 | BOOST_ASIO_CORO_YIELD 87 | { 88 | BOOST_ASIO_HANDLER_LOCATION(( 89 | __FILE__, __LINE__, 90 | "http_io::write_some_op")); 91 | dest_.async_write_some( 92 | *rv, 93 | std::move(self)); 94 | } 95 | sr_.consume(bytes_transferred); 96 | 97 | upcall: 98 | self.complete( 99 | ec, bytes_transferred ); 100 | } 101 | } 102 | }; 103 | 104 | //------------------------------------------------ 105 | 106 | template 107 | class write_op 108 | : public asio::coroutine 109 | { 110 | WriteStream& dest_; 111 | http_proto::serializer& sr_; 112 | std::size_t n_ = 0; 113 | 114 | public: 115 | write_op( 116 | WriteStream& dest, 117 | http_proto::serializer& sr) noexcept 118 | : dest_(dest) 119 | , sr_(sr) 120 | { 121 | } 122 | 123 | template 124 | void 125 | operator()( 126 | Self& self, 127 | system::error_code ec = {}, 128 | std::size_t bytes_transferred = 0) 129 | { 130 | BOOST_ASIO_CORO_REENTER(*this) 131 | { 132 | do 133 | { 134 | BOOST_ASIO_CORO_YIELD 135 | { 136 | BOOST_ASIO_HANDLER_LOCATION(( 137 | __FILE__, __LINE__, 138 | "http_io::write_op")); 139 | async_write_some( 140 | dest_, sr_, std::move(self)); 141 | } 142 | n_ += bytes_transferred; 143 | if(ec.failed()) 144 | break; 145 | } 146 | while(! sr_.is_done()); 147 | 148 | // upcall 149 | self.complete(ec, n_ ); 150 | } 151 | } 152 | }; 153 | 154 | //------------------------------------------------ 155 | 156 | #if 0 157 | template< 158 | class WriteStream, 159 | class ReadStream, 160 | class CompletionCondition> 161 | class relay_some_op 162 | : public asio::coroutine 163 | { 164 | WriteStream& dest_; 165 | ReadStream& src_; 166 | CompletionCondition cond_; 167 | http_proto::serializer& sr_; 168 | std::size_t bytes_read_ = 0; 169 | 170 | public: 171 | relay_some_op( 172 | WriteStream& dest, 173 | ReadStream& src, 174 | CompletionCondition const& cond, 175 | http_proto::serializer& sr) noexcept 176 | : dest_(dest) 177 | , src_(src) 178 | , cond_(cond) 179 | , sr_(sr) 180 | { 181 | } 182 | 183 | template 184 | void 185 | operator()( 186 | Self& self, 187 | system::error_code ec = {}, 188 | std::size_t bytes_transferred = 0) 189 | { 190 | urls::result< 191 | http_proto::serializer::buffers> rv; 192 | 193 | BOOST_ASIO_CORO_REENTER(*this) 194 | { 195 | // Nothing to do 196 | BOOST_ASSERT(! sr_.is_complete()); 197 | 198 | rv = sr_.prepare(); 199 | if(! rv) 200 | { 201 | ec = rv.error(); 202 | BOOST_ASIO_CORO_YIELD 203 | { 204 | BOOST_ASIO_HANDLER_LOCATION(( 205 | __FILE__, __LINE__, 206 | "http_io::relay_some_op")); 207 | asio::post(std::move(self)); 208 | } 209 | goto upcall; 210 | } 211 | 212 | BOOST_ASIO_CORO_YIELD 213 | { 214 | BOOST_ASIO_HANDLER_LOCATION(( 215 | __FILE__, __LINE__, 216 | "http_io::relay_some_op")); 217 | dest_.async_write_some( 218 | write_buffers(*rv), 219 | std::move(self)); 220 | } 221 | sr_.consume(bytes_transferred); 222 | 223 | upcall: 224 | self.complete( 225 | ec, bytes_transferred ); 226 | } 227 | } 228 | }; 229 | #endif 230 | 231 | } // detail 232 | 233 | //------------------------------------------------ 234 | 235 | template< 236 | class AsyncWriteStream, 237 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 238 | void(system::error_code, std::size_t)) CompletionToken> 239 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 240 | void (system::error_code, std::size_t)) 241 | async_write_some( 242 | AsyncWriteStream& dest, 243 | http_proto::serializer& sr, 244 | CompletionToken&& token) 245 | { 246 | return asio::async_compose< 247 | CompletionToken, 248 | void(system::error_code, std::size_t)>( 249 | detail::write_some_op< 250 | AsyncWriteStream>{dest, sr}, 251 | token, dest); 252 | } 253 | 254 | template< 255 | class AsyncWriteStream, 256 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 257 | void(system::error_code, std::size_t)) CompletionToken> 258 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 259 | void (system::error_code, std::size_t)) 260 | async_write( 261 | AsyncWriteStream& dest, 262 | http_proto::serializer& sr, 263 | CompletionToken&& token) 264 | { 265 | return asio::async_compose< 266 | CompletionToken, 267 | void(system::error_code, std::size_t)>( 268 | detail::write_op< 269 | AsyncWriteStream>{dest, sr}, 270 | token, 271 | dest); 272 | } 273 | 274 | #if 0 275 | template< 276 | class AsyncWriteStream, 277 | class AsyncReadStream, 278 | class CompletionCondition, 279 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 280 | void(system::error_code, std::size_t)) CompletionToken> 281 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 282 | void (system::error_code, std::size_t)) 283 | async_relay_some( 284 | AsyncWriteStream& dest, 285 | AsyncReadStream& src, 286 | CompletionCondition const& cond, 287 | http_proto::serializer& sr, 288 | CompletionToken&& token) 289 | { 290 | return asio::async_compose< 291 | CompletionToken, 292 | void(system::error_code, std::size_t)>( 293 | detail::relay_some_op< 294 | AsyncWriteStream, 295 | AsyncReadStream, 296 | CompletionCondition>{ 297 | dest, src, cond, sr}, 298 | token, 299 | dest, 300 | src); 301 | } 302 | #endif 303 | 304 | } // http_io 305 | } // boost 306 | 307 | #endif 308 | -------------------------------------------------------------------------------- /include/boost/http_io/read.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_READ_HPP 11 | #define BOOST_HTTP_IO_READ_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace boost { 22 | namespace http_io { 23 | 24 | /** Read a complete header from the stream. 25 | */ 26 | template< 27 | class AsyncReadStream, 28 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 29 | void(system::error_code, std::size_t)) CompletionToken 30 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 31 | typename AsyncReadStream::executor_type)> 32 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 33 | void (system::error_code, std::size_t)) 34 | async_read_header( 35 | AsyncReadStream& s, 36 | http_proto::parser& pr, 37 | CompletionToken&& token 38 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( 39 | typename AsyncReadStream::executor_type)); 40 | 41 | /** Read some of the message body from the stream 42 | 43 | @throws std::logic_error `pr.got_header() == false` 44 | */ 45 | template< 46 | class AsyncReadStream, 47 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 48 | void(system::error_code, std::size_t)) CompletionToken 49 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 50 | typename AsyncReadStream::executor_type)> 51 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 52 | void (system::error_code, std::size_t)) 53 | async_read_some( 54 | AsyncReadStream& s, 55 | http_proto::parser& pr, 56 | CompletionToken&& token 57 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( 58 | typename AsyncReadStream::executor_type)); 59 | 60 | /** Read the complete message body from the stream 61 | 62 | @throws std::logic_error `pr.got_header() == false` 63 | */ 64 | template< 65 | class AsyncReadStream, 66 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 67 | void(system::error_code, std::size_t)) CompletionToken 68 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 69 | typename AsyncReadStream::executor_type)> 70 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 71 | void (system::error_code, std::size_t)) 72 | async_read( 73 | AsyncReadStream& s, 74 | http_proto::parser& pr, 75 | CompletionToken&& token 76 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( 77 | typename AsyncReadStream::executor_type)); 78 | 79 | } // http_io 80 | } // boost 81 | 82 | #include 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /include/boost/http_io/write.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #ifndef BOOST_HTTP_IO_WRITE_HPP 11 | #define BOOST_HTTP_IO_WRITE_HPP 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace boost { 19 | namespace http_io { 20 | 21 | /** Write HTTP data to a stream 22 | */ 23 | template< 24 | class AsyncWriteStream, 25 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 26 | void(system::error_code, std::size_t)) CompletionToken 27 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 28 | typename AsyncWriteStream::executor_type)> 29 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 30 | void (system::error_code, std::size_t)) 31 | async_write_some( 32 | AsyncWriteStream& dest, 33 | http_proto::serializer& sr, 34 | CompletionToken&& token 35 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( 36 | typename AsyncWriteStream::executor_type)); 37 | 38 | /** Write HTTP data to a stream 39 | */ 40 | template< 41 | class AsyncWriteStream, 42 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 43 | void(system::error_code, std::size_t)) CompletionToken 44 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 45 | typename AsyncWriteStream::executor_type)> 46 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 47 | void (system::error_code, std::size_t)) 48 | async_write( 49 | AsyncWriteStream& dest, 50 | http_proto::serializer& sr, 51 | CompletionToken&& token 52 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( 53 | typename AsyncWriteStream::executor_type)); 54 | 55 | #if 0 56 | /** 57 | */ 58 | template< 59 | class AsyncWriteStream, 60 | class AsyncReadStream, 61 | class CompletionCondition, 62 | BOOST_ASIO_COMPLETION_TOKEN_FOR( 63 | void(system::error_code, std::size_t)) CompletionToken 64 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE( 65 | typename AsyncWriteStream::executor_type)> 66 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, 67 | void (system::error_code, std::size_t)) 68 | async_relay_some( 69 | AsyncWriteStream& dest, 70 | AsyncReadStream& src, 71 | CompletionCondition const& cond, 72 | http_proto::serializer& sr, 73 | CompletionToken&& token 74 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN( 75 | typename AsyncWriteStream::executor_type)); 76 | #endif 77 | 78 | } // http_io 79 | } // boost 80 | 81 | #include 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Boost.Http.Io 4 | 5 | 6 | 7 | Automatic redirection failed, please go to 8 | ./doc/html/index.html 9 |
10 | 11 | Boost.Http.Io
12 |
13 | Copyright (C) 2023 Vinnie Falco
14 |
15 | Distributed under the Boost Software License, Version 1.0. 16 | (See accompanying file LICENSE_1_0.txt or copy at 17 | http://www.boost.org/LICENSE_1_0.txt)
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /meta/explicit-failures-markup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | C++11 is the minimum requirement. 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /meta/libraries.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "http_io", 3 | "name": "Http.Io", 4 | "authors": [ 5 | "Vinnie Falco" 6 | ], 7 | "description": "HTTP/1 using Asio in C++11", 8 | "category": [ 9 | "Networking" 10 | ], 11 | "maintainers": [ 12 | "Vinnie Falco " 13 | ], 14 | "cxxstd": "11" 15 | } 16 | -------------------------------------------------------------------------------- /src/detail/except.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/CPPAlliance/http_io 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace boost { 16 | namespace http_io { 17 | namespace detail { 18 | 19 | void 20 | throw_logic_error( 21 | source_location const& loc) 22 | { 23 | throw_exception( 24 | std::logic_error( 25 | "logic error"), 26 | loc); 27 | } 28 | 29 | } // detail 30 | } // http_io 31 | } // boost 32 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | if(NOT TARGET tests) 11 | add_custom_target(tests) 12 | set_property(TARGET tests PROPERTY FOLDER Dependencies) 13 | endif() 14 | 15 | add_subdirectory(unit) 16 | -------------------------------------------------------------------------------- /test/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | build-project unit ; 11 | -------------------------------------------------------------------------------- /test/cmake_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Christian Mazakas 3 | # Copyright (c) 2022 alandefreitas (alandefreitas@gmail.com) 4 | # 5 | # Distributed under the Boost Software License, Version 1.0. 6 | # https://www.boost.org/LICENSE_1_0.txt 7 | # 8 | 9 | cmake_minimum_required(VERSION 3.5...3.16) 10 | 11 | project(cmake_subdir_test LANGUAGES CXX) 12 | set(__ignore__ ${CMAKE_C_COMPILER}) 13 | set(__ignore__ ${CMAKE_C_FLAGS}) 14 | 15 | if(BOOST_CI_INSTALL_TEST) 16 | find_package(Boost CONFIG REQUIRED COMPONENTS http_io) 17 | else() 18 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 19 | add_subdirectory(../.. boostorg/http_io) 20 | 21 | set(BOOST_URL_BUILD_TESTS OFF CACHE BOOL "" FORCE) 22 | 23 | set(deps 24 | # Primary dependencies 25 | 26 | asio 27 | assert 28 | config 29 | http_proto 30 | system 31 | throw_exception 32 | url 33 | 34 | # Secondary dependencies 35 | 36 | align 37 | context 38 | date_time 39 | buffers 40 | container_hash 41 | core 42 | mp11 43 | static_assert 44 | type_traits 45 | winapi 46 | variant2 47 | optional 48 | describe 49 | pool 50 | predef 51 | smart_ptr 52 | exception 53 | move 54 | utility 55 | algorithm 56 | io 57 | lexical_cast 58 | numeric/conversion 59 | range 60 | tokenizer 61 | array 62 | bind 63 | concept_check 64 | function 65 | iterator 66 | mpl 67 | regex 68 | tuple 69 | unordered 70 | container 71 | integer 72 | conversion 73 | preprocessor 74 | detail 75 | intrusive 76 | function_types 77 | fusion 78 | functional 79 | typeof 80 | ) 81 | 82 | foreach(dep IN LISTS deps) 83 | add_subdirectory(../../../${dep} boostorg/${dep} EXCLUDE_FROM_ALL) 84 | endforeach() 85 | endif() 86 | 87 | add_executable(main main.cpp) 88 | target_link_libraries(main Boost::http_io) 89 | 90 | enable_testing() 91 | add_test(NAME main COMMAND main) 92 | add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C $) 93 | -------------------------------------------------------------------------------- /test/cmake_test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /test/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | set(EXTRAFILES 11 | ../../../url/extra/test_main.cpp 12 | ../../../url/extra/test_suite.hpp 13 | ) 14 | 15 | file(GLOB_RECURSE PFILES CONFIGURE_DEPENDS *.cpp *.hpp) 16 | list(APPEND PFILES 17 | CMakeLists.txt 18 | Jamfile 19 | ) 20 | 21 | find_package(OpenSSL REQUIRED) 22 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "" FILES ${PFILES}) 23 | source_group("_extra" FILES ${EXTRAFILES}) 24 | add_executable(boost_http_io_tests ${PFILES} ${EXTRAFILES}) 25 | target_include_directories(boost_http_io_tests PRIVATE . ../../../url/extra) 26 | target_link_libraries(boost_http_io_tests PRIVATE 27 | OpenSSL::SSL 28 | boost_beast 29 | boost_http_io 30 | ) 31 | 32 | add_test(NAME boost_http_io_tests COMMAND boost_http_io_tests) 33 | add_dependencies(tests boost_http_io_tests) 34 | -------------------------------------------------------------------------------- /test/unit/Jamfile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) 3 | # 4 | # Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | # Official repository: https://github.com/cppalliance/http_io 8 | # 9 | 10 | import testing ; 11 | import ac ; 12 | 13 | using openssl ; 14 | 15 | project 16 | : requirements 17 | $(c11-requires) 18 | /boost/http_io//boost_http_io 19 | /boost/beast//boost_beast/off 20 | [ ac.check-library /openssl//ssl/off : /openssl//ssl/off : ] 21 | [ ac.check-library /boost/http_proto//boost_http_proto_zlib/off : /boost/http_proto//boost_http_proto_zlib/off : ] 22 | ../../../url/extra/test_main.cpp 23 | . 24 | ../../../url/extra 25 | extra 26 | on 27 | darwin,norecover:static 28 | windows:_WIN32_WINNT=0x0601 29 | msvc:_SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING 30 | msvc:_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING 31 | msvc:_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING 32 | ; 33 | 34 | local SOURCES = 35 | buffer.cpp 36 | read.cpp 37 | sandbox.cpp 38 | write.cpp 39 | ; 40 | 41 | for local f in $(SOURCES) 42 | { 43 | run $(f) : : : ; 44 | # run $(f) : target-name $(f:B)_ ; 45 | } 46 | -------------------------------------------------------------------------------- /test/unit/beast.cpp: -------------------------------------------------------------------------------- 1 | // Temporary include to ensure depinst.py recognizes Boost.Beast as a dependency. 2 | // This can be removed once proper tests are in place. 3 | #include 4 | -------------------------------------------------------------------------------- /test/unit/buffer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "test_suite.hpp" 16 | 17 | namespace boost { 18 | namespace http_io { 19 | 20 | BOOST_STATIC_ASSERT( 21 | std::is_constructible< 22 | asio::const_buffer, 23 | buffers::const_buffer>::value); 24 | 25 | BOOST_STATIC_ASSERT( 26 | std::is_constructible< 27 | asio::mutable_buffer, 28 | buffers::mutable_buffer>::value); 29 | 30 | struct buffer_test 31 | { 32 | void 33 | run() 34 | { 35 | } 36 | }; 37 | 38 | TEST_SUITE( 39 | buffer_test, 40 | "boost.http_io.buffer"); 41 | 42 | } // http_io 43 | } // boost 44 | -------------------------------------------------------------------------------- /test/unit/client.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/cppalliance/http_io 8 | // 9 | 10 | // Test that header file is self-contained. 11 | #include 12 | 13 | #include 14 | 15 | #include "test_suite.hpp" 16 | 17 | namespace boost { 18 | namespace http_io { 19 | 20 | struct success_handler 21 | { 22 | bool pass = false; 23 | 24 | void 25 | operator()(system::error_code ec, ...) 26 | { 27 | pass = BOOST_TEST(! ec.failed()); 28 | } 29 | }; 30 | 31 | /** Connect two TCP sockets together. 32 | */ 33 | template 34 | bool 35 | connect( 36 | asio::basic_stream_socket& s1, 37 | asio::basic_stream_socket& s2) 38 | 39 | { 40 | BOOST_ASSERT(s1.get_executor() == s2.get_executor()); 41 | try 42 | { 43 | asio::basic_socket_acceptor< 44 | asio::ip::tcp, Executor> a(s1.get_executor()); 45 | auto ep = asio::ip::tcp::endpoint( 46 | asio::ip::make_address_v4("127.0.0.1"), 0); 47 | a.open(ep.protocol()); 48 | a.set_option( 49 | asio::socket_base::reuse_address(true)); 50 | a.bind(ep); 51 | a.listen(0); 52 | ep = a.local_endpoint(); 53 | a.async_accept(s2, success_handler()); 54 | s1.async_connect(ep, success_handler()); 55 | s1.get_executor().context().restart(); 56 | s1.get_executor().context().run(); 57 | if(! BOOST_TEST_EQ(s1.remote_endpoint(), s2.local_endpoint())) 58 | return false; 59 | if(! BOOST_TEST_EQ(s2.remote_endpoint(), s1.local_endpoint())) 60 | return false; 61 | } 62 | catch(std::exception const&) 63 | { 64 | BOOST_TEST_FAIL(); 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | using socket_type = 72 | asio::basic_stream_socket< 73 | asio::ip::tcp, 74 | asio::io_context::executor_type>; 75 | 76 | class client_test 77 | { 78 | public: 79 | void 80 | testClient() 81 | { 82 | asio::io_context ioc; 83 | socket_type s0(ioc.get_executor()); 84 | socket_type s1(ioc.get_executor()); 85 | connect(s0, s1); 86 | client s(std::move(s1)); 87 | } 88 | 89 | void 90 | run() 91 | { 92 | testClient(); 93 | } 94 | }; 95 | 96 | TEST_SUITE(client_test, "boost.http_io.client"); 97 | 98 | } // http_io 99 | } // boost 100 | -------------------------------------------------------------------------------- /test/unit/read.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | // Test that header file is self-contained. 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "test_suite.hpp" 17 | 18 | namespace boost { 19 | namespace http_io { 20 | 21 | #if 0 22 | 23 | auto read_some( Stream&, parser& ); 24 | auto read_some( Stream&, parser&, DynamicBuffer& ); 25 | auto read( Stream&, parser& ); 26 | auto read( Stream&, parser&, DynamicBuffer& ); 27 | 28 | 29 | //-------------------------------------------- 30 | 31 | read( s, p ); // read message 32 | 33 | p.header(); // header 34 | p.body(); // decoded body 35 | 36 | //-------------------------------------------- 37 | 38 | read_some( s, p ); // read header 39 | if( ! p.is_complete() ) 40 | read( s, p ); // read body 41 | 42 | p.header(); // header 43 | p.body(); // decoded body 44 | 45 | //-------------------------------------------- 46 | 47 | read_some( s, p ); // read header 48 | read( s, p, b ); // read body into b 49 | 50 | p.header(); // header 51 | b; // decoded body 52 | 53 | //-------------------------------------------- 54 | 55 | read_some( s, p, b ); // read header, some body 56 | if( ! p.is_complete() ) 57 | read( s, p, b ); // read body into b 58 | else 59 | // (avoid immediate completion) 60 | 61 | p.header(); // header 62 | b; // decoded body 63 | 64 | //-------------------------------------------- 65 | 66 | read_some( s, p ); // read header 67 | if( ! p.is_complete() ) 68 | read( s, p, b ); // read body into b 69 | else if( ! p.body().empty() ) 70 | p.append_body( b ); // not an I/O 71 | 72 | p.header(); // header 73 | b; // decoded body 74 | 75 | //-------------------------------------------- 76 | 77 | read( s, p, ec ); // read header, some body 78 | if( ec == error::buffer_full ) 79 | ec = {}; 80 | if( ! ec.failed() ) 81 | { 82 | process( p,body() ); 83 | p.discard_body(); 84 | } 85 | 86 | #endif 87 | 88 | class read_test 89 | { 90 | public: 91 | void 92 | testRead() 93 | { 94 | boost::asio::io_context ioc; 95 | boost::asio::post( 96 | ioc.get_executor(), 97 | [] 98 | { 99 | }); 100 | } 101 | 102 | void 103 | run() 104 | { 105 | testRead(); 106 | } 107 | }; 108 | 109 | TEST_SUITE(read_test, "boost.http_io.read"); 110 | 111 | } // http_io 112 | } // boost 113 | -------------------------------------------------------------------------------- /test/unit/sandbox.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | #include "test_suite.hpp" 11 | 12 | #if 0 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace boost { 24 | namespace http_proto { 25 | 26 | static std::size_t constexpr buffer_bytes = 8 * 1024 * 1024; 27 | static std::size_t constexpr payload_size = buffer_bytes * 10; 28 | 29 | using tcp = asio::ip::tcp; 30 | namespace ssl = asio::ssl; 31 | using string_view = 32 | boost::core::string_view; 33 | 34 | //static auto const& log = test_suite::log; 35 | static std::stringstream log; 36 | 37 | struct logging_socket : asio::ip::tcp::socket 38 | { 39 | using asio::ip::tcp::socket::socket; 40 | 41 | template 45 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE_PREFIX(WriteToken, 46 | void (boost::system::error_code, std::size_t)) 47 | async_write_some(const ConstBufferSequence& buffers, 48 | BOOST_ASIO_MOVE_ARG(WriteToken) token 49 | BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) 50 | BOOST_ASIO_INITFN_AUTO_RESULT_TYPE_SUFFIX(( 51 | async_initiate( 53 | declval(), token, 54 | buffers, socket_base::message_flags(0)))) 55 | { 56 | return asio::ip::tcp::socket::async_write_some( 57 | buffers, 58 | asio::deferred([](boost::system::error_code ec, std::size_t n) 59 | { 60 | log << "async_write_some: " << n << "\n"; 61 | return asio::deferred.values(ec, n); 62 | }))(std::forward(token)); 63 | } 64 | 65 | }; 66 | 67 | class my_transfer_all_t 68 | { 69 | public: 70 | typedef std::size_t result_type; 71 | 72 | template 73 | std::size_t operator()(const Error& err, std::size_t) 74 | { 75 | return !!err ? 0 : std::size_t(-1); 76 | } 77 | }; 78 | 79 | static constexpr my_transfer_all_t my_transfer_all{}; 80 | 81 | struct sandbox_test 82 | { 83 | void 84 | do_write( 85 | asio::io_context& ioc, 86 | asio::yield_context yield) 87 | { 88 | using clock_type = 89 | std::chrono::high_resolution_clock; 90 | 91 | tcp::resolver dns(ioc); 92 | logging_socket sock(ioc); 93 | std::unique_ptr up(new char[buffer_bytes]); 94 | asio::mutable_buffer mb(up.get(), buffer_bytes); 95 | request req; 96 | 97 | asio::async_connect( 98 | sock, 99 | dns.resolve( 100 | "httpbin.cpp.al", "http"), 101 | yield); 102 | 103 | // header 104 | req.set_start_line( 105 | method::post, "/post", version::http_1_1); 106 | req.append(field::host, "httpbin.cpp.al"); 107 | req.append(field::accept, "application/text"); 108 | req.append(field::user_agent, "boost"); 109 | req.set_payload_size(payload_size); 110 | asio::async_write( 111 | sock, 112 | asio::buffer(req.buffer()), 113 | yield); 114 | 115 | // body 116 | std::size_t n = 0; 117 | std::size_t count = 0; 118 | while(n < payload_size) 119 | { 120 | auto amount = payload_size - n; 121 | if( amount > buffer_bytes) 122 | amount = buffer_bytes; 123 | auto const t0 = clock_type::now(); 124 | auto bytes_transferred = 125 | asio::async_write( 126 | sock, 127 | asio::mutable_buffer( 128 | mb.data(), 129 | amount), 130 | my_transfer_all, 131 | yield); 132 | auto const ms = std::chrono::duration_cast< 133 | std::chrono::milliseconds>( 134 | clock_type::now() - t0).count(); 135 | log << 136 | "write " << bytes_transferred << 137 | " bytes in " << ms << 138 | "ms\n"; 139 | 140 | n += bytes_transferred; 141 | ++count; 142 | } 143 | log << count << " writes total\n\n"; 144 | } 145 | 146 | void 147 | do_write_some( 148 | asio::io_context& ioc, 149 | asio::yield_context yield) 150 | { 151 | using clock_type = 152 | std::chrono::high_resolution_clock; 153 | 154 | tcp::resolver dns(ioc); 155 | tcp::socket sock(ioc); 156 | std::unique_ptr up(new char[buffer_bytes]); 157 | asio::mutable_buffer mb(up.get(), buffer_bytes); 158 | request req; 159 | 160 | asio::async_connect( 161 | sock, 162 | dns.resolve( 163 | "httpbin.cpp.al", "http"), 164 | yield); 165 | 166 | // header 167 | req.set_start_line( 168 | method::post, "/post", version::http_1_1); 169 | req.append(field::host, "httpbin.cpp.al"); 170 | req.append(field::accept, "application/text"); 171 | req.append(field::user_agent, "boost"); 172 | req.set_payload_size(payload_size); 173 | asio::async_write( 174 | sock, 175 | asio::buffer(req.buffer()), 176 | yield); 177 | 178 | // body 179 | std::size_t n = 0; 180 | std::size_t count = 0; 181 | while(n < payload_size) 182 | { 183 | auto amount = payload_size - n; 184 | if( amount > buffer_bytes) 185 | amount = buffer_bytes; 186 | auto const t0 = clock_type::now(); 187 | auto bytes_transferred = 188 | sock.async_write_some( 189 | asio::mutable_buffer( 190 | mb.data(), 191 | amount), 192 | yield); 193 | auto const ms = std::chrono::duration_cast< 194 | std::chrono::milliseconds>( 195 | clock_type::now() - t0).count(); 196 | log << 197 | "write_some " << bytes_transferred << 198 | " bytes in " << ms << 199 | "ms\n"; 200 | 201 | n += bytes_transferred; 202 | ++count; 203 | } 204 | log << count << " write_somes total\n\n"; 205 | } 206 | 207 | void 208 | run() 209 | { 210 | asio::io_context ioc; 211 | asio::spawn(ioc, [&]( 212 | asio::yield_context yield) 213 | { 214 | do_write( ioc, yield ); 215 | do_write_some( ioc, yield ); 216 | }); 217 | ioc.run(); 218 | test_suite::log << log.str(); 219 | } 220 | }; 221 | 222 | TEST_SUITE( 223 | sandbox_test, 224 | "boost.http_io.sandbox"); 225 | 226 | } // http_proto 227 | } // boost 228 | 229 | #endif 230 | -------------------------------------------------------------------------------- /test/unit/write.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/http_io 8 | // 9 | 10 | // Test that header file is self-contained. 11 | #include 12 | 13 | #include "test_suite.hpp" 14 | 15 | namespace boost { 16 | namespace http_io { 17 | 18 | class any_async_read_stream 19 | { 20 | }; 21 | 22 | class write_test 23 | { 24 | public: 25 | void 26 | testWrite() 27 | { 28 | } 29 | 30 | void 31 | run() 32 | { 33 | testWrite(); 34 | } 35 | }; 36 | 37 | TEST_SUITE( 38 | write_test, 39 | "boost.http_io.write"); 40 | 41 | } // http_io 42 | } // boost 43 | --------------------------------------------------------------------------------