diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..4d845c5 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,107 @@ +name: CI + +on: + push: + pull_request: + schedule: + - cron: '0 0 1 * *' # This line schedules the workflow to run at 00:00 on the first day of every month + +defaults: + run: + shell: bash + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - os: ubuntu-latest + compiler: gcc + - os: ubuntu-latest + compiler: clang + - os: windows-latest + compiler: msvc + - os: macos-latest + compiler: + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/vcpkg + ~/vcpkg_installed + ${{ env.HOME }}/.cache/vcpkg/archives + ${{ env.XDG_CACHE_HOME }}/vcpkg/archives + ${{ env.LOCALAPPDATA }}\vcpkg\archives + ${{ env.APPDATA }}\vcpkg\archives + key: ${{ runner.os }}-${{ matrix.compiler }}-${{ env.BUILD_TYPE }}-${{ hashFiles('**/CMakeLists.txt') }}-${{ hashFiles('./vcpkg.json')}} + restore-keys: | + ${{ runner.os }}-${{ env.BUILD_TYPE }}- + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + vcvarsall: ${{ contains(matrix.os, 'windows') }} + cmake: true + ninja: true + vcpkg: true + cppcheck: false + + - name: Install compiler for Macos + if: startsWith(matrix.os, 'macos') + run: | + brew install llvm + + - name: Prepare the PATH + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + echo "$env:USERPROFILE\vcpkg" >> $GITHUB_PATH + echo "$env:USERPROFILE\ninja" >> $GITHUB_PATH + else + echo "$HOME/vcpkg" >> $GITHUB_PATH + echo "$HOME/ninja" >> $GITHUB_PATH + fi + + - name: Install dependencies + run: | + cp -v ci/vcpkg/vcpkg.json . + vcpkg install + + - name: Build project + run: | + pushd ~ + if [ -d build ]; then + echo "Build dir exists" + ls -la build + else + mkdir -v build + fi + cd build + pwd + set -x + cmake -DVCPKG_INSTALLED_DIR=~/vcpkg_installed -DVCPKG_VERBOSE=ON -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake ${GITHUB_WORKSPACE} + cmake --build . + popd + continue-on-error: true + + - name: Dump diagnostics + if: failure() + run: | + cd ~/build + echo "---------------------------------" + cat build.ninja + echo "---------------------------------" + + - name: Run Unit Tests + run: | + pushd ~/build + ctest -R UNITTESTS . -C Release + popd diff --git a/CMakeLists.txt b/CMakeLists.txt index 64e1df7..6b6da95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,11 @@ -cmake_minimum_required(VERSION 3.0) - -if (UNIX) - # Ninja creates too many problems while maintaining - # compatibility with old and new versions of Linux/Cmake - set (CMAKE_GENERATOR "Unix Makefiles" CACHE INTERNAL "" FORCE) -endif() +cmake_minimum_required(VERSION 3.10) if (DEFINED ENV{RESTC_CPP_VERSION}) set(RESTC_CPP_VERSION $ENV{RESTC_CPP_VERSION}) endif() if (NOT DEFINED RESTC_CPP_VERSION) - set(RESTC_CPP_VERSION 0.97.0) + set(RESTC_CPP_VERSION 1.0.0) endif() if(NOT DEFINED RESTC_BOOST_VERSION) @@ -22,6 +16,18 @@ project (restc-cpp VERSION ${RESTC_CPP_VERSION}) message(STATUS "Building restc-cpp version ${PROJECT_VERSION}") +include(CheckCXXCompilerFlag) + +if (MSVC) + # Thank you Microsoft. Its so nice of you to give us all these meaningful reasons to stay up all night. + check_cxx_compiler_flag("/std:c++20" COMPILER_SUPPORTS_CXX20) + check_cxx_compiler_flag("/std:c++17" COMPILER_SUPPORTS_CXX17) + add_compile_options(/Zc:__cplusplus) +else() + check_cxx_compiler_flag("-std=c++20" COMPILER_SUPPORTS_CXX20) + check_cxx_compiler_flag("-std=c++17" COMPILER_SUPPORTS_CXX17) +endif() + if (NOT DEFINED INSTALL_RAPIDJSON_HEADERS) option(INSTALL_RAPIDJSON_HEADERS "Install rapidjson headers when make install is executed" ON) endif() @@ -32,6 +38,30 @@ option(RESTC_CPP_AUTORUN_UNIT_TESTS "Run Unit Tests automatically after build" O option(RESTC_CPP_WITH_FUNCTIONALT_TESTS "Enable Functional Testing" ON) +option(RESTC_USE_LEGACY_BOOST_FIND "Use the old Boost find module" OFF) + +set(GTEST_TAG "main" CACHE STRING "Gtest branch to use. Required on older Linux versions because newer gtest requure newer cmake!") +set(LOGFAULT_TAG "master" CACHE STRING "Logfault branch to use. Required on older Linux versions because newer gtest requure newer cmake!") + +find_package(RapidJSON QUIET) + +if (NOT RapidJSON_FOUND AND INSTALL_RAPIDJSON_HEADERS) + message(STATUS "Rapidjson not found. Adding it as an external project.") + set(RESTC_CPP_EXTERNAL_DEPS ${RESTC_CPP_EXTERNAL_DEPS} externalRapidJson) + set(restc_cpp_add_rapidjson ON) +endif() + +if (RESTC_CPP_LOG_WITH_LOGFAULT) + find_path(LOGFAULT logfault/logfault.h) + if (LOGFAULT) + message ("Using existing logfault") + else() + message ("Embedding logfault header only library") + set(RESTC_CPP_EXTERNAL_DEPS ${RESTC_CPP_EXTERNAL_DEPS} externalLogfault) + set(restc_cpp_add_logfault ON) + endif() +endif() + include(cmake_scripts/external-projects.cmake) if (EXISTS ${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) @@ -68,7 +98,26 @@ option(RESTC_CPP_LOG_JSON_SERIALIZATION "Enable trace logging for json serializa option(RESTC_CPP_WITH_ZLIB "Use zlib" ON) -option(RESTC_CPP_USE_CPP17 "Use the C++17 standard" ON) +option(RESTC_CPP_USE_CPP14 "Use the C++14 standard" OFF) + +option(RESTC_CPP_USE_CPP17 "Use the C++17 standard" OFF) + +option(RESTC_CPP_USE_CPP20 "Use the C++20 standard" OFF) + +if (NOT RESTC_CPP_USE_CPP14 AND NOT RESTC_CPP_USE_CPP17 AND NOT RESTC_CPP_USE_CPP20) + # Check if GCC 12 is being used and default it to C++17 + # https://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg1887728.html + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13.0 AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0) + message(STATUS "GCC 12 detected, defaulting to C++17") + set(RESTC_CPP_USE_CPP17 ON) + elseif (COMPILER_SUPPORTS_CXX20) + set(RESTC_CPP_USE_CPP20 ON) + elseif (COMPILER_SUPPORTS_CXX17) + set(RESTC_CPP_USE_CPP17 ON) + else () + set(RESTC_CPP_USE_CPP14 ON) + endif() +endif() option(RESTC_CPP_THREADED_CTX "Allow asio contextx with multiple therads. Enables thread-safe internal access." OFF) @@ -139,7 +188,10 @@ endif() message(STATUS "Using ${CMAKE_CXX_COMPILER}") macro(SET_CPP_STANDARD target) - if (RESTC_CPP_USE_CPP17) + if (RESTC_CPP_USE_CPP20) + message(STATUS "Using C++ 20 for ${target}") + set_property(TARGET ${target} PROPERTY CXX_STANDARD 20) + elseif (RESTC_CPP_USE_CPP17) message(STATUS "Using C++ 17 for ${target}") set_property(TARGET ${target} PROPERTY CXX_STANDARD 17) else() @@ -168,6 +220,7 @@ set(ACTUAL_SOURCES src/RequestBodyStringImpl.cpp src/RequestBodyFileImpl.cpp src/url_encode.cpp + src/boost_compitability.cpp ${LOGGING_SRC} ) @@ -179,14 +232,7 @@ if (RESTC_CPP_WITH_ZLIB) set(ACTUAL_SOURCES ${ACTUAL_SOURCES} src/ZipReaderImpl.cpp) endif() -if (WIN32) - include(cmake_scripts/pch.cmake) - ADD_MSVC_PRECOMPILED_HEADER(restc-cpp/restc-cpp.h src/pch.cpp ACTUAL_SOURCES) - add_definitions(-DWAR_PCH) - set(SOURCES ${ACTUAL_SOURCES} src/pch.cpp ${HEADERS} ${RESFILES}) -else() - set(SOURCES ${ACTUAL_SOURCES}) -endif() +set(SOURCES ${ACTUAL_SOURCES}) add_library(${PROJECT_NAME} ${SOURCES}) set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_OUTPUT_NAME restc-cppD) @@ -199,21 +245,21 @@ target_include_directories(${PROJECT_NAME} SET_CPP_STANDARD(${PROJECT_NAME}) -if (RESTC_CPP_LOG_WITH_LOGFAULT) - find_path(LOGFAULT logfault/logfault.h) - if (LOGFAULT) - message ("Using existing logfault") - else() - message ("Embedding logfault header only library") - set(RESTC_CPP_EXTERNAL_DEPS ${RESTC_CPP_EXTERNAL_DEPS} externalLogfault) - endif() +if(NOT "${RESTC_CPP_EXTERNAL_DEPS}" STREQUAL "") + add_dependencies(${PROJECT_NAME} ${RESTC_CPP_EXTERNAL_DEPS}) endif() -add_dependencies(${PROJECT_NAME} externalRapidJson ${RESTC_CPP_EXTERNAL_DEPS}) - if (NOT EMBEDDED_RESTC_CPP) - #set(Boost_USE_MULTITHREADED ON) - find_package(Boost ${RESTC_BOOST_VERSION} REQUIRED COMPONENTS + + if (RESTC_USE_LEGACY_BOOST_FIND) + unset(restc_cpp_boost_find_config) + message("Using legacy Boost find config") + elseif(CMAKE_VERSION VERSION_GREATER "3.28") + set(restc_cpp_boost_find_config CONFIG) + message("Using new Boost find config") + endif() + + find_package(Boost ${RESTC_BOOST_VERSION} REQUIRED ${restc_cpp_boost_find_config} COMPONENTS system program_options filesystem @@ -223,6 +269,9 @@ if (NOT EMBEDDED_RESTC_CPP) chrono ${BOOST_LOG_DEP} ) + + message(STATUS "Boost version found: ${Boost_VERSION}") + target_include_directories(${PROJECT_NAME} PUBLIC ${Boost_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME} PUBLIC ${Boost_LIBRARIES}) target_compile_definitions(${PROJECT_NAME} PUBLIC -DBOOST_COROUTINE_NO_DEPRECATION_WARNING=1) @@ -255,16 +304,12 @@ if (NOT EMBEDDED_RESTC_CPP) link_directories(${Boost_LIBRARY_DIRS}) endif() - include(cmake_scripts/pch.cmake) - - set_property(TARGET PROPERTY CXX_STANDARD 17) - if(WIN32) add_definitions(-D_WIN32_WINNT=0x0600) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SAFESEH:NO") endif() - set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib CACHE PATH "Destination location") + set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib CACHE PATH "Destination location") link_directories(${LIBRARY_OUTPUT_PATH}) include(cmake_scripts/doxygen.cmake) diff --git a/README.md b/README.md index 956a673..adf921e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[](https://github.com/jgaa/restc-cpp/actions/workflows/ci.yaml) + # Introduction to the restc-cpp C++ library <i>The magic that takes the pain out of accessing JSON API's from C++ </i> @@ -73,7 +75,7 @@ that experience. So I spent a few weeks creating my own HTTP Client library using boost::asio with JSON serialization/deserialization. # Dependencies -Restc-cpp depends on C++14 with its standard libraries and: +Restc-cpp depends on C++14 (or newer) with its standard libraries and: - boost - rapidjson (CMake will download and install rapidjson for the project) - gtest (CMake will download and install gtest for the project if it is not installed) @@ -251,33 +253,54 @@ Please refer to the [tutorial](doc/Tutorial.md) for more examples. # Current Status The project has been in public BETA since April 11th 2017. +# Supported compilers +These are the compilers that are being tested before anything is merged to the master branch. + +- g++ from 8 to 14 +- clang (current) +- msvc (current) +- Apple clang (current) + +# Supported C++ standards +These are the C++ versions that are are being tested before anything is merged to the master branch. + +- C++14 +- C++17 +- C++20 + # Supported operating systems These are the operating systems where my Continues Integration (Jenkins) servers currently compiles the project and run all the tests: - Debian Testing - - Debian Bullseye (Stable) + - Debian Bookworm + - Debian Bullseye - Debian Buster - - Debian Stretch - - Windows 10 / Microsoft Visual Studio 2019, Community version using vcpkg for dependencies - - Ubuntu Xenial (LTS) + - Windows 10 / Microsoft Visual Studio 2022, Community version using vcpkg for dependencies + - Ubuntu Noble (LTS) + - Ubuntu Jammy (LTS) - Ubuntu Bionic (LTS) - -Support for MacOS has been removed after Apples announcement that their love for privacy was just -a marketing gimmick. - -Fedora is currently disabled in my CI because of failures to start their Docker containers. (Work in progress). Ubuntu Jammy don't work in docker with my Jenkins CI pipeline, so I have no reliable way to test it. Windows 11 cannot be run on my KVM /QEMU system, because it don't support "secure" boot, so I have no way to test it. + - Fedora (latest) + - MacOS (latest) The Jenkins setup is [here](ci/jenkins). -I currently use my own CI infrastructure running on my own hardware. I use Jenkins on a VM with Debian Bullseye, and three slaves for Docker on Linux VM's, one slave running on a VM with Microsoft Windows 10 Pro. Using Docker to build with different Linux distributions gives me flexibility. It also immediately catches mistakes that break the build or test(s) on a specific Linux distribution or platform. Using my own infrastructure improves the security, as I don't share any credentials with 3rd party services or allow external access into my LAN. +I currently use my own CI infrastructure running on my own hardware. I use Jenkins on a VM with Debian Bookworm, and three slaves for Docker on Linux VM's, one slave running on a VM with Microsoft Windows 10 Pro. Using Docker to build with different Linux distributions gives me flexibility. It also immediately catches mistakes that break the build or test(s) on a specific Linux distribution or platform. Using my own infrastructure improves the security, as I don't share any credentials with 3rd party services or allow external access into my LAN. + +Github Actions can not compile for various Linux variants (at least not on the free plan for Open Source projects), +and it can not run multiple docker-containers (or even *any* containers for Windows or MacOS builds) to allow integration testing. +I have configured it for this repository anyway, because it's automation setup is different than the Jenkins setup, +which have helped identifying some issues with the projects cmake files. # Blog-posts about the project: - [About version 0.90](https://lastviking.eu/restc_cpp_90.html) - [restc-cpp tags on The Last Viking's Nest](https://lastviking.eu/_tags/restc-cpp.html) # Similar projects - - [Boost.Beast](https://github.com/boostorg/beast) by Vinnie Falco. When you like to write many lines of code... - [RESTinCurl](https://github.com/jgaa/RESTinCurl) by me. Aimed at mobile applications, IoT and projects that already link with libcurl. + - [Boost.Beast](https://github.com/boostorg/beast) by Vinnie Falco. When you like to write many lines of code... + + **Json serialization only** + - [Boost.Json](https://www.boost.org/doc/libs/1_83_0/libs/json/doc/html/index.html) - [JSON for Modern C++](https://nlohmann.github.io/json/) by Niels Lohmann. My favorite json library, when I need to more than just static serialization. - [json11 - tiny JSON library for C++11, providing JSON parsing and serialization](https://github.com/dropbox/json11) diff --git a/ci/jenkins/Dockerfile.debian-stretch b/ci/jenkins/Dockefile.debian-bookworm similarity index 81% rename from ci/jenkins/Dockerfile.debian-stretch rename to ci/jenkins/Dockefile.debian-bookworm index b9286dc..307f980 100644 --- a/ci/jenkins/Dockerfile.debian-stretch +++ b/ci/jenkins/Dockefile.debian-bookworm @@ -1,14 +1,15 @@ -FROM debian:stretch +FROM debian:bookworm MAINTAINER Jarle Aase <jgaa@jgaa.com> +# In case you need proxy RUN DEBIAN_FRONTEND="noninteractive" apt-get -q update &&\ DEBIAN_FRONTEND="noninteractive" apt-get -y -q --no-install-recommends upgrade &&\ - DEBIAN_FRONTEND="noninteractive" apt-get install -y -q \ + DEBIAN_FRONTEND="noninteractive" apt-get install -y -q\ openssh-server g++ git \ build-essential \ zlib1g-dev g++ cmake make libboost-all-dev libssl-dev \ - openjdk-8-jdk &&\ + default-jdk &&\ apt-get -y -q autoremove &&\ apt-get -y -q clean diff --git a/ci/jenkins/Dockefile.debian-buster b/ci/jenkins/Dockefile.debian-buster index 9d338e8..d42e508 100644 --- a/ci/jenkins/Dockefile.debian-buster +++ b/ci/jenkins/Dockefile.debian-buster @@ -8,7 +8,7 @@ RUN DEBIAN_FRONTEND="noninteractive" apt-get -q update &&\ DEBIAN_FRONTEND="noninteractive" apt-get install -y -q \ openssh-server g++ git \ build-essential \ - zlib1g-dev g++ cmake make libboost-all-dev libssl-dev \ + zlib1g-dev g++ cmake make libboost-all-dev libssl-dev libgtest-dev \ default-jdk &&\ apt-get -y -q autoremove &&\ apt-get -y -q clean diff --git a/ci/jenkins/Dockefile.ubuntu-bionic b/ci/jenkins/Dockefile.ubuntu-bionic deleted file mode 100644 index 724e76b..0000000 --- a/ci/jenkins/Dockefile.ubuntu-bionic +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:bionic - -MAINTAINER Jarle Aase <jgaa@jgaa.com> - -# In case you need proxy -#RUN echo 'Acquire::http::Proxy "http://127.0.0.1:8080";' >> /etc/apt/apt.conf - -RUN apt-get -q update &&\ - apt-get -y -q --no-install-recommends upgrade &&\ - apt-get -y -q install openssh-server g++ git \ - automake autoconf ruby ruby-dev rubygems build-essential \ - zlib1g-dev g++ cmake make libboost-all-dev libssl-dev \ - openjdk-8-jdk &&\ - gem install --no-ri --no-rdoc fpm &&\ - apt-get -y -q autoremove &&\ - apt-get -y -q clean - -# Set user jenkins to the image -RUN useradd -m -d /home/jenkins -s /bin/sh jenkins &&\ - echo "jenkins:jenkins" | chpasswd - -# Standard SSH port -EXPOSE 22 - -# Default command -CMD ["/usr/sbin/sshd", "-D"] diff --git a/ci/jenkins/Dockefile.ubuntu-jammy b/ci/jenkins/Dockefile.ubuntu-jammy index 2c4155c..ebc9def 100644 --- a/ci/jenkins/Dockefile.ubuntu-jammy +++ b/ci/jenkins/Dockefile.ubuntu-jammy @@ -14,7 +14,8 @@ RUN DEBIAN_FRONTEND="noninteractive" apt-get -q update &&\ # Set user jenkins to the image RUN useradd -m -d /home/jenkins -s /bin/sh jenkins &&\ echo "jenkins:jenkins" | chpasswd &&\ - mkdir -p /home/jenkins/build/workspace/restc-staging + mkdir -p /home/jenkins/build/workspace/restc-staging &&\ + mkdir -p /run/sshd # Standard SSH port EXPOSE 22 diff --git a/ci/jenkins/Dockefile.ubuntu-xenial b/ci/jenkins/Dockefile.ubuntu-xenial index d61a7f4..4fc4f3f 100644 --- a/ci/jenkins/Dockefile.ubuntu-xenial +++ b/ci/jenkins/Dockefile.ubuntu-xenial @@ -8,10 +8,9 @@ MAINTAINER Jarle Aase <jgaa@jgaa.com> RUN apt-get -q update &&\ apt-get -y -q --no-install-recommends upgrade &&\ apt-get -y -q install openssh-server g++ git \ - automake autoconf ruby ruby-dev rubygems build-essential \ - zlib1g-dev g++ cmake make libboost-all-dev libssl-dev \ + automake autoconf build-essential \ + zlib1g-dev g++ cmake make libboost-all-dev libssl-dev libgtest-dev \ openjdk-8-jdk &&\ - gem install --no-ri --no-rdoc fpm &&\ apt-get -y -q autoremove &&\ apt-get -y -q clean diff --git a/ci/jenkins/Dockerfile.fedora b/ci/jenkins/Dockerfile.fedora index 7996a34..24389ca 100644 --- a/ci/jenkins/Dockerfile.fedora +++ b/ci/jenkins/Dockerfile.fedora @@ -1,17 +1,25 @@ -FROM fedora:33 +FROM fedora:latest MAINTAINER Jarle Aase <jgaa@jgaa.com> -RUN echo "root:password" | chpasswd -RUN useradd jenkins -RUN echo "jenkins:jenkins" | chpasswd +RUN dnf -q update -y &&\ + dnf -q upgrade -y &&\ + dnf -q install -y openssh-server gcc-c++ git gnupg2 \ + automake autoconf make \ + zlib-devel gcc-c++ cmake boost-devel openssl-devel \ + java-11-openjdk-devel &&\ + dnf -q autoremove -y &&\ + dnf clean all &&\ + ssh-keygen -A -RUN dnf -y update &&\ - dnf -y install @development-tools git jre-openjdk zlib-devel openssl-devel boost-devel cmake gcc-c++ openssh-server +# Set user jenkins to the image +RUN useradd -m -d /home/jenkins -s /bin/bash jenkins &&\ + chmod 0777 /home/jenkins &&\ + echo "jenkins:jenkins" | chpasswd &&\ + mkdir -p /run/sshd -# expose the ssh port +# Standard SSH port EXPOSE 22 -# entrypoint by starting sshd +# Default command CMD ["/usr/sbin/sshd", "-D"] - diff --git a/ci/jenkins/Dockerfile.ubuntu-bionic b/ci/jenkins/Dockerfile.ubuntu-bionic new file mode 100644 index 0000000..fb4ec0d --- /dev/null +++ b/ci/jenkins/Dockerfile.ubuntu-bionic @@ -0,0 +1,24 @@ +FROM ubuntu:18.04 + +MAINTAINER Jarle Aase <jgaa@jgaa.com> + +RUN DEBIAN_FRONTEND="noninteractive" apt-get -q update &&\ + DEBIAN_FRONTEND="noninteractive" apt-get -y -q --no-install-recommends upgrade &&\ + DEBIAN_FRONTEND="noninteractive" apt-get install -y -q openssh-server g++ git gpgv \ + automake autoconf build-essential \ + zlib1g-dev g++ cmake make libboost-all-dev libssl-dev \ + default-jdk &&\ + apt-get -y -q autoremove &&\ + apt-get -y -q clean + +# Set user jenkins to the image +RUN useradd -m -d /home/jenkins -s /bin/sh jenkins &&\ + echo "jenkins:jenkins" | chpasswd &&\ + mkdir -p /home/jenkins/build/workspace/restc-staging &&\ + mkdir -p /run/sshd + +# Standard SSH port +EXPOSE 22 + +# Default command +CMD ["/usr/sbin/sshd", "-D"] diff --git a/ci/jenkins/Dockerfile.ubuntu-noble b/ci/jenkins/Dockerfile.ubuntu-noble new file mode 100644 index 0000000..822a155 --- /dev/null +++ b/ci/jenkins/Dockerfile.ubuntu-noble @@ -0,0 +1,23 @@ +FROM ubuntu:24.04 + +MAINTAINER Jarle Aase <jgaa@jgaa.com> + +RUN DEBIAN_FRONTEND="noninteractive" apt-get -q update &&\ + DEBIAN_FRONTEND="noninteractive" apt-get -y -q --no-install-recommends upgrade &&\ + DEBIAN_FRONTEND="noninteractive" apt-get install -y -q openssh-server g++ git gpgv \ + automake autoconf build-essential rapidjson-dev \ + zlib1g-dev g++ cmake make libboost-all-dev libssl-dev \ + default-jdk &&\ + apt-get -y -q autoremove &&\ + apt-get -y -q clean + +# Set user jenkins to the image +RUN useradd -m -d /home/jenkins -s /bin/sh jenkins &&\ + echo "jenkins:jenkins" | chpasswd &&\ + mkdir -p /run/sshd + +# Standard SSH port +EXPOSE 22 + +# Default command +CMD ["/usr/sbin/sshd", "-D"] diff --git a/ci/jenkins/Jenkinsfile.groovy b/ci/jenkins/Jenkinsfile.groovy index 30b4199..d9dd3b4 100644 --- a/ci/jenkins/Jenkinsfile.groovy +++ b/ci/jenkins/Jenkinsfile.groovy @@ -1,21 +1,21 @@ #!/usr/bin/env groovy pipeline { - agent { label 'master' } + agent { label 'main' } environment { - RESTC_CPP_VERSION = "0.97.0" + RESTC_CPP_VERSION = "1.0.0" // It is not possible to get the current IP number when running in the sandbox, and // Jenkinsfiles always runs in the sandbox. // For simplicity, I just put it here (I already wasted 3 hours on this) - RESTC_CPP_TEST_DOCKER_ADDRESS="192.168.1.131" + RESTC_CPP_TEST_DOCKER_ADDRESS="192.168.1.55" CTEST_OUTPUT_ON_FAILURE=1 } stages { stage('Prepare') { - agent { label 'master' } + agent { label 'main' } steps { sh 'docker-compose -f ./ci/mock-backends/docker-compose.yml up --build -d' } @@ -23,480 +23,454 @@ pipeline { stage('Build') { parallel { -// Broken: java.io.IOException: Failed to run image '692f7cce9b970633dba347a9aaf12846429c073f'. Error: docker: Error // response from daemon: OCI runtime create failed: container_linux.go:367: starting container process caused: chdir to cwd ("/home/jenkins/build/workspace/restc-staging") set in config.json failed: permission denied: unknown. -// stage('Ubuntu Jammy') { -// agent { -// dockerfile { -// filename 'Dockefile.ubuntu-jammy' -// dir 'ci/jenkins' -// label 'docker' -// } -// } -// -// options { -// timeout(time: 30, unit: "MINUTES") -// } -// -// steps { -// echo "Building on ubuntu-jammy-AMD64 in ${WORKSPACE}" -// checkout scm -// sh 'pwd; ls -la' -// sh 'rm -rf build' -// sh 'mkdir build' -// sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' -// -// echo 'Getting ready to run tests' -// script { -// try { -// sh 'cd build && ctest --no-compress-output -T Test' -// } catch (exc) { -// -// unstable(message: "${STAGE_NAME} - Testing failed") -// } -// } -// } -// } -// -// stage('Ubuntu Jammy MT CTX') { -// agent { -// dockerfile { -// filename 'Dockefile.ubuntu-jammy' -// dir 'ci/jenkins' -// label 'docker' -// } -// } -// -// options { -// timeout(time: 30, unit: "MINUTES") -// } -// -// steps { -// echo "Building on ubuntu-jammy-AMD64 in ${WORKSPACE}" -// checkout scm -// sh 'pwd; ls -la' -// sh 'rm -rf build' -// sh 'mkdir build' -// sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' -// -// echo 'Getting ready to run tests' -// script { -// try { -// sh 'cd build && ctest --no-compress-output -T Test' -// } catch (exc) { -// -// unstable(message: "${STAGE_NAME} - Testing failed") -// } -// } -// } -// } - - stage('Ubuntu Bionic') { + + stage('macOS') { + agent {label 'macos'} + + // environment { + // CPPFLAGS = "-I/usr/local/opt/openssl/include -I/usr/local/opt/zlib/include -I/usr/local/opt/boost/include/" + // LDFLAGS = "-L/usr/local/opt/openssl/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/boost/lib/" + // } + + steps { + echo "Building on macos in ${WORKSPACE}" + sh 'brew install openssl boost zlib rapidjson googletest cmake ninja' + checkout scm + sh 'pwd; ls -la' + sh 'rm -rf build' + sh 'mkdir build' + sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j4' + + echo 'Getting ready to run tests' + script { + try { + sh 'cd build && ctest --no-compress-output -T Test' + } catch (exc) { + echo 'Testing failed' + currentBuild.result = 'UNSTABLE' + } + } + } + } + + stage('Ubuntu Noble') { agent { dockerfile { - filename 'Dockefile.ubuntu-bionic' + filename 'Dockerfile.ubuntu-noble' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on ubuntu-bionic-AMD64 in ${WORKSPACE}" + echo "Building on ubuntu-noble-AMD64 in ${NODE_NAME} --> ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=OFF .. && make -j $(nproc)' + sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") - } } } } - - stage('Ubuntu Bionic MT CTX') { + + stage('Ubuntu Noble MT CTX') { agent { dockerfile { - filename 'Dockefile.ubuntu-bionic' + filename 'Dockerfile.ubuntu-noble' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on ubuntu-bionic-AMD64 in ${WORKSPACE}" + echo "Building on ubuntu-noble-AMD64 in ${NODE_NAME} --> ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=OFF .. && make -j $(nproc)' + sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Ubuntu Bionic C++17') { + + stage('Ubuntu Jammy') { agent { dockerfile { - filename 'Dockefile.ubuntu-bionic' + filename 'Dockefile.ubuntu-jammy' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on ubuntu-bionic-AMD64 in ${WORKSPACE}" + echo "Building on ubuntu-jammy-AMD64 in ${NODE_NAME} --> ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Ubuntu Bionic C++17 MT CTX') { + + stage('Ubuntu Jammy MT CTX') { agent { dockerfile { - filename 'Dockefile.ubuntu-bionic' + filename 'Dockefile.ubuntu-jammy' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on ubuntu-bionic-AMD64 in ${WORKSPACE}" + echo "Building on ubuntu-jammy-AMD64 in ${NODE_NAME} --> ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - stage('Ubuntu Xenial') { + stage('Ubuntu Bionic') { agent { dockerfile { - filename 'Dockefile.ubuntu-xenial' + filename 'Dockerfile.ubuntu-bionic' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on ubuntu-xenial-AMD64 in ${WORKSPACE}" + echo "Building on ubuntu-bionic-AMD64 in ${NODE_NAME} --> ${WORKSPACE}" checkout scm - sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DGTEST_TAG=release-1.10.0 -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=OFF .. && make -j $(nproc)' + sh 'cd build && cmake -DGTEST_TAG=release-1.12.0 -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { - sh 'cd build && ctest -E "HTTPS_FUNCTIONAL_TESTS|PROXY_TESTS" --no-compress-output -T Test' + sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Ubuntu Xenial MT CTX') { + + stage('Debian Buster ') { agent { dockerfile { - filename 'Dockefile.ubuntu-xenial' + filename 'Dockefile.debian-buster' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on ubuntu-xenial-AMD64 in ${WORKSPACE}" + echo "Building on debian-buster-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DGTEST_TAG=release-1.10.0 -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=OFF .. && make -j $(nproc)' + sh 'cd build && cmake -DGTEST_TAG=release-1.12.0 -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { - sh 'cd build && ctest -E "HTTPS_FUNCTIONAL_TESTS|PROXY_TESTS" --no-compress-output -T Test' + sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - stage('Debian Stretch') { + stage('Debian Buster MT CTX') { agent { dockerfile { - filename 'Dockerfile.debian-stretch' + filename 'Dockefile.debian-buster' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on debian-stretch-AMD64 in ${WORKSPACE}" + echo "Building on debian-buster-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=OFF .. && make -j $(nproc)' + sh 'cd build && cmake -DGTEST_TAG=release-1.12.0 -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { - sh 'cd build && ctest -E "HTTPS_FUNCTIONAL_TESTS|PROXY_TESTS" --no-compress-output -T Test' + sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Debian Stretch MT CTX') { + + stage('Debian Buster MT CTX C++14') { agent { dockerfile { - filename 'Dockerfile.debian-stretch' + filename 'Dockefile.debian-buster' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on debian-stretch-AMD64 in ${WORKSPACE}" + echo "Building on debian-buster-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=OFF .. && make -j $(nproc)' + sh 'cd build && cmake -DGTEST_TAG=release-1.12.0 -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP14=ON .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { - sh 'cd build && ctest -E "HTTPS_FUNCTIONAL_TESTS|PROXY_TESTS" --no-compress-output -T Test' + sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Debian Buster C++17') { + + stage('Debian Bullseye') { agent { dockerfile { - filename 'Dockefile.debian-buster' + filename 'Dockefile.debian-bullseye' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on debian-buster-AMD64 in ${WORKSPACE}" + echo "Building on debian-bullseye-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Debian Buster C++17 MT CTX') { + + stage('Debian Bullseye MT CTX') { agent { dockerfile { - filename 'Dockefile.debian-buster' + filename 'Dockefile.debian-bullseye' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on debian-buster-AMD64 in ${WORKSPACE}" + echo "Building on debian-bullseye-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Debian Bullseye C++17') { + + stage('Debian Bookworm') { agent { dockerfile { - filename 'Dockefile.debian-bullseye' + filename 'Dockefile.debian-bookworm' dir 'ci/jenkins' label 'docker' + args '-u root' } } - options { timeout(time: 30, unit: "MINUTES") } - + steps { - echo "Building on debian-bullseye-AMD64 in ${WORKSPACE}" + echo "Building on debian-bookworm-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Debian Bullseye C++17 MT CTX') { + + stage('Debian Bookworm MT CTX') { agent { dockerfile { - filename 'Dockefile.debian-bullseye' + filename 'Dockefile.debian-bookworm' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } steps { - echo "Building on debian-bullseye-AMD64 in ${WORKSPACE}" + echo "Building on debian-bookworm-AMD64 in ${WORKSPACE}" checkout scm sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - stage('Debian Testing C++17') { + stage('Debian Testing') { agent { dockerfile { filename 'Dockefile.debian-testing' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } @@ -507,29 +481,30 @@ pipeline { sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Debian Testing MT CTX C++17') { + + stage('Debian Testing MT CTX') { agent { dockerfile { filename 'Dockefile.debian-testing' dir 'ci/jenkins' label 'docker' + args '-u root' } } - + options { timeout(time: 30, unit: "MINUTES") } @@ -540,84 +515,59 @@ pipeline { sh 'pwd; ls -la' sh 'rm -rf build' sh 'mkdir build' - sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release -DRESTC_CPP_USE_CPP17=ON .. && make -j $(nproc)' + sh 'cd build && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && make -j $(nproc)' echo 'Getting ready to run tests' script { try { sh 'cd build && ctest --no-compress-output -T Test' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } -// stage('Fedora') { -// agent { -// dockerfile { -// filename 'Dockerfile.fedora' -// dir 'ci/jenkins' -// label 'docker' -// } -// } -// -// steps { -// echo "Building on Fedora in ${WORKSPACE}" -// checkout scm -// sh 'pwd; ls -la' -// sh 'rm -rf build' -// sh 'mkdir build' -// sh 'cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make' -// -// echo 'Getting ready to run tests' -// script { -// try { -// sh 'cd build && ctest --no-compress-output -T Test' -// } catch (exc) { -// -// unstable(message: "${STAGE_NAME} - Testing failed") -// } -// } -// } -// } -// -// stage('Centos7') { -// agent { -// dockerfile { -// filename 'Dockerfile.centos7' -// dir 'ci/jenkins' -// label 'docker' -// } -// } -// -// steps { -// echo "Building on Centos7 in ${WORKSPACE}" -// checkout scm -// sh 'pwd; ls -la' -// sh 'rm -rf build' -// sh 'mkdir build' -// sh 'cd build && source scl_source enable devtoolset-7 && cmake -DCMAKE_BUILD_TYPE=Release -DBOOST_ROOT=/opt/boost .. && make' -// -// echo 'Getting ready to run tests' -// script { -// try { -// sh 'cd build && ctest --no-compress-output -T Test' -// } catch (exc) { -// -// unstable(message: "${STAGE_NAME} - Testing failed") -// } -// } -// } -// } - - stage('Windows X64 with vcpkg C++17') { + stage('Fedora CTX') { + agent { + dockerfile { + filename 'Dockerfile.fedora' + dir 'ci/jenkins' + label 'docker' + } + } + + steps { + echo "Building on Fedora in ${WORKSPACE}" + checkout scm + sh 'set -x' + sh 'rm -rf build-fedora' + sh 'mkdir build-fedora' + sh 'cd build-fedora && cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_BUILD_TYPE=Release .. && cmake --build . -j $(nproc)' + + echo 'Getting ready to run tests' + script { + try { + sh 'cd build-fedora && ctest --no-compress-output -T Test' + } catch (exc) { + + unstable(message: "${STAGE_NAME} - Testing failed") + } + } + + sh 'rm -rf build-fedora' + } + } + + stage('Windows X64 with vcpkg') { agent {label 'windows'} - + options { - timeout(time: 30, unit: "MINUTES") + // vcpkg now installs and compiles pretty much everything that exists on github if you ask it to prepare boost and openssl. + // It's becoming as bad as js and npm. + timeout(time: 60, unit: "MINUTES") } steps { @@ -625,13 +575,14 @@ pipeline { checkout scm bat script: ''' - PATH=%PATH%;C:\\Program Files\\CMake\\bin;C:\\devel\\vcpkg - vcpkg install zlib openssl boost-fusion boost-filesystem boost-log boost-program-options boost-asio boost-date-time boost-chrono boost-coroutine boost-uuid boost-scope-exit --triplet x64-windows + PATH=%PATH%;C:\\Program Files\\CMake\\bin;C:\\src\\vcpkg;C:\\Program Files\\Git\\bin + vcpkg integrate install + vcpkg install rapidjson gtest zlib openssl boost --triplet x64-windows if %errorlevel% neq 0 exit /b %errorlevel% rmdir /S /Q build mkdir build cd build - cmake -DRESTC_CPP_USE_CPP17=ON -DCMAKE_PREFIX_PATH=C:\\devel\\vcpkg\\installed\\x64-windows\\lib;C:\\devel\\vcpkg\\installed\\x64-windows\\include .. + cmake -DCMAKE_TOOLCHAIN_FILE=C:/src/vcpkg/scripts/buildsystems/vcpkg.cmake .. if %errorlevel% neq 0 exit /b %errorlevel% cmake --build . --config Release if %errorlevel% neq 0 exit /b %errorlevel% @@ -642,25 +593,27 @@ pipeline { script { try { bat script: ''' - PATH=%PATH%;C:\\devel\\vcpkg\\installed\\x64-windows\\bin;C:\\Program Files\\CMake\\bin + PATH=%PATH%;C:\\src\\vcpkg\\installed\\x64-windows\\bin;C:\\Program Files\\CMake\\bin cd build ctest -C Release if %errorlevel% neq 0 exit /b %errorlevel% ''' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - - stage('Windows X64 with vcpkg MT CTX C++17') { + + stage('Windows X64 with vcpkg MT CTX') { agent {label 'windows'} - + options { - timeout(time: 30, unit: "MINUTES") + // vcpkg now installs and compiles pretty much everything that exists on github if you ask it to prepare boost and openssl. + // It's becoming as bad as js and npm. + timeout(time: 60, unit: "MINUTES") } steps { @@ -668,13 +621,14 @@ pipeline { checkout scm bat script: ''' - PATH=%PATH%;C:\\Program Files\\CMake\\bin;C:\\devel\\vcpkg - vcpkg install zlib openssl boost-fusion boost-filesystem boost-log boost-program-options boost-asio boost-date-time boost-chrono boost-coroutine boost-uuid boost-scope-exit --triplet x64-windows + PATH=%PATH%;C:\\Program Files\\CMake\\bin;C:\\src\\vcpkg;C:\\Program Files\\Git\\bin + vcpkg integrate install + vcpkg install rapidjson gtest zlib openssl boost --triplet x64-windows if %errorlevel% neq 0 exit /b %errorlevel% rmdir /S /Q build mkdir build cd build - cmake -DRESTC_CPP_THREADED_CTX=ON -DRESTC_CPP_USE_CPP17=ON -DCMAKE_PREFIX_PATH=C:\\devel\\vcpkg\\installed\\x64-windows\\lib;C:\\devel\\vcpkg\\installed\\x64-windows\\include .. + cmake -DRESTC_CPP_THREADED_CTX=ON -DCMAKE_TOOLCHAIN_FILE=C:/src/vcpkg/scripts/buildsystems/vcpkg.cmake .. if %errorlevel% neq 0 exit /b %errorlevel% cmake --build . --config Release if %errorlevel% neq 0 exit /b %errorlevel% @@ -685,19 +639,20 @@ pipeline { script { try { bat script: ''' - PATH=%PATH%;C:\\devel\\vcpkg\\installed\\x64-windows\\bin;C:\\Program Files\\CMake\\bin + PATH=%PATH%;C:\\src\\vcpkg\\installed\\x64-windows\\bin;C:\\Program Files\\CMake\\bin cd build ctest -C Release if %errorlevel% neq 0 exit /b %errorlevel% ''' } catch (exc) { - + unstable(message: "${STAGE_NAME} - Testing failed") } } } } - } + + } // parallel post { always { diff --git a/ci/vcpkg/vcpkg.json b/ci/vcpkg/vcpkg.json new file mode 100644 index 0000000..fa78bbf --- /dev/null +++ b/ci/vcpkg/vcpkg.json @@ -0,0 +1,22 @@ +{ + "name": "restc-cpp", + "license": "MIT", + "dependencies": [ + "boost-scope-exit", + "boost-system", + "boost-context", + "boost-coroutine", + "boost-filesystem", + "boost-asio", + "boost-chrono", + "boost-date-time", + "boost-log", + "boost-uuid", + "boost-program-options", + "boost-functional", + "zlib", + "openssl", + "gtest", + "rapidjson" + ] +} diff --git a/cmake_scripts/external-projects.cmake b/cmake_scripts/external-projects.cmake index 410f7f5..cfaf6c2 100644 --- a/cmake_scripts/external-projects.cmake +++ b/cmake_scripts/external-projects.cmake @@ -12,38 +12,42 @@ set(EXTERNAL_PROJECTS_INSTALL_PREFIX ${EXTERNAL_PROJECTS_PREFIX}/installed) set(RESTC_EXTERNAL_INSTALLED_LIB_DIR ${EXTERNAL_PROJECTS_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) link_directories(${RESTC_EXTERNAL_INSTALLED_LIB_DIR}) -ExternalProject_Add( - externalRapidJson - PREFIX "${EXTERNAL_PROJECTS_PREFIX}" - GIT_REPOSITORY "https://github.com/Tencent/rapidjson.git" - GIT_TAG "master" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - LOG_INSTALL ON - ) +if (restc_cpp_add_rapidjson) + ExternalProject_Add( + externalRapidJson + PREFIX "${EXTERNAL_PROJECTS_PREFIX}" + GIT_REPOSITORY "https://github.com/Tencent/rapidjson.git" + GIT_TAG "master" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_INSTALL ON + ) + + set(EXTERNAL_RAPIDJSON_INCLUDE_DIR ${EXTERNAL_PROJECTS_PREFIX}/src/externalRapidJson/include/rapidjson) -set(EXTERNAL_RAPIDJSON_INCLUDE_DIR ${EXTERNAL_PROJECTS_PREFIX}/src/externalRapidJson/include/rapidjson) + message(STATUS "EXTERNAL_RAPIDJSON_INCLUDE_DIR: ${EXTERNAL_RAPIDJSON_INCLUDE_DIR}") + + include_directories(${EXTERNAL_PROJECTS_PREFIX}/src/externalRapidJson/include) + + if (INSTALL_RAPIDJSON_HEADERS ) + install(DIRECTORY ${EXTERNAL_RAPIDJSON_INCLUDE_DIR} DESTINATION include) + endif() +endif() +if (restc_cpp_add_logfault) ExternalProject_Add(externalLogfault PREFIX "${EXTERNAL_PROJECTS_PREFIX}" GIT_REPOSITORY "https://github.com/jgaa/logfault.git" - GIT_TAG "master" - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_PROJECTS_INSTALL_PREFIX} + GIT_TAG "${LOGFAULT_TAG}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${EXTERNAL_PROJECTS_INSTALL_PREFIX} + -DCMAKE_GENERATOR=${CMAKE_GENERATOR} ) - -message(STATUS "EXTERNAL_RAPIDJSON_INCLUDE_DIR: ${EXTERNAL_RAPIDJSON_INCLUDE_DIR}") - -if (INSTALL_RAPIDJSON_HEADERS) - install(DIRECTORY ${EXTERNAL_RAPIDJSON_INCLUDE_DIR} DESTINATION include) endif() -include_directories( - ${EXTERNAL_PROJECTS_PREFIX}/src/externalRapidJson/include - ${EXTERNAL_PROJECTS_PREFIX}/src/externalLest/include - ${EXTERNAL_PROJECTS_PREFIX}/installed/include - ) +include_directories(${EXTERNAL_PROJECTS_PREFIX}/installed/include) # If we compile the tests; download and install gtest if it's not found on the target # On ubuntu and debian, you can install `libgtest-dev` to avoid this step. @@ -55,11 +59,6 @@ if (RESTC_CPP_WITH_UNIT_TESTS OR RESTC_CPP_WITH_FUNCTIONALT_TESTS) message("Will download and install googletest as a cmake included project") set(DEPENDS_GTEST googletest) set(GTEST_LIBRARIES gtest) - - if (NOT DEFINED GTEST_TAG) - set(GTEST_TAG "main") - endif() - message("GTEST_TAG: ${GTEST_TAG}") if (WIN32) diff --git a/create-and-run-containers.sh b/create-and-run-containers.sh index ba68e84..e1cb197 100755 --- a/create-and-run-containers.sh +++ b/create-and-run-containers.sh @@ -1,20 +1,36 @@ #!/bin/bash -pushd ci/mock-backends - +# Check if Docker is running docker ps > /dev/null -if [ $? -eq 0 ]; then - echo "Building and starting Docker containers for testing" -else +if [ $? -ne 0 ]; then echo - echo "Cannot run docker-compose commands. " - echo "Please install docker-compose or give this user access to run it" + echo "Cannot run docker commands. " + echo "Please install Docker or give this user access to run it" popd exit -1 fi -docker-compose stop -docker-compose build -docker-compose up -d +# Determine the correct Docker Compose command +DOCKER_COMPOSE="docker compose" +$DOCKER_COMPOSE version > /dev/null 2>&1 + +if [ $? -ne 0 ]; then + DOCKER_COMPOSE="docker-compose" + $DOCKER_COMPOSE version > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Neither 'docker compose' nor 'docker-compose' is available. Please install Docker Compose." + popd + exit -1 + fi +fi + +echo "Using Docker Compose command: $DOCKER_COMPOSE" + +# Run Docker Compose commands +pushd ci/mock-backends +$DOCKER_COMPOSE stop +$DOCKER_COMPOSE build +$DOCKER_COMPOSE up -d docker ps popd + diff --git a/examples/logip/logip.cpp b/examples/logip/logip.cpp index 51cf490..b12fbab 100644 --- a/examples/logip/logip.cpp +++ b/examples/logip/logip.cpp @@ -11,6 +11,7 @@ #include <ctime> +#include <thread> #include "restc-cpp/logging.h" #ifdef RESTC_CPP_LOG_WITH_BOOST_LOG @@ -19,6 +20,9 @@ #include <boost/log/expressions.hpp> #endif +#include <boost/exception/all.hpp> +#include <boost/exception/diagnostic_information.hpp> + #include "restc-cpp/restc-cpp.h" #include "restc-cpp/RequestBuilder.h" #include "restc-cpp/SerializeJson.h" @@ -38,12 +42,13 @@ BOOST_FUSION_ADAPT_STRUCT( string now() { char date[32] = {}; - auto now = time(NULL); + auto now = time(nullptr); strftime(date, sizeof(date), "%Y-%m-%d %H:%M", localtime(&now)); return date; } -int main(int argc, char *argv[]) { +int main(int /*argc*/, char * /*argv*/[]) +{ #ifdef RESTC_CPP_LOG_WITH_BOOST_LOG namespace logging = boost::log; logging::core::get()->set_filter @@ -70,22 +75,14 @@ int main(int argc, char *argv[]) { .Execute()); valid = true; } catch (const boost::exception& ex) { - clog << now() - << "Caught boost exception: " - << boost::diagnostic_information(ex) - << endl; + clog << now() << "Caught boost exception: " << boost::diagnostic_information(ex) + << '\n'; } catch (const exception& ex) { - clog << now() - << "Caught exception: " - << ex.what() - << endl; + clog << now() << "Caught exception: " << ex.what() << '\n'; } if (valid && (current_ip != data.ip)) { - clog << now() - << ' ' - << data.ip - << endl; + clog << now() << ' ' << data.ip << '\n'; current_ip = data.ip; } diff --git a/include/restc-cpp/DataReader.h b/include/restc-cpp/DataReader.h index 66cb5e7..f37db73 100644 --- a/include/restc-cpp/DataReader.h +++ b/include/restc-cpp/DataReader.h @@ -44,7 +44,7 @@ class DataReader { virtual ~DataReader() = default; virtual bool IsEof() const = 0; - virtual boost::asio::const_buffers_1 ReadSome() = 0; + virtual boost_const_buffer ReadSome() = 0; virtual void Finish() = 0; // Make sure there are no pending data for the current request static ptr_t CreateIoReader(const Connection::ptr_t& conn, diff --git a/include/restc-cpp/DataReaderStream.h b/include/restc-cpp/DataReaderStream.h index 71d673d..e7f3fc3 100644 --- a/include/restc-cpp/DataReaderStream.h +++ b/include/restc-cpp/DataReaderStream.h @@ -34,10 +34,10 @@ class DataReaderStream : public DataReader { } /*! Read whatever we have buffered or can get downstream */ - boost::asio::const_buffers_1 ReadSome() override; + boost_const_buffer ReadSome() override; /*! Read up to maxBytes from whatever we have buffered or can get downstream.*/ - boost::asio::const_buffers_1 GetData(size_t maxBytes); + boost_const_buffer GetData(size_t maxBytes); /*! Get one char * diff --git a/include/restc-cpp/DataWriter.h b/include/restc-cpp/DataWriter.h index 8b1e31f..1167a7e 100644 --- a/include/restc-cpp/DataWriter.h +++ b/include/restc-cpp/DataWriter.h @@ -44,10 +44,10 @@ class DataWriter { virtual ~DataWriter() = default; /*! Write some data */ - virtual void Write(boost::asio::const_buffers_1 buffers) = 0; + virtual void Write(boost_const_buffer buffers) = 0; /*! Write without altering the data (headers) */ - virtual void WriteDirect(boost::asio::const_buffers_1 buffers) = 0; + virtual void WriteDirect(boost_const_buffer buffers) = 0; /*! Write some data */ virtual void Write(const write_buffers_t& buffers) = 0; diff --git a/include/restc-cpp/IteratorFromJsonSerializer.h b/include/restc-cpp/IteratorFromJsonSerializer.h index e720eb3..b41c6c6 100644 --- a/include/restc-cpp/IteratorFromJsonSerializer.h +++ b/include/restc-cpp/IteratorFromJsonSerializer.h @@ -18,12 +18,13 @@ class IteratorFromJsonSerializer public: using data_t = typename std::remove_const<typename std::remove_reference<objectT>::type>::type; - class Iterator : public std::iterator< - std::input_iterator_tag, - data_t, - std::ptrdiff_t, - const data_t *, - data_t&> { + class Iterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = data_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; public: Iterator() {} @@ -42,7 +43,7 @@ class IteratorFromJsonSerializer } Iterator(Iterator&& it) - : owner_{it.owner_}, data_{move(it.data_)} {} + : owner_{it.owner_}, data_{std::move(it.data_)} {} Iterator(IteratorFromJsonSerializer *owner) : owner_{owner} {} @@ -70,7 +71,7 @@ class IteratorFromJsonSerializer Iterator& operator = (Iterator&& it) { owner_ = it.owner_; - it.data_ = move(it.data_); + it.data_ = std::move(it.data_); } bool operator == (const Iterator& other) const { @@ -170,7 +171,7 @@ class IteratorFromJsonSerializer RapidJsonDeserializer<objectT> handler( *data, *properties_); json_reader_.Parse(reply_stream_, handler); - return move(data); + return std::move(data); } else if (ch == ']') { reply_stream_.Take(); state_ = State::DONE; diff --git a/include/restc-cpp/RapidJsonReader.h b/include/restc-cpp/RapidJsonReader.h index fccbca3..9325015 100644 --- a/include/restc-cpp/RapidJsonReader.h +++ b/include/restc-cpp/RapidJsonReader.h @@ -6,6 +6,7 @@ #include <assert.h> +#include "restc-cpp/boost_compatibility.h" #include "rapidjson/reader.h" #include "restc-cpp/restc-cpp.h" @@ -96,7 +97,7 @@ class RapidJsonReader { const auto len = boost::asio::buffer_size(buffer); if (len) { - ch_ = boost::asio::buffer_cast<const char*>(buffer); + ch_ = boost_buffer_cast(buffer); end_ = ch_ + len; } else { ch_ = end_ = nullptr; diff --git a/include/restc-cpp/RequestBuilder.h b/include/restc-cpp/RequestBuilder.h index ac02bbe..9ce15fc 100644 --- a/include/restc-cpp/RequestBuilder.h +++ b/include/restc-cpp/RequestBuilder.h @@ -187,7 +187,7 @@ class RequestBuilder args_ = Request::args_t(); } - args_->push_back({move(name), move(value)}); + args_->push_back({std::move(name), std::move(value)}); return *this; } @@ -202,14 +202,14 @@ class RequestBuilder * \param value Value of the argument */ RequestBuilder& Argument(std::string name, int64_t value) { - return Argument(move(name), std::to_string(value)); + return Argument(std::move(name), std::to_string(value)); } /*! Supply your own RequestBody to the request */ RequestBuilder& Body(std::unique_ptr<RequestBody> body) { assert(!body_); - body_ = move(body); + body_ = std::move(body); return *this; } @@ -252,7 +252,7 @@ class RequestBuilder */ RequestBuilder& Data(std::string&& body) { assert(!body_); - body_ = RequestBody::CreateStringBody(move(body)); + body_ = RequestBody::CreateStringBody(std::move(body)); return *this; } @@ -368,7 +368,7 @@ class RequestBuilder } #endif auto req = Request::Create( - url_, type_, ctx_->GetClient(), move(body_), args_, headers_, auth_); + url_, type_, ctx_->GetClient(), std::move(body_), args_, headers_, auth_); auto orig = req->GetProperties(); diff --git a/include/restc-cpp/SerializeJson-cpp17.h b/include/restc-cpp/SerializeJson-cpp17.h index 72911fa..56035b5 100644 --- a/include/restc-cpp/SerializeJson-cpp17.h +++ b/include/restc-cpp/SerializeJson-cpp17.h @@ -8,6 +8,7 @@ #include <stack> #include <type_traits> #include <optional> +#include <list> #include "restc-cpp/RapidJsonReader.h" #include "restc-cpp/RapidJsonWriter.h" diff --git a/include/restc-cpp/SerializeJson.h b/include/restc-cpp/SerializeJson.h index 4b73f95..fd03779 100644 --- a/include/restc-cpp/SerializeJson.h +++ b/include/restc-cpp/SerializeJson.h @@ -11,6 +11,7 @@ #include <set> #include <deque> #include <map> +#include <list> #include <type_traits> #include <boost/iterator/function_input_iterator.hpp> diff --git a/include/restc-cpp/Socket.h b/include/restc-cpp/Socket.h index aed4acd..c9ca098 100644 --- a/include/restc-cpp/Socket.h +++ b/include/restc-cpp/Socket.h @@ -11,6 +11,7 @@ #include <boost/system/error_code.hpp> +#include "restc-cpp/boost_compatibility.h" #include "restc-cpp/typename.h" #include "restc-cpp/logging.h" @@ -34,13 +35,13 @@ class Socket virtual const boost::asio::ip::tcp::socket& GetSocket() const = 0; - virtual std::size_t AsyncReadSome(boost::asio::mutable_buffers_1 buffers, + virtual std::size_t AsyncReadSome(boost_mutable_buffer buffers, boost::asio::yield_context& yield) = 0; - virtual std::size_t AsyncRead(boost::asio::mutable_buffers_1 buffers, + virtual std::size_t AsyncRead(boost_mutable_buffer buffers, boost::asio::yield_context& yield) = 0; - virtual void AsyncWrite(const boost::asio::const_buffers_1& buffers, + virtual void AsyncWrite(const boost_const_buffer& buffers, boost::asio::yield_context& yield) = 0; virtual void AsyncWrite(const write_buffers_t& buffers, @@ -48,7 +49,7 @@ class Socket template <typename T> void AsyncWriteT(const T& buffer, boost::asio::yield_context& yield) { - boost::asio::const_buffers_1 b{buffer.data(), buffer.size()}; + boost_const_buffer b{buffer.data(), buffer.size()}; AsyncWrite(b, yield); } diff --git a/include/restc-cpp/boost_compatibility.h b/include/restc-cpp/boost_compatibility.h new file mode 100644 index 0000000..a610b36 --- /dev/null +++ b/include/restc-cpp/boost_compatibility.h @@ -0,0 +1,217 @@ +#pragma once + +#include <boost/asio.hpp> +#include <boost/version.hpp> +#include <boost/asio/ip/address_v4.hpp> + +/** + * @file + * @brief Compatibility layer for handling breaking changes in Boost.Asio across versions. + * + * Boost frequently introduces breaking changes in its Asio library, with a backward + * compatibility window of about 5 years. This header helps maintain compatibility with + * multiple Boost versions, making it easier to support older versions without requiring + * extensive refactoring. + */ + +#if BOOST_VERSION >= 107000 +#include <boost/coroutine/exceptions.hpp> +/// Macro for catching exceptions in Boost Coroutine, ensuring required handling for `forced_unwind`. +#define RESTC_CPP_IN_COROUTINE_CATCH_ALL \ +catch (boost::coroutines::detail::forced_unwind const&) { \ + throw; /* required for Boost Coroutine! */ \ +} catch (...) +#elif BOOST_VERSION >= 106000 +#include <boost/coroutine2/detail/forced_unwind.hpp> +/// Macro for catching exceptions in Boost Coroutine, ensuring required handling for `forced_unwind`. +#define RESTC_CPP_IN_COROUTINE_CATCH_ALL \ +catch (boost::coroutines::detail::forced_unwind const&) { \ + throw; /* required for Boost Coroutine! */ \ +} catch (...) +#else +static_assert(false, "Unsupported boost version"); +catch (...) +#endif + +#if BOOST_VERSION >= 108100 +/// Macro for handling function signature changes in Boost 1.86 and later. +#define RESTC_CPP_SPAWN_TRAILER \ + , boost::asio::detached +#else +#define RESTC_CPP_SPAWN_TRAILER +#endif + + +namespace restc_cpp { + +#if BOOST_VERSION >= 107000 + /// Type alias for constant buffer in Boost 1.70 and later. + using boost_const_buffer = boost::asio::const_buffer; + /// Type alias for mutable buffer in Boost 1.70 and later. + using boost_mutable_buffer = boost::asio::mutable_buffer; +#else + /// Type alias for constant buffer in Boost versions earlier than 1.70. + using boost_const_buffer = boost::asio::const_buffers_1; + /// Type alias for mutable buffer in Boost versions earlier than 1.70. + using boost_mutable_buffer = boost::asio::mutable_buffers_1; +#endif + +#if BOOST_VERSION >= 106600 + /// Type alias for IO service in Boost 1.66 and later. + using boost_io_service = boost::asio::io_context; + /// Type alias for work guard in Boost 1.66 and later. + using boost_work = boost::asio::executor_work_guard<boost::asio::io_context::executor_type>; +#else + /// Type alias for IO service in Boost versions earlier than 1.66. + using boost_io_service = boost::asio::io_service; + /// Type alias for work guard in Boost versions earlier than 1.66. + using boost_work = boost::asio::io_service::work; +#endif + + /** + * @brief Extracts a const char pointer from a Boost buffer. + * + * @tparam Buffer The type of the buffer. + * @param buffer The buffer to extract the pointer from. + * @return A const char pointer to the data in the buffer. + */ + template <typename Buffer> + const char* boost_buffer_cast(const Buffer& buffer) { +#if BOOST_VERSION >= 107000 + return static_cast<const char*>(buffer.data()); +#else + return boost::asio::buffer_cast<const char*>(buffer); +#endif + } + + /** + * @brief Retrieves the size of a Boost buffer. + * + * @tparam Buffer The type of the buffer. + * @param buffer The buffer to measure. + * @return The size of the buffer in bytes. + */ + template <typename Buffer> + std::size_t boost_buffer_size(const Buffer& buffer) { +#if BOOST_VERSION >= 107000 + return buffer.size(); +#else + return boost::asio::buffer_size(buffer); +#endif + } + + /** + * @brief Dispatches a handler to the IO service. + * + * @tparam IOService The type of the IO service. + * @tparam Handler The type of the handler. + * @param io_service The IO service to use. + * @param handler The handler to dispatch. + */ + template <typename IOService, typename Handler> + void boost_dispatch(IOService *io_service, Handler&& handler) { +#if BOOST_VERSION >= 106600 + io_service->get_executor().dispatch( + std::forward<Handler>(handler), + std::allocator<void>() // Default allocator + ); +#else + io_service->dispatch(std::forward<Handler>(handler)); +#endif + } + + /** + * @brief Wrapper for Boost resolver results for compatibility with older Boost versions. + * + * @tparam Iterator The type of the iterator used for results. + */ + template <typename Iterator> + class ResolverResultsWrapper { + public: + /** + * @brief Constructor. + * @param begin The beginning iterator of the results. + * @param end The end iterator of the results. + */ + explicit ResolverResultsWrapper(const Iterator& begin, const Iterator& end) + : begin_(begin), end_(end) {} + + /** + * @brief Returns the beginning iterator of the results. + * @return The beginning iterator. + */ + Iterator begin() const { return begin_; } + + /** + * @brief Returns the end iterator of the results. + * @return The end iterator. + */ + Iterator end() const { return end_; } + + private: + Iterator begin_; + Iterator end_; + }; + + template <typename Resolver, typename YieldContext> +#if BOOST_VERSION >= 106600 + /// Type alias for resolver results in Boost 1.66 and later. + using ResolverResults = boost::asio::ip::tcp::resolver::results_type; +#else + /// Type alias for resolver results in Boost versions earlier than 1.66. + using ResolverResults = ResolverResultsWrapper<boost::asio::ip::tcp::resolver::iterator>; +#endif + + /** + * @brief Resolves a host and service to endpoints, with compatibility for multiple Boost versions. + * + * @tparam Resolver The type of the resolver. + * @tparam YieldContext The type of the yield context. + * @param resolver The resolver to use for the operation. + * @param host The host to resolve. + * @param service The service to resolve. + * @param yield The yield context for asynchronous operations. + * @return The resolver results, wrapped if necessary for older Boost versions. + */ + template <typename Resolver, typename YieldContext> + ResolverResults<Resolver, YieldContext> boost_resolve( + Resolver& resolver, + const std::string& host, + const std::string& service, + YieldContext yield) + { +#if BOOST_VERSION >= 107000 + return resolver.async_resolve(host, service, yield); +#elif BOOST_VERSION >= 106600 + return resolver.async_resolve(host, service, yield); +#else + boost::asio::ip::tcp::resolver::query query(host, service); + auto it = resolver.async_resolve(query, yield); + auto end = boost::asio::ip::tcp::resolver::iterator(); + return ResolverResultsWrapper(it, end); +#endif + } + + /** + * @brief Creates a Boost endpoint from an IP address and port. + * @param ip_address The IP address as a string. + * @param port The port number. + * @return A Boost TCP endpoint. + */ + boost::asio::ip::tcp::endpoint boost_create_endpoint(const std::string& ip_address, unsigned short port); + + /** + * @brief Converts an IPv4 address from string format to a 32-bit unsigned integer. + * @param ip_address The IPv4 address as a string. + * @return The IPv4 address as a 32-bit unsigned integer. + */ + uint32_t boost_convert_ipv4_to_uint(const std::string& ip_address); + + /** + * @brief Creates a work guard for the given IO service. + * @param ioservice The IO service to manage. + * @return A unique pointer to the work guard. + */ + std::unique_ptr<boost_work> boost_make_work(boost_io_service& ioservice); + +} // namespace restc_cpp diff --git a/include/restc-cpp/helper.h b/include/restc-cpp/helper.h index d2a94ec..5bba863 100644 --- a/include/restc-cpp/helper.h +++ b/include/restc-cpp/helper.h @@ -59,9 +59,17 @@ class ToBuffer { } - operator boost::asio::const_buffers_1 () const { +#if BOOST_VERSION >= 107000 + // For Boost 1.70 and newer + operator boost::asio::const_buffer() const { + return {buf_.data(), buf_.size()}; + } +#else + // For Boost versions older than 1.70 + operator boost::asio::const_buffers_1() const { return {buf_.c_str(), buf_.size()}; } +#endif operator const boost::string_ref() const { return buf_; diff --git a/include/restc-cpp/logging.h b/include/restc-cpp/logging.h index 7e2515a..dd580b0 100644 --- a/include/restc-cpp/logging.h +++ b/include/restc-cpp/logging.h @@ -22,6 +22,7 @@ #include <thread> #include <iomanip> #include <array> +#include <ctime> namespace restc_cpp { @@ -108,6 +109,17 @@ class Logger { } + +inline std::tm *restc_cpp_localtime(const time_t now, std::tm& timeInfo) { +#ifdef _WIN32 + localtime_s(&timeInfo, &now); // Windows-specific +#else + localtime_r(&now, &timeInfo); // POSIX-specific +#endif + + return &timeInfo; +} + //#define RESTC_CPP_TEST_LOGGING_SETUP(level) RestcCppTestStartLogger(level) #define RESTC_CPP_TEST_LOGGING_SETUP(level) RestcCppTestStartLogger("trace") @@ -130,9 +142,9 @@ inline void RestcCppTestStartLogger(const std::string& level = "info") { const std::string& msg) { static const std::array<std::string, 6> levels = {"NONE", "ERROR", "WARN", "INFO", "DEBUG", "TRACE"}; - const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::tm timeInfo = {}; - std::clog << std::put_time(std::localtime(&now), "%c") << ' ' + std::clog << std::put_time(restc_cpp_localtime(time({}), timeInfo), "%c") << ' ' << levels.at(static_cast<size_t>(level)) << ' ' << std::this_thread::get_id() << ' ' << msg << std::endl; diff --git a/include/restc-cpp/restc-cpp.h b/include/restc-cpp/restc-cpp.h index 867035d..f8629e1 100644 --- a/include/restc-cpp/restc-cpp.h +++ b/include/restc-cpp/restc-cpp.h @@ -25,6 +25,7 @@ #include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid_io.hpp> +#include "restc-cpp/boost_compatibility.h" #include "restc-cpp/helper.h" #include "restc-cpp/Connection.h" @@ -52,11 +53,6 @@ # define RESTC_CPP_IO_BUFFER_SIZE (1024 * 16) #endif -#define RESTC_CPP_IN_COROUTINE_CATCH_ALL \ - catch (boost::coroutines::detail::forced_unwind const&) { \ - throw; /* required for Boost Coroutine! */ \ - } catch (...) - namespace restc_cpp { class RestClient; @@ -140,7 +136,7 @@ class Request { Type type = Type::NONE; std::string address; - const std::string& GetName(); + const std::string &GetName() const; }; using args_t = std::deque<Arg>; @@ -258,6 +254,12 @@ class Reply { virtual std::string GetBodyAsString(size_t maxSize = RESTC_CPP_SANE_DATA_LIMIT) = 0; + /*! Get the complete data from the server, but discard it. + * + * Use this to wait for a request to complete before moving on. + */ + virtual void fetchAndIgnore() = 0; + /*! Get some data from the server. * * This is the lowest level to fetch data. Buffers will be @@ -271,7 +273,7 @@ class Reply { * be fetched from the server. The data is safe to use until * the method is called again. */ - virtual boost::asio::const_buffers_1 GetSomeData() = 0; + virtual boost_const_buffer GetSomeData() = 0; /*! Returns true as long as you have not yet pulled all * the data from the response. @@ -336,15 +338,15 @@ class Context { const auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>( duration).count(); - boost::posix_time::microseconds ms(microseconds); + ::boost::posix_time::microseconds ms(microseconds); Sleep(ms); } /*! Asynchronously sleep for a period */ - virtual void Sleep(const boost::posix_time::microseconds& ms) = 0; + virtual void Sleep(const ::boost::posix_time::microseconds& ms) = 0; static std::unique_ptr<Context> - Create(boost::asio::yield_context& yield, + Create(::boost::asio::yield_context& yield, RestClient& rc); }; @@ -415,9 +417,9 @@ class RestClient { prom->set_exception(std::current_exception()); } done_handler.reset(); - }); + } RESTC_CPP_SPAWN_TRAILER); - return move(future); + return future; } /*! Process from within an existing coroutine */ @@ -428,7 +430,7 @@ class RestClient { } virtual std::shared_ptr<ConnectionPool> GetConnectionPool() = 0; - virtual boost::asio::io_service& GetIoService() = 0; + virtual boost_io_service& GetIoService() = 0; #ifdef RESTC_CPP_WITH_TLS virtual std::shared_ptr<boost::asio::ssl::context> GetTLSContext() = 0; @@ -462,30 +464,30 @@ class RestClient { #ifdef RESTC_CPP_WITH_TLS static std::unique_ptr<RestClient> Create( - std::shared_ptr<boost::asio::ssl::context> ctx); + std::shared_ptr<::boost::asio::ssl::context> ctx); static std::unique_ptr<RestClient> Create( - std::shared_ptr<boost::asio::ssl::context> ctx, - const boost::optional<Request::Properties>& properties); + std::shared_ptr<::boost::asio::ssl::context> ctx, + const ::boost::optional<Request::Properties>& properties); static std::unique_ptr<RestClient> Create( - std::shared_ptr<boost::asio::ssl::context> ctx, - const boost::optional<Request::Properties>& properties, - boost::asio::io_service& ioservice); + std::shared_ptr<::boost::asio::ssl::context> ctx, + const ::boost::optional<Request::Properties>& properties, + boost_io_service& ioservice); #endif static std::unique_ptr<RestClient> - Create(const boost::optional<Request::Properties>& properties); + Create(const ::boost::optional<Request::Properties>& properties); static std::unique_ptr<RestClient> CreateUseOwnThread(); static std::unique_ptr<RestClient> - CreateUseOwnThread(const boost::optional<Request::Properties>& properties); + CreateUseOwnThread(const ::boost::optional<Request::Properties>& properties); static std::unique_ptr<RestClient> - Create(const boost::optional<Request::Properties>& properties, - boost::asio::io_service& ioservice); + Create(const ::boost::optional<Request::Properties>& properties, + boost_io_service& ioservice); static std::unique_ptr<RestClient> - Create(boost::asio::io_service& ioservice); + Create(boost_io_service& ioservice); protected: diff --git a/include/restc-cpp/test_helper.h b/include/restc-cpp/test_helper.h index 2cdf860..9079391 100644 --- a/include/restc-cpp/test_helper.h +++ b/include/restc-cpp/test_helper.h @@ -14,10 +14,27 @@ namespace restc_cpp { // Substitute localhost with whatever is in the environment-variable // RESTC_CPP_TEST_DOCKER_ADDRESS inline std::string GetDockerUrl(std::string url) { - const char *docker_addr = std::getenv("RESTC_CPP_TEST_DOCKER_ADDRESS"); +#ifdef _WIN32 + // On Windows, use _dupenv_s to safely retrieve the environment variable + size_t len = 0; + char* docker_addr = nullptr; + errno_t err = _dupenv_s(&docker_addr, &len, "RESTC_CPP_TEST_DOCKER_ADDRESS"); + if (err != 0 || docker_addr == nullptr) { + docker_addr = nullptr; // Ensure docker_addr is nullptr if the variable isn't set + } +#else + // On Linux/macOS, use std::getenv + auto docker_addr = std::getenv("RESTC_CPP_TEST_DOCKER_ADDRESS"); +#endif + if (docker_addr) { boost::replace_all(url, "localhost", docker_addr); +#ifdef _WIN32 + // Free the allocated memory on Windows + free(docker_addr); +#endif } + return url; } diff --git a/include/restc-cpp/url_encode.h b/include/restc-cpp/url_encode.h index 9ffa0dc..9731313 100644 --- a/include/restc-cpp/url_encode.h +++ b/include/restc-cpp/url_encode.h @@ -1,7 +1,16 @@ +#pragma once + +#ifndef RESTC_CPP_URL_ENCODE_H_ +#define RESTC_CPP_URL_ENCODE_H_ + #include "restc-cpp.h" +#include <boost/utility/string_ref.hpp> + namespace restc_cpp { std::string url_encode(const boost::string_ref& src); } // namespace + +#endif // RESTC_CPP_URL_ENCODE_H_ diff --git a/src/ChunkedReaderImpl.cpp b/src/ChunkedReaderImpl.cpp index 259e5ef..3819c9a 100644 --- a/src/ChunkedReaderImpl.cpp +++ b/src/ChunkedReaderImpl.cpp @@ -17,13 +17,11 @@ class ChunkedReaderImpl : public DataReader { public: ChunkedReaderImpl(add_header_fn_t&& fn, unique_ptr<DataReaderStream>&& source) - : stream_{move(source)}, add_header_(move(fn)) + : stream_{std::move(source)}, add_header_(std::move(fn)) { } - bool IsEof() const override { - return stream_->IsEof(); - } + [[nodiscard]] bool IsEof() const override { return stream_->IsEof(); } void Finish() override { ReadSome(); @@ -36,15 +34,16 @@ class ChunkedReaderImpl : public DataReader { } } - string ToPrintable(boost::string_ref buf) const { + [[nodiscard]] static string ToPrintable(boost::string_ref buf) + { ostringstream out; - locale loc; + locale const loc; auto pos = 0; - out << endl; + out << '\n'; for(const auto ch : buf) { - if (!(++pos % line_length)) { - out << endl; + if ((++pos % line_length) == 0u) { + out << '\n'; } if (std::isprint(ch, loc)) { out << ch; @@ -56,18 +55,18 @@ class ChunkedReaderImpl : public DataReader { return out.str(); } - void Log(const boost::asio::const_buffers_1 buffers, const char *tag) { - const auto buf_len = boost::asio::buffer_size(*buffers.begin()); + static void Log(const ::restc_cpp::boost_const_buffer buffers, const char * tag) + { + const auto buf_len = boost_buffer_size(buffers); // At the time of the implementation, there are never multiple buffers. RESTC_CPP_LOG_TRACE_(tag << ' ' << "# " << buf_len << " bytes: " << ToPrintable({ - boost::asio::buffer_cast<const char *>(*buffers.begin()), - buf_len})); + boost_buffer_cast(buffers), buf_len})); } - boost::asio::const_buffers_1 ReadSome() override { + ::restc_cpp::boost_const_buffer ReadSome() override { EatPadding(); @@ -103,18 +102,17 @@ class ChunkedReaderImpl : public DataReader { if (eat_chunk_padding_) { eat_chunk_padding_ = false; - char ch = {}; - if ((ch = stream_->Getc()) != '\r') { + if (stream_->Getc() != '\r') { throw ParseException("Chunk: Missing padding CR!"); } - if ((ch = stream_->Getc()) != '\n') { + if (stream_->Getc() != '\n') { throw ParseException("Chunk: Missing padding LF!"); } } } - boost::asio::const_buffers_1 GetData() { + ::restc_cpp::boost_const_buffer GetData() { auto rval = stream_->GetData(chunk_len_); const auto seg_len = boost::asio::buffer_size(rval); @@ -133,11 +131,11 @@ class ChunkedReaderImpl : public DataReader { size_t chunk_len = 0; char ch = stream_->Getc(); - if (!isxdigit(ch)) { + if (isxdigit(ch) == 0) { throw ParseException("Missing chunk-length in new chunk."); } - for(; isxdigit(ch); ch = stream_->Getc()) { + for (; isxdigit(ch) != 0; ch = stream_->Getc()) { chunk_len *= magic_16; if (ch >= 'a') { chunk_len += magic_10 + (ch - 'a'); @@ -148,8 +146,9 @@ class ChunkedReaderImpl : public DataReader { } } - for(; ch != '\r'; ch = stream_->Getc()) + for (; ch != '\r'; ch = stream_->Getc()) { ; + } if (ch != '\r') { throw ParseException("Missing CR in first chunk line"); @@ -171,7 +170,7 @@ class ChunkedReaderImpl : public DataReader { DataReader::ptr_t DataReader::CreateChunkedReader(add_header_fn_t fn, unique_ptr<DataReaderStream>&& source) { - return make_unique<ChunkedReaderImpl>(move(fn), move(source)); + return make_unique<ChunkedReaderImpl>(std::move(fn), std::move(source)); } diff --git a/src/ChunkedWriterImpl.cpp b/src/ChunkedWriterImpl.cpp index dcdef3d..51e7234 100644 --- a/src/ChunkedWriterImpl.cpp +++ b/src/ChunkedWriterImpl.cpp @@ -16,15 +16,15 @@ namespace restc_cpp { class ChunkedWriterImpl : public DataWriter { public: ChunkedWriterImpl(add_header_fn_t fn, ptr_t&& source) - : next_{move(source)}, add_header_fn_{move(fn)} + : next_{std::move(source)}, add_header_fn_{std::move(fn)} { } - void WriteDirect(boost::asio::const_buffers_1 buffers) override { + void WriteDirect(::restc_cpp::boost_const_buffer buffers) override { next_->WriteDirect(buffers); } - void Write(boost::asio::const_buffers_1 buffers) override { + void Write(::restc_cpp::boost_const_buffer buffers) override { const auto len = boost::asio::buffer_size(buffers); buffers_.resize(2); buffers_[1] = buffers; @@ -34,9 +34,7 @@ class ChunkedWriterImpl : public DataWriter { void Write(const write_buffers_t& buffers) override { const auto len = boost::asio::buffer_size(buffers); buffers_.resize(1); - for(auto &b : buffers) { - buffers_.push_back(b); - } + std::copy(buffers.begin(), buffers.end(), std::back_inserter(buffers_)); DoWrite(len); } @@ -101,7 +99,7 @@ class ChunkedWriterImpl : public DataWriter { DataWriter::ptr_t DataWriter::CreateChunkedWriter(add_header_fn_t fn, ptr_t&& source) { - return make_unique<ChunkedWriterImpl>(move(fn), move(source)); + return make_unique<ChunkedWriterImpl>(std::move(fn), std::move(source)); } } // namespace diff --git a/src/ConnectionPoolImpl.cpp b/src/ConnectionPoolImpl.cpp index c2627be..5dc6e01 100644 --- a/src/ConnectionPoolImpl.cpp +++ b/src/ConnectionPoolImpl.cpp @@ -23,6 +23,11 @@ using namespace std; +#if __cplusplus < 201703L +# include <boost/unordered_map.hpp> +#endif + + namespace restc_cpp { class ConnectionPoolImpl @@ -31,35 +36,73 @@ class ConnectionPoolImpl public: struct Key { - Key(boost::asio::ip::tcp::endpoint ep, - const Connection::Type connectionType) - : endpoint{move(ep)}, type{connectionType} {} + Key(boost::asio::ip::tcp::endpoint ep, Connection::Type connectionType) + : endpoint(std::move(ep)), type(connectionType) {} Key(const Key&) = default; Key(Key&&) = default; ~Key() = default; - Key& operator = (const Key&) = delete; - Key& operator = (Key&&) = delete; + Key& operator=(const Key&) = delete; + Key& operator=(Key&&) = delete; - bool operator < (const Key& key) const { + bool operator<(const Key& key) const { if (static_cast<int>(type) < static_cast<int>(key.type)) { return true; } - + if (static_cast<int>(type) > static_cast<int>(key.type)) { + return false; + } return endpoint < key.endpoint; } - friend std::ostream& operator << (std::ostream& o, const Key& v) { + bool operator==(const Key& key) const { + return type == key.type && endpoint == key.endpoint; + } + + friend std::ostream& operator<<(std::ostream& o, const Key& v) { return o << "{Key " - << (v.type == Connection::Type::HTTPS? "https" : "http") - << "://" - << v.endpoint - << "}"; + << (v.type == Connection::Type::HTTPS ? "https" : "http") + << "://" + << v.endpoint + << "}"; } + // Custom hash function + struct KeyHash { + std::size_t operator()(const Key& key) const { + std::size_t h1 = 0; + + // Hash the binary address data + if (key.endpoint.address().is_v4()) { + // IPv4: 4 bytes + const auto addr = key.endpoint.address().to_v4().to_bytes(); + h1 = std::hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(addr.data())); + } else if (key.endpoint.address().is_v6()) { + // IPv6: 16 bytes + const auto addr = key.endpoint.address().to_v6().to_bytes(); + const uint64_t* parts = reinterpret_cast<const uint64_t*>(addr.data()); + h1 = std::hash<uint64_t>()(parts[0]) ^ std::hash<uint64_t>()(parts[1]); + } + + // Hash the port and type + std::size_t h2 = std::hash<unsigned short>()(key.endpoint.port()); + std::size_t h3 = std::hash<int>()(static_cast<int>(key.type)); + + // Combine the hashes + return h1 ^ (h2 << 1) ^ (h3 << 2); + } + }; + + // Equality comparison for hash table + struct KeyEqual { + bool operator()(const Key& lhs, const Key& rhs) const { + return lhs == rhs; + } + }; + private: - const boost::asio::ip::tcp::endpoint endpoint; - const Connection::Type type; + boost::asio::ip::tcp::endpoint endpoint; + Connection::Type type; }; struct Entry { @@ -70,7 +113,7 @@ class ConnectionPoolImpl const Connection::Type connectionType, Connection::ptr_t conn, const Request::Properties& prop) - : key{move(ep), connectionType}, connection{move(conn)}, ttl{prop.cacheTtlSeconds} + : key{std::move(ep), connectionType}, connection{std::move(conn)}, ttl{prop.cacheTtlSeconds} , created{time(nullptr)} {} friend ostream& operator << (ostream& o, const Entry& e) { @@ -112,7 +155,7 @@ class ConnectionPoolImpl using release_callback_t = std::function<void (const Entry::ptr_t&)>; ConnectionWrapper(Entry::ptr_t entry, release_callback_t on_release) - : on_release_{move(on_release)}, entry_{move(entry)} + : on_release_{std::move(on_release)}, entry_{std::move(entry)} { } @@ -126,11 +169,13 @@ class ConnectionPoolImpl return entry_->GetConnection()->GetSocket(); } - const Socket& GetSocket() const override { - return entry_->GetConnection()->GetSocket(); + [[nodiscard]] const Socket &GetSocket() const override + { + return entry_->GetConnection()->GetSocket(); } - boost::uuids::uuid GetId() const override { + [[nodiscard]] boost::uuids::uuid GetId() const override + { return entry_->GetConnection()->GetId(); } @@ -146,7 +191,7 @@ class ConnectionPoolImpl }; - ConnectionPoolImpl(RestClient& owner) + explicit ConnectionPoolImpl(RestClient& owner) : owner_{owner}, properties_{owner.GetConnectionProperties()} , cache_cleanup_timer_{owner.GetIoService()} { @@ -207,11 +252,13 @@ class ConnectionPoolImpl LOCK_ALWAYS_; cache_cleanup_timer_.expires_from_now( boost::posix_time::seconds(properties_->cacheCleanupIntervalSeconds)); - cache_cleanup_timer_.async_wait(std::bind(&ConnectionPoolImpl::OnCacheCleanup, - shared_from_this(), std::placeholders::_1)); + cache_cleanup_timer_.async_wait([capture0 = shared_from_this()](auto &&PH1) { + capture0->OnCacheCleanup(std::forward<decltype(PH1)>(PH1)); + }); } - void OnCacheCleanup(const boost::system::error_code& error) { + void OnCacheCleanup(const boost::system::error_code &error) + { RESTC_CPP_LOG_TRACE_("OnCacheCleanup: enter"); if (closed_) { RESTC_CPP_LOG_TRACE_("OnCacheCleanup: closed"); @@ -252,7 +299,8 @@ class ConnectionPoolImpl RESTC_CPP_LOG_TRACE_("OnCacheCleanup: leave"); } - void OnRelease(const Entry::ptr_t entry) { + void OnRelease(const Entry::ptr_t &entry) + { { LOCK_ALWAYS_; in_use_.erase(entry->GetKey()); @@ -373,7 +421,7 @@ class ConnectionPoolImpl } auto entry = make_shared<Entry>(ep, connectionType, - make_shared<ConnectionImpl>(move(socket)), + make_shared<ConnectionImpl>(std::move(socket)), *properties_); RESTC_CPP_LOG_TRACE_("Created new connection " << *entry); @@ -395,9 +443,13 @@ class ConnectionPoolImpl #endif std::once_flag close_once_; RestClient& owner_; - multimap<Key, Entry::ptr_t> idle_; - multimap<Key, std::weak_ptr<Entry>> in_use_; - //std::queue<Entry> pending_; +#if __cplusplus < 201703L + boost::unordered_multimap<Key, Entry::ptr_t, Key::KeyHash, Key::KeyEqual> idle_; + boost::unordered_multimap<Key, std::weak_ptr<Entry>, Key::KeyHash, Key::KeyEqual> in_use_; +#else + std::unordered_multimap<Key, Entry::ptr_t, Key::KeyHash, Key::KeyEqual> idle_; + std::unordered_multimap<Key, std::weak_ptr<Entry>, Key::KeyHash, Key::KeyEqual> in_use_; +#endif const Request::Properties::ptr_t properties_; ConnectionWrapper::release_callback_t on_release_; boost::asio::deadline_timer cache_cleanup_timer_; diff --git a/src/DataReaderStream.cpp b/src/DataReaderStream.cpp index 5d23cc1..a04bf29 100644 --- a/src/DataReaderStream.cpp +++ b/src/DataReaderStream.cpp @@ -10,7 +10,7 @@ using namespace std; namespace restc_cpp { DataReaderStream::DataReaderStream(std::unique_ptr<DataReader>&& source) -: source_{move(source)} { +: source_{std::move(source)} { RESTC_CPP_LOG_TRACE_("DataReaderStream: Chained to " << RESTC_CPP_TYPENAME(decltype(*source_))); } @@ -28,16 +28,16 @@ void DataReaderStream::Fetch() { RESTC_CPP_LOG_TRACE_("DataReaderStream::Fetch: EOF"); throw ProtocolException("Fetch(): EOF"); } - curr_ = boost::asio::buffer_cast<const char *>(buf); + curr_ = boost_buffer_cast(buf); end_ = curr_ + boost::asio::buffer_size(buf); } } -boost::asio::const_buffers_1 +::restc_cpp::boost_const_buffer DataReaderStream::ReadSome() { Fetch(); - boost::asio::const_buffers_1 rval = {curr_, + ::restc_cpp::boost_const_buffer rval = {curr_, static_cast<size_t>(end_ - curr_)}; curr_ = end_; RESTC_CPP_LOG_TRACE_("DataReaderStream::ReadSome: Returning buffer with " @@ -50,14 +50,14 @@ DataReaderStream::ReadSome() { return rval; } -boost::asio::const_buffers_1 +::restc_cpp::boost_const_buffer DataReaderStream::GetData(size_t maxBytes) { Fetch(); const auto diff = end_ - curr_; assert(diff >= 0); const auto seg_len = std::min<size_t>(maxBytes, diff); - boost::asio::const_buffers_1 rval = {curr_, seg_len}; + ::restc_cpp::boost_const_buffer rval = {curr_, seg_len}; if (seg_len > 0) { curr_ += seg_len - 1; } @@ -135,7 +135,7 @@ void DataReaderStream::ReadServerResponse(Reply::HttpResponse& response) throw ProtocolException("ReadHeaders(): No CR/LF after HTTP response phrase!"); } - response.reason_phrase = move(value); + response.reason_phrase = std::move(value); RESTC_CPP_LOG_TRACE_("ReadServerResponse: getc_bytes is " << getc_bytes_); RESTC_CPP_LOG_TRACE_("HTTP Response: " @@ -150,7 +150,7 @@ void DataReaderStream::ReadHeaderLines(const add_header_fn_t& addHeader) { constexpr size_t max_headers = 256; while(true) { - char ch; + char ch = 0; string name; string value; for(ch = Getc(); ch != '\r'; ch = Getc()) { @@ -190,7 +190,7 @@ void DataReaderStream::ReadHeaderLines(const add_header_fn_t& addHeader) { } RESTC_CPP_LOG_TRACE_(name << ": " << value); - addHeader(move(name), move(value)); + addHeader(std::move(name), std::move(value)); name.clear(); value.clear(); } @@ -199,11 +199,12 @@ void DataReaderStream::ReadHeaderLines(const add_header_fn_t& addHeader) { std::string DataReaderStream::GetHeaderValue() { constexpr size_t max_header_value_len = 1024 * 4; std::string value; - char ch; + char ch = 0; while(true) { - for (ch = Getc(); ch == ' ' || ch == '\t'; ch = Getc()) + for (ch = Getc(); ch == ' ' || ch == '\t'; ch = Getc()) { ; // skip space + } for (; ch != '\r'; ch = Getc()) { value += ch; diff --git a/src/IoReaderImpl.cpp b/src/IoReaderImpl.cpp index f1d2573..acae520 100644 --- a/src/IoReaderImpl.cpp +++ b/src/IoReaderImpl.cpp @@ -24,7 +24,7 @@ class IoReaderImpl : public DataReader { } - boost::asio::const_buffers_1 ReadSome() override { + ::restc_cpp::boost_const_buffer ReadSome() override { if (auto conn = connection_.lock()) { auto timer = IoTimer::Create("IoReaderImpl", cfg_.msReadTimeout, @@ -33,7 +33,7 @@ class IoReaderImpl : public DataReader { for(size_t retries = 0;; ++retries) { size_t bytes = 0; try { - if (retries) { + if (retries != 0u) { RESTC_CPP_LOG_DEBUG_("IoReaderImpl::ReadSome: taking a nap"); ctx_.Sleep(retries * 20ms); RESTC_CPP_LOG_DEBUG_("IoReaderImpl::ReadSome: Waking up. Will try to read from the socket now."); @@ -68,7 +68,8 @@ class IoReaderImpl : public DataReader { throw ObjectExpiredException("Connection expired"); } - bool IsEof() const override { + [[nodiscard]] bool IsEof() const override + { if (auto conn = connection_.lock()) { return !conn->GetSocket().IsOpen(); } diff --git a/src/IoWriterImpl.cpp b/src/IoWriterImpl.cpp index 256c287..2cee694 100644 --- a/src/IoWriterImpl.cpp +++ b/src/IoWriterImpl.cpp @@ -19,11 +19,11 @@ class IoWriterImpl : public DataWriter { { } - void WriteDirect(boost::asio::const_buffers_1 buffers) override { + void WriteDirect(::restc_cpp::boost_const_buffer buffers) override { Write(buffers); } - void Write(boost::asio::const_buffers_1 buffers) override { + void Write(::restc_cpp::boost_const_buffer buffers) override { { auto timer = IoTimer::Create("IoWriterImpl", @@ -59,9 +59,7 @@ class IoWriterImpl : public DataWriter { ; } - void SetHeaders(Request::headers_t& ) override { - ; - } + void SetHeaders(Request::headers_t & /*headers*/) override { ; } private: Context& ctx_; diff --git a/src/NoBodyReaderImpl.cpp b/src/NoBodyReaderImpl.cpp index 1fcf01a..c19e302 100644 --- a/src/NoBodyReaderImpl.cpp +++ b/src/NoBodyReaderImpl.cpp @@ -10,14 +10,12 @@ class NoBodyReaderImpl : public DataReader { public: NoBodyReaderImpl() = default; - bool IsEof() const override { - return true; - } + [[nodiscard]] bool IsEof() const override { return true; } void Finish() override { } - boost::asio::const_buffers_1 ReadSome() override { + ::restc_cpp::boost_const_buffer ReadSome() override { return {nullptr, 0}; } }; diff --git a/src/PlainReaderImpl.cpp b/src/PlainReaderImpl.cpp index 3d17dfa..86f9c22 100644 --- a/src/PlainReaderImpl.cpp +++ b/src/PlainReaderImpl.cpp @@ -13,18 +13,17 @@ class PlainReaderImpl : public DataReader { PlainReaderImpl(size_t contentLength, ptr_t&& source) : remaining_{contentLength}, - source_{move(source)} {} + source_{std::move(source)} {} - bool IsEof() const override { - return remaining_ == 0; - } + [[nodiscard]] bool IsEof() const override { return remaining_ == 0; } void Finish() override { - if (source_) + if (source_) { source_->Finish(); + } } - boost::asio::const_buffers_1 ReadSome() override { + ::restc_cpp::boost_const_buffer ReadSome() override { if (IsEof()) { return {nullptr, 0}; @@ -48,7 +47,7 @@ class PlainReaderImpl : public DataReader { DataReader::ptr_t DataReader::CreatePlainReader(size_t contentLength, ptr_t&& source) { - return make_unique<PlainReaderImpl>(contentLength, move(source)); + return make_unique<PlainReaderImpl>(contentLength, std::move(source)); } diff --git a/src/PlainWriterImpl.cpp b/src/PlainWriterImpl.cpp index 21b870c..d49fdfb 100644 --- a/src/PlainWriterImpl.cpp +++ b/src/PlainWriterImpl.cpp @@ -13,15 +13,15 @@ namespace restc_cpp { class PlainWriterImpl : public DataWriter { public: PlainWriterImpl(size_t contentLength, ptr_t&& source) - : next_{move(source)}, content_length_{contentLength} + : next_{std::move(source)}, content_length_{contentLength} { } - void WriteDirect(boost::asio::const_buffers_1 buffers) override { + void WriteDirect(::restc_cpp::boost_const_buffer buffers) override { next_->WriteDirect(buffers); } - void Write(boost::asio::const_buffers_1 buffers) override { + void Write(::restc_cpp::boost_const_buffer buffers) override { next_->Write(buffers); } @@ -47,7 +47,7 @@ class PlainWriterImpl : public DataWriter { DataWriter::ptr_t DataWriter::CreatePlainWriter(size_t contentLength, ptr_t&& source) { - return make_unique<PlainWriterImpl>(contentLength, move(source)); + return make_unique<PlainWriterImpl>(contentLength, std::move(source)); } } // namespace diff --git a/src/ReplyImpl.cpp b/src/ReplyImpl.cpp index 7e9654e..058f043 100644 --- a/src/ReplyImpl.cpp +++ b/src/ReplyImpl.cpp @@ -44,7 +44,7 @@ ReplyImpl::ReplyImpl(Connection::ptr_t connection, RestClient& owner, Request::Properties::ptr_t& properties, Request::Type type) -: connection_{move(connection)}, ctx_{ctx} +: connection_{std::move(connection)}, ctx_{ctx} , properties_{properties} , owner_{owner} , connection_id_(connection_ ? connection_->GetId() @@ -57,7 +57,7 @@ ReplyImpl::ReplyImpl(Connection::ptr_t connection, Context& ctx, RestClient& owner, Request::Type type) -: connection_{move(connection)}, ctx_{ctx} +: connection_{std::move(connection)}, ctx_{ctx} , properties_{owner.GetConnectionProperties()} , owner_{owner} , connection_id_(connection_ ? connection_->GetId() @@ -75,7 +75,7 @@ ReplyImpl::~ReplyImpl() { << "received data."); connection_->GetSocket().Close(); connection_.reset(); - } catch(std::exception& ex) { + } catch(const std::exception& ex) { RESTC_CPP_LOG_WARN_("~ReplyImpl(): Caught exception:" << ex.what()); } } @@ -93,14 +93,14 @@ void ReplyImpl::StartReceiveFromServer(DataReader::ptr_t&& reader) { connection_); assert(reader); - auto stream = make_unique<DataReaderStream>(move(reader)); + auto stream = make_unique<DataReaderStream>(std::move(reader)); stream->ReadServerResponse(response_); stream->ReadHeaderLines( [this](std::string&& name, std::string&& value) { - headers_.insert({move(name), move(value)}); + headers_.insert({std::move(name), std::move(value)}); }); - HandleContentType(move(stream)); + HandleContentType(std::move(stream)); HandleConnectionLifetime(); HandleDecompression(); CheckIfWeAreDone(); @@ -115,13 +115,13 @@ void ReplyImpl::HandleContentType(unique_ptr<DataReaderStream>&& stream) { reader_ = DataReader::CreateNoBodyReader(); } else if (const auto cl = GetHeader(content_len_name)) { content_length_ = stoi(*cl); - reader_ = DataReader::CreatePlainReader(*content_length_, move(stream)); + reader_ = DataReader::CreatePlainReader(*content_length_, std::move(stream)); } else { auto te = GetHeader(transfer_encoding_name); if (te && ciEqLibC()(*te, chunked_name)) { reader_ = DataReader::CreateChunkedReader([this](string&& name, string&& value) { - headers_[name] = move(value); - }, move(stream)); + headers_[name] = std::move(value); + }, std::move(stream)); } else { reader_ = DataReader::CreateNoBodyReader(); } @@ -154,15 +154,15 @@ void ReplyImpl::HandleDecompression() { return; } - boost::tokenizer<> tok(*te_hdr); + boost::tokenizer<> const tok(*te_hdr); for(auto it = tok.begin(); it != tok.end(); ++it) { #ifdef RESTC_CPP_WITH_ZLIB if (ciEqLibC()(gzip, *it)) { RESTC_CPP_LOG_TRACE_("Adding gzip reader to " << *connection_); - reader_ = DataReader::CreateGzipReader(move(reader_)); + reader_ = DataReader::CreateGzipReader(std::move(reader_)); } else if (ciEqLibC()(deflate, *it)) { RESTC_CPP_LOG_TRACE_("Adding deflate reader to " << *connection_); - reader_ = DataReader::CreateZipReader(move(reader_)); + reader_ = DataReader::CreateZipReader(std::move(reader_)); } else #endif // RESTC_CPP_WITH_ZLIB { @@ -174,10 +174,10 @@ void ReplyImpl::HandleDecompression() { } } -boost::asio::const_buffers_1 ReplyImpl::GetSomeData() { +::restc_cpp::boost_const_buffer ReplyImpl::GetSomeData() { auto rval = reader_ ? reader_->ReadSome() - : boost::asio::const_buffers_1{nullptr, 0}; + : ::restc_cpp::boost_const_buffer{nullptr, 0}; CheckIfWeAreDone(); return rval; } @@ -197,7 +197,7 @@ string ReplyImpl::GetBodyAsString(const size_t maxSize) { "Too much data for the curent buffer limit."); } - buffer.append(boost::asio::buffer_cast<const char*>(data), + buffer.append(boost_buffer_cast(data), buffer_size); } @@ -205,6 +205,15 @@ string ReplyImpl::GetBodyAsString(const size_t maxSize) { return buffer; } +void ReplyImpl::fetchAndIgnore() +{ + while(!IsEof()) { + reader_->ReadSome(); + } + + ReleaseConnection(); +} + void ReplyImpl::CheckIfWeAreDone() { if (reader_ && reader_->IsEof()) { reader_->Finish(); @@ -235,7 +244,7 @@ ReplyImpl::Create(Connection::ptr_t connection, Request::Properties::ptr_t& properties, Request::Type type) { - return make_unique<ReplyImpl>(move(connection), ctx, owner, properties, type); + return make_unique<ReplyImpl>(std::move(connection), ctx, owner, properties, type); } } // restc_cpp diff --git a/src/ReplyImpl.h b/src/ReplyImpl.h index 0d25e82..f158ed3 100644 --- a/src/ReplyImpl.h +++ b/src/ReplyImpl.h @@ -45,11 +45,13 @@ class ReplyImpl : public Reply { return response_; } - boost::asio::const_buffers_1 GetSomeData() override; + boost_const_buffer GetSomeData() override; string GetBodyAsString(size_t maxSize = RESTC_CPP_SANE_DATA_LIMIT) override; + void fetchAndIgnore() override; + bool MoreDataToRead() override { return !IsEof(); } @@ -65,8 +67,8 @@ class ReplyImpl : public Reply { Request::Properties::ptr_t& properties, Request::Type type); - static boost::string_ref b2sr(boost::asio::const_buffers_1 buffer) { - return { boost::asio::buffer_cast<const char*>(buffer), + static boost::string_ref b2sr(boost_const_buffer buffer) { + return { boost_buffer_cast(buffer), boost::asio::buffer_size(buffer)}; } diff --git a/src/RequestBodyFileImpl.cpp b/src/RequestBodyFileImpl.cpp index fc63709..7f20a2c 100644 --- a/src/RequestBodyFileImpl.cpp +++ b/src/RequestBodyFileImpl.cpp @@ -19,19 +19,15 @@ class RequestBodyFileImpl : public RequestBody { public: RequestBodyFileImpl(boost::filesystem::path path) - : path_{move(path)} + : path_{std::move(path)} , size_{boost::filesystem::file_size(path_)} { file_ = make_unique<ifstream>(path_.string(), ios::binary); } - Type GetType() const noexcept override { - return Type::FIXED_SIZE; - } + [[nodiscard]] Type GetType() const noexcept override { return Type::FIXED_SIZE; } - uint64_t GetFixedSize() const override { - return size_; - } + [[nodiscard]] uint64_t GetFixedSize() const override { return size_; } bool GetData(write_buffers_t & buffers) override { const auto bytes_left = size_ - bytes_read_; @@ -50,11 +46,11 @@ class RequestBodyFileImpl : public RequestBody if (read_this_time == 0) { const auto err = errno; throw IoException(string{"file read failed: "} - + to_string(err) + " " + strerror(err)); + + to_string(err) + " " + std::system_category().message(err)); } bytes_read_ += read_this_time; - buffers.push_back({buffer_.data(), read_this_time}); + buffers.emplace_back(buffer_.data(), read_this_time); return true; } @@ -81,7 +77,7 @@ class RequestBodyFileImpl : public RequestBody unique_ptr<RequestBody> RequestBody::CreateFileBody( boost::filesystem::path path) { - return make_unique<impl::RequestBodyFileImpl>(move(path)); + return make_unique<impl::RequestBodyFileImpl>(std::move(path)); } } // restc_cpp diff --git a/src/RequestBodyStringImpl.cpp b/src/RequestBodyStringImpl.cpp index 0f26078..fe81d5d 100644 --- a/src/RequestBodyStringImpl.cpp +++ b/src/RequestBodyStringImpl.cpp @@ -15,25 +15,21 @@ namespace impl { class RequestBodyStringImpl : public RequestBody { public: - RequestBodyStringImpl(string body) - : body_{move(body)} + explicit RequestBodyStringImpl(string body) + : body_{std::move(body)} { } - Type GetType() const noexcept override { - return Type::FIXED_SIZE; - } + [[nodiscard]] Type GetType() const noexcept override { return Type::FIXED_SIZE; } - std::uint64_t GetFixedSize() const override { - return body_.size(); - } + [[nodiscard]] std::uint64_t GetFixedSize() const override { return body_.size(); } bool GetData(write_buffers_t & buffers) override { if (eof_) { return false; } - buffers.push_back({body_.c_str(), body_.size()}); + buffers.emplace_back(body_.c_str(), body_.size()); eof_ = true; return true; } @@ -42,10 +38,7 @@ class RequestBodyStringImpl : public RequestBody eof_ = false; } - std::string GetCopyOfData() const override { - return body_; - } - + [[nodiscard]] std::string GetCopyOfData() const override { return body_; } private: string body_; @@ -58,7 +51,7 @@ class RequestBodyStringImpl : public RequestBody std::unique_ptr<RequestBody> RequestBody::CreateStringBody( std::string body) { - return make_unique<impl::RequestBodyStringImpl>(move(body)); + return make_unique<impl::RequestBodyStringImpl>(std::move(body)); } } // restc_cpp diff --git a/src/RequestImpl.cpp b/src/RequestImpl.cpp index 1139209..ebe1c40 100644 --- a/src/RequestImpl.cpp +++ b/src/RequestImpl.cpp @@ -22,6 +22,10 @@ using namespace std::string_literals; namespace { +string ref_to_string(boost::string_ref ref) { + return {ref.data(), ref.size()}; +} + // We support versions of boost prior to the introduction of this convenience function boost::asio::ip::address_v6 make_address_v6(const char* str, @@ -29,9 +33,14 @@ boost::asio::ip::address_v6 make_address_v6(const char* str, { boost::asio::ip::address_v6::bytes_type bytes; unsigned long scope_id = 0; - if (boost::asio::detail::socket_ops::inet_pton( - BOOST_ASIO_OS_DEF(AF_INET6), str, &bytes[0], &scope_id, ec) <= 0) - return boost::asio::ip::address_v6(); + if (boost::asio::detail::socket_ops::inet_pton(BOOST_ASIO_OS_DEF(AF_INET6), + str, + bytes.data(), + &scope_id, + ec) + <= 0) { + return {}; + } return boost::asio::ip::address_v6(bytes, scope_id); } @@ -39,24 +48,25 @@ boost::asio::ip::address_v4 make_address_v4(const char* str, boost::system::error_code& ec) { boost::asio::ip::address_v4::bytes_type bytes; - if (boost::asio::detail::socket_ops::inet_pton( - BOOST_ASIO_OS_DEF(AF_INET), str, &bytes, 0, ec) <= 0) - return boost::asio::ip::address_v4(); + if (boost::asio::detail::socket_ops::inet_pton(BOOST_ASIO_OS_DEF(AF_INET), str, &bytes, nullptr, ec) + <= 0) { + return {}; + } return boost::asio::ip::address_v4(bytes); } boost::asio::ip::address make_address(const char* str, boost::system::error_code& ec) { - boost::asio::ip::address_v6 ipv6_address = - make_address_v6(str, ec); - if (!ec) - return boost::asio::ip::address{ipv6_address}; + boost::asio::ip::address_v6 const ipv6_address = make_address_v6(str, ec); + if (!ec) { + return boost::asio::ip::address{ipv6_address}; + } - boost::asio::ip::address_v4 ipv4_address = - make_address_v4(str, ec); - if (!ec) - return boost::asio::ip::address{ipv4_address}; + boost::asio::ip::address_v4 const ipv4_address = make_address_v4(str, ec); + if (!ec) { + return boost::asio::ip::address{ipv4_address}; + } return boost::asio::ip::address{}; } @@ -65,7 +75,8 @@ boost::asio::ip::address make_address(const char* str, namespace restc_cpp { -const std::string& Request::Proxy::GetName() { +const std::string &Request::Proxy::GetName() const +{ static const array<string, 3> names = { "NONE", "HTTP", "SOCKS5" }; @@ -86,18 +97,18 @@ constexpr char SOCKS5_HOSTNAME_ADDR = 0x03; * ipv4: 1.2.3.4:123 -> "1.2.3.4", 123 * ipv6: [fe80::4479:f6ff:fea3:aa23]:123 -> "fe80::4479:f6ff:fea3:aa23", 123 */ -pair<string, uint16_t> ParseAddress(const std::string addr) { +pair<string, uint16_t> ParseAddress(const std::string& addr) { auto pos = addr.find('['); // IPV6 string host; string port; if (pos != string::npos) { - auto host = addr.substr(1); // strip '[' + host = addr.substr(1); // strip '[' pos = host.find(']'); if (pos == string::npos) { throw ParseException{"IPv6 address must have a ']'"}; } port = host.substr(pos); - host = host.substr(0, pos); + host.resize(pos); if (port.size() < 3 || (host.at(1) != ':')) { throw ParseException{"Need `]:<port>` in "s + addr}; @@ -144,7 +155,7 @@ void ParseAddressIntoSocke5ConnectRequest(const std::string& addr, if (host.size() > SOCKS5_MAX_HOSTNAME_LEN) { throw ParseException{"SOCKS5 address must be <= 255 bytes"}; } - if (host.size() < 1) { + if (host.empty()) { throw ParseException{"SOCKS5 address must be > 1 byte"}; } @@ -172,14 +183,14 @@ void ParseAddressIntoSocke5ConnectRequest(const std::string& addr, } // Add 2 byte port number in network byte order - assert(sizeof(final_port) >= 2); - const unsigned char *p = reinterpret_cast<const unsigned char *>(&final_port); + static_assert(sizeof(final_port) >= 2); + const auto *p = reinterpret_cast<const unsigned char *>(&final_port); out.push_back(*p); out.push_back(*(p +1)); } // Return 0 whene there is no more bytes to read -size_t ValidateCompleteSocks5ConnectReply(uint8_t *buf, size_t len) { +size_t ValidateCompleteSocks5ConnectReply(const uint8_t *buf, size_t len) { if (len < 5) { throw RestcCppException{"SOCKS5 server connect reply must start at minimum 5 bytes"s}; } @@ -205,7 +216,7 @@ size_t ValidateCompleteSocks5ConnectReply(uint8_t *buf, size_t len) { break; case SOCKS5_HOSTNAME_ADDR: if (len < 4) { - return false; // We need the length field... + return 0u; // We need the length field... } hdr_len += buf[3] + 1 + 1; break; @@ -223,7 +234,7 @@ size_t ValidateCompleteSocks5ConnectReply(uint8_t *buf, size_t len) { void DoSocks5Handshake(Connection& connection, const Url& url, - const Request::Properties properties, + const Request::Properties& properties, Context& ctx) { assert(properties.proxy.type == Request::Proxy::Type::SOCKS5); @@ -231,7 +242,7 @@ void DoSocks5Handshake(Connection& connection, // Send no-auth handshake { - array<uint8_t, 3> hello = {SOCKS5_VERSION, 1, 0}; + array<uint8_t, 3> const hello = {SOCKS5_VERSION, 1, 0}; RESTC_CPP_LOG_TRACE_("DoSocks5Handshake - saying hello"); sck.AsyncWriteT(hello, ctx.GetYield()); } @@ -253,7 +264,7 @@ void DoSocks5Handshake(Connection& connection, { vector<uint8_t> params; - auto addr = url.GetHost().to_string() + ":" + to_string(url.GetPort()); + auto addr = url.GetHost().to_string() + ":" + ref_to_string(url.GetPort()); ParseAddressIntoSocke5ConnectRequest(addr, params); RESTC_CPP_LOG_TRACE_("DoSocks5Handshake - saying connect to " << url.GetHost().to_string() << ":" << url.GetPort()); @@ -261,7 +272,7 @@ void DoSocks5Handshake(Connection& connection, } { - array<uint8_t, 255 + 6> reply; + array<uint8_t, 255 + 6> reply{}; size_t remaining = 5; // Minimum length uint8_t *next = reply.data(); @@ -294,7 +305,7 @@ class RequestImpl : public Request { RedirectException(RedirectException &&) = default; RedirectException(int redirCode, string redirUrl, std::unique_ptr<Reply> reply) - : code{redirCode}, url{move(redirUrl)}, redirectReply{move(reply)} + : code{redirCode}, url{std::move(redirUrl)}, redirectReply{std::move(reply)} {} RedirectException() = delete; @@ -302,9 +313,9 @@ class RequestImpl : public Request { RedirectException& operator = (const RedirectException&) = delete; RedirectException& operator = (RedirectException&&) = delete; - int GetCode() const noexcept { return code; }; - const std::string& GetUrl() const noexcept { return url; } - Reply& GetRedirectReply() const { return *redirectReply; } + [[nodiscard]] int GetCode() const noexcept { return code; }; + [[nodiscard]] const std::string &GetUrl() const noexcept { return url; } + [[nodiscard]] Reply &GetRedirectReply() const { return *redirectReply; } private: const int code; @@ -319,17 +330,16 @@ class RequestImpl : public Request { const boost::optional<args_t>& args, const boost::optional<headers_t>& headers, const boost::optional<auth_t>& auth = {}) - : url_{move(url)}, parsed_url_{url_.c_str()} , request_type_{requestType} - , body_{move(body)}, owner_{owner} + : url_{std::move(url)}, parsed_url_{url_.c_str()} , request_type_{requestType} + , body_{std::move(body)}, owner_{owner} { if (args || headers || auth) { - Properties::ptr_t props = owner_.GetConnectionProperties(); - assert(props); - properties_ = make_shared<Properties>(*props); + Properties::ptr_t const props = owner_.GetConnectionProperties(); + assert(props); + properties_ = make_shared<Properties>(*props); - if (args) { - properties_->args.insert(properties_->args.end(), - args->begin(), args->end()); + if (args) { + properties_->args.insert(properties_->args.end(), args->begin(), args->end()); } merge_map(headers, properties_->headers); @@ -364,8 +374,12 @@ class RequestImpl : public Request { valb-=magic_6; } } - if (valb>-magic_6) out.push_back(alphabeth[((val<<magic_8)>>(valb+magic_8))&magic_3f]); - while (out.size()%magic_4) out.push_back('='); + if (valb > -magic_6) { + out.push_back(alphabeth[((val << magic_8) >> (valb + magic_8)) & magic_3f]); + } + while ((out.size() % magic_4) != 0u) { + out.push_back('='); + } return out; } @@ -376,19 +390,24 @@ class RequestImpl : public Request { std::string pre_base = auth.name + ':' + auth.passwd; properties_->headers[authorization] = basic_sp + Base64Encode(pre_base); - std::memset(&pre_base[0], 0, pre_base.capacity()); +#if __cplusplus >= 201703L // C++17 or later + std::memset(pre_base.data(), 0, pre_base.capacity()); +#else // C++14 or earlier + if (!pre_base.empty()) { + std::memset(&pre_base[0], 0, pre_base.capacity()); + } +#endif pre_base.clear(); } - const Properties& GetProperties() const override { - return *properties_; - } + [[nodiscard]] const Properties &GetProperties() const override { return *properties_; } void SetProperties(Properties::ptr_t propreties) override { - properties_ = move(propreties); + properties_ = std::move(propreties); } - const std::string& Verb(const Type requestType) { + static const std::string &Verb(const Type requestType) + { static const std::array<std::string, 7> names = {{ "GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH" @@ -397,7 +416,8 @@ class RequestImpl : public Request { return names.at(static_cast<size_t>(requestType)); } - uint64_t GetContentBytesSent() const noexcept { + [[nodiscard]] uint64_t GetContentBytesSent() const noexcept + { return bytes_sent_ - header_size_; } @@ -425,7 +445,7 @@ class RequestImpl : public Request { << "' --> '" << url << "') "); - url_ = move(url); + url_ = std::move(url); parsed_url_ = url_.c_str(); add_url_args_ = false; // Use whatever arguments we got in the redirect } @@ -434,7 +454,8 @@ class RequestImpl : public Request { private: - void ValidateReply(const Reply& reply) { + static void ValidateReply(const Reply &reply) + { // Silence the cursed clang tidy! constexpr auto magic_2 = 2; constexpr auto magic_100 = 100; @@ -447,7 +468,8 @@ class RequestImpl : public Request { constexpr auto http_408 = 408; const auto& response = reply.GetHttpResponse(); - if ((response.status_code / magic_100) > magic_2) switch(response.status_code) { + if ((response.status_code / magic_100) > magic_2) { + switch (response.status_code) { case http_401: throw HttpAuthenticationException(response); case http_403: @@ -464,6 +486,7 @@ class RequestImpl : public Request { throw HttpRequestTimeOutException(response); default: throw RequestFailedWithErrorException(response); + } } } @@ -482,8 +505,9 @@ class RequestImpl : public Request { } // Add arguments to the path as ?name=value&name=value... - bool first_arg = true; + if (add_url_args_) { + bool first_arg = true; // Normal processing. request_buffer << url_encode(parsed_url_.GetPath()); for(const auto& arg : properties_->args) { @@ -527,7 +551,7 @@ class RequestImpl : public Request { return request_buffer.str(); } - boost::asio::ip::tcp::resolver::query GetRequestEndpoint() { + std::pair<std::string, std::string> GetRequestEndpoint() { const auto proxy_type = properties_->proxy.type; if (proxy_type == Request::Proxy::Type::SOCKS5) { @@ -543,7 +567,7 @@ class RequestImpl : public Request { } if (proxy_type == Request::Proxy::Type::HTTP) { - Url proxy {properties_->proxy.address.c_str()}; + Url const proxy{properties_->proxy.address.c_str()}; RESTC_CPP_LOG_TRACE_("Using " << properties_->proxy.GetName() << " Proxy at: " @@ -557,6 +581,8 @@ class RequestImpl : public Request { parsed_url_.GetPort().to_string()}; } + + /* If we are redirected, we need to reset the body * Some body implementations may not support that and throw in Reset() */ @@ -573,7 +599,8 @@ class RequestImpl : public Request { boost::asio::ip::tcp::endpoint ToEp(const std::string& endp, const protocolT& protocol, Context& ctx) const { - string host, port; + string host; + string port; auto endipv6 = endp.find(']'); if (endipv6 == string::npos) { @@ -604,18 +631,15 @@ class RequestImpl : public Request { return {protocol, static_cast<uint16_t>(port_num)}; } - boost::asio::ip::tcp::resolver::query q{host, port}; boost::asio::ip::tcp::resolver resolver(owner_.GetIoService()); + auto results = boost_resolve(resolver, host, port, ctx.GetYield()); - auto ep = resolver.async_resolve(q, ctx.GetYield()); - const decltype(ep) addr_end; - for(; ep != addr_end; ++ep) - if (ep != addr_end) { - - RESTC_CPP_LOG_TRACE_("ep=" << ep->endpoint() << ", protocol=" << ep->endpoint().protocol().protocol()); + for (auto it = results.begin(); it != results.end(); ++it) { + const auto endpoint = it->endpoint(); + RESTC_CPP_LOG_TRACE_("ep=" << endpoint << ", protocol=" << endpoint.protocol().protocol()); - if (protocol == ep->endpoint().protocol()) { - return ep->endpoint(); + if (protocol == endpoint.protocol()) { + return endpoint; } RESTC_CPP_LOG_TRACE_("Incorrect protocol, looping for next alternative"); @@ -662,23 +686,13 @@ class RequestImpl : public Request { boost::asio::ip::tcp::resolver resolver(owner_.GetIoService()); // Resolve the hostname - const auto query = GetRequestEndpoint(); + const auto [host, service] = GetRequestEndpoint(); - RESTC_CPP_LOG_TRACE_("Resolving " << query.host_name() << ":" - << query.service_name()); - - auto address_it = resolver.async_resolve(query, - ctx.GetYield()); - const decltype(address_it) addr_end; - - for(; address_it != addr_end; ++address_it) { -// if (owner_.IsClosing()) { -// RESTC_CPP_LOG_DEBUG_("RequestImpl::Connect: The rest client is closed (at first loop). Aborting."); -// throw FailedToConnectException("Failed to connect (closed)"); -// } - - const auto endpoint = address_it->endpoint(); + RESTC_CPP_LOG_TRACE_("Resolving " << host << ":" << service); + auto results = boost_resolve(resolver, host, service, ctx.GetYield()); + for (auto it = results.begin(); it != results.end(); ++it) { + const auto endpoint = it->endpoint(); RESTC_CPP_LOG_TRACE_("Trying endpoint " << endpoint); for(size_t retries = 0; retries < 8; ++retries) { @@ -726,17 +740,11 @@ class RequestImpl : public Request { } } - -// if (owner_.IsClosed()) { -// RESTC_CPP_LOG_DEBUG_("RequestImpl::Connect: The rest client is closed. Aborting."); -// throw FailedToConnectException("Failed to connect (closed)"); -// } - auto timer = IoTimer::Create(timer_name, properties_->connectTimeoutMs, connection); try { - if (retries) { + if (retries != 0u) { RESTC_CPP_LOG_DEBUG_("RequestImpl::Connect: taking a nap"); ctx.Sleep(retries * 20ms); RESTC_CPP_LOG_DEBUG_("RequestImpl::Connect: Waking up. Will try to read from the socket now."); @@ -753,8 +761,12 @@ class RequestImpl : public Request { } RESTC_CPP_LOG_TRACE_("RequestImpl::Connect: calling AsyncConnect --> " << endpoint); + + // 2025-01-18: [jgaa] Changing the hostname from the one digged up by the resolver in older versions of boost + // to the one we actually want to connect to. This affects certificate validation for TLS. + // I believe this is the correct way to do it. connection->GetSocket().AsyncConnect( - endpoint, address_it->host_name(), + endpoint, host, properties_->tcpNodelay, ctx.GetYield()); RESTC_CPP_LOG_TRACE_("RequestImpl::Connect: OK AsyncConnect --> " << endpoint); return connection; @@ -804,8 +816,7 @@ class RequestImpl : public Request { properties_->beforeWriteFn(); } - while(boost::asio::buffer_size(write_buffer)) - { + while (boost::asio::buffer_size(write_buffer) != 0u) { auto timer = IoTimer::Create(timer_name, properties_->sendTimeoutMs, connection_); @@ -815,9 +826,7 @@ class RequestImpl : public Request { auto b = write_buffer[0]; - writer_->WriteDirect( - {boost::asio::buffer_cast<const char *>(b), - boost::asio::buffer_size(b)}); + writer_->WriteDirect({boost_buffer_cast(b), boost_buffer_size(b)}); have_sent_headers = true; @@ -869,25 +878,25 @@ class RequestImpl : public Request { if (body_) { if (body_->GetType() == RequestBody::Type::FIXED_SIZE) { writer_ = DataWriter::CreatePlainWriter( - body_->GetFixedSize(), move(writer_)); + body_->GetFixedSize(), std::move(writer_)); } else { - writer_ = DataWriter::CreateChunkedWriter(nullptr, move(writer_)); + writer_ = DataWriter::CreateChunkedWriter(nullptr, std::move(writer_)); } } else { static const string transfer_encoding{"Transfer-Encoding"}; static const string chunked{"chunked"}; auto h = properties_->headers.find(transfer_encoding); if ((h != properties_->headers.end()) && ciEqLibC()(h->second, chunked)) { - writer_ = DataWriter::CreateChunkedWriter(nullptr, move(writer_)); + writer_ = DataWriter::CreateChunkedWriter(nullptr, std::move(writer_)); } else { - writer_ = DataWriter::CreatePlainWriter(0, move(writer_)); + writer_ = DataWriter::CreatePlainWriter(0, std::move(writer_)); } } // TODO: Add compression write_buffers_t write_buffer; - ToBuffer headers(BuildOutgoingRequest()); + ToBuffer const headers(BuildOutgoingRequest()); write_buffer.push_back(headers); header_size_ = boost::asio::buffer_size(write_buffer); @@ -941,7 +950,7 @@ class RequestImpl : public Request { "No Location header in redirect reply"); } RESTC_CPP_LOG_TRACE_("GetReply: RedirectException. location=" << *redirect_location); - throw RedirectException(http_code, *redirect_location, move(reply)); + throw RedirectException(http_code, *redirect_location, std::move(reply)); } if (properties_->throwOnHttpError) { @@ -955,7 +964,7 @@ class RequestImpl : public Request { * received. */ - return move(reply); + return reply; } @@ -989,7 +998,7 @@ Request::Create(const std::string& url, const boost::optional<headers_t>& headers, const boost::optional<auth_t>& auth) { - return make_unique<RequestImpl>(url, requestType, owner, move(body), args, headers, auth); + return make_unique<RequestImpl>(url, requestType, owner, std::move(body), args, headers, auth); } } // restc_cpp diff --git a/src/RestClientImpl.cpp b/src/RestClientImpl.cpp index 8e4c17c..1da53e1 100644 --- a/src/RestClientImpl.cpp +++ b/src/RestClientImpl.cpp @@ -7,6 +7,9 @@ #include <thread> #include <future> +#include <boost/exception/all.hpp> +#include <boost/exception/diagnostic_information.hpp> + #include "restc-cpp/restc-cpp.h" #include "restc-cpp/logging.h" #include "restc-cpp/ConnectionPool.h" @@ -23,7 +26,7 @@ using namespace std; namespace restc_cpp { -class RestClientImpl : public RestClient { +class RestClientImpl final : public RestClient { public: /*! Proper shutdown handling @@ -76,13 +79,13 @@ class RestClientImpl : public RestClient { unique_ptr< Reply > Post(string url, string body) override { auto req = Request::Create(url, restc_cpp::Request::Type::POST, rc_, - {RequestBody::CreateStringBody(move(body))}); + {RequestBody::CreateStringBody(std::move(body))}); return Request(*req); } unique_ptr< Reply > Put(string url, string body) override { auto req = Request::Create(url, restc_cpp::Request::Type::PUT, rc_, - {RequestBody::CreateStringBody(move(body))}); + {RequestBody::CreateStringBody(std::move(body))}); return Request(*req); } @@ -125,38 +128,38 @@ class RestClientImpl : public RestClient { RestClientImpl(const boost::optional<Request::Properties>& properties, bool useMainThread) - : ioservice_instance_{make_unique<boost::asio::io_service>()} + : ioservice_instance_{make_unique<boost_io_service>()} { #ifdef RESTC_CPP_WITH_TLS - setDefaultSSLContext(); + setDefaultSSLContext(); #endif - io_service_ = ioservice_instance_.get(); + io_service_ = ioservice_instance_.get(); Init(properties, useMainThread); } #ifdef RESTC_CPP_WITH_TLS RestClientImpl(const boost::optional<Request::Properties>& properties, bool useMainThread, shared_ptr<boost::asio::ssl::context> ctx) - : ioservice_instance_{ make_unique<boost::asio::io_service>() } + : ioservice_instance_{ make_unique<boost_io_service>() } { - tls_context_ = move(ctx); + tls_context_ = std::move(ctx); io_service_ = ioservice_instance_.get(); Init(properties, useMainThread); } RestClientImpl(const boost::optional<Request::Properties>& properties, bool useMainThread, shared_ptr<boost::asio::ssl::context> ctx, - boost::asio::io_service& ioservice) + boost_io_service& ioservice) : io_service_{ &ioservice } { - tls_context_ = move(ctx); + tls_context_ = std::move(ctx); io_service_ = ioservice_instance_.get(); Init(properties, useMainThread); } #endif RestClientImpl(const boost::optional<Request::Properties>& properties, - boost::asio::io_service& ioservice) + boost_io_service& ioservice) : io_service_{&ioservice} { #ifdef RESTC_CPP_WITH_TLS @@ -219,7 +222,11 @@ class RestClientImpl : public RestClient { done_mutexes_.push_back(make_unique<recursive_mutex>()); } - work_ = make_unique<boost::asio::io_service::work>(*io_service_); +#if BOOST_VERSION >= 106600 + work_ = std::make_unique<boost_work>(boost::asio::make_work_guard(io_service_->get_executor())); +#else + work_ = std::make_unique<boost_work>(*io_service_); +#endif RESTC_CPP_LOG_TRACE_("Starting " <<default_connection_properties_->threads << " worker thread(s)"); for(size_t i = 0; i < default_connection_properties_->threads; ++i) { @@ -256,7 +263,7 @@ class RestClientImpl : public RestClient { void CloseWhenReady(bool wait) override { ClearWork(); if (!io_service_->stopped()) { - io_service_->dispatch([this](){ + boost_dispatch(io_service_, [this](){ if (current_tasks_ == 0) { OnNoMoreWork(); } @@ -284,7 +291,7 @@ class RestClientImpl : public RestClient { call_once(close_once_, [&] { auto promise = make_shared<std::promise<void>>(); - io_service_->dispatch([this, promise]() { + boost_dispatch(io_service_, [this, promise]() { LOCK_; if (work_) { work_.reset(); @@ -308,8 +315,6 @@ class RestClientImpl : public RestClient { DoneHandlerImpl handler(*this); try { fn(ctx); - } catch (boost::coroutines::detail::forced_unwind const&) { - throw; // required for Boost Coroutine! } catch(const exception& ex) { RESTC_CPP_LOG_ERROR_("ProcessInWorker: Caught exception: " << ex.what()); if (promise) { @@ -321,7 +326,7 @@ class RestClientImpl : public RestClient { RESTC_CPP_LOG_ERROR_("ProcessInWorker: Caught boost exception: " << boost::diagnostic_information(ex)); terminate(); - } catch(...) { + } RESTC_CPP_IN_COROUTINE_CATCH_ALL { ostringstream estr; #ifdef __unix__ estr << " of type : " << __cxxabiv1::__cxa_current_exception_type()->name(); @@ -342,7 +347,7 @@ class RestClientImpl : public RestClient { void Process(const prc_fn_t& fn) override { boost::asio::spawn(*io_service_, bind(&RestClientImpl::ProcessInWorker, this, - placeholders::_1, fn, nullptr)); + placeholders::_1, fn, nullptr) RESTC_CPP_SPAWN_TRAILER); } future< void > ProcessWithPromise(const prc_fn_t& fn) override { @@ -351,7 +356,7 @@ class RestClientImpl : public RestClient { boost::asio::spawn(*io_service_, bind(&RestClientImpl::ProcessInWorker, this, - placeholders::_1, fn, promise)); + placeholders::_1, fn, promise) RESTC_CPP_SPAWN_TRAILER); return future; } @@ -361,7 +366,7 @@ class RestClientImpl : public RestClient { return pool_; } - boost::asio::io_service& GetIoService() override { return *io_service_; } + boost_io_service& GetIoService() override { return *io_service_; } #ifdef RESTC_CPP_WITH_TLS shared_ptr<boost::asio::ssl::context> GetTLSContext() override { return tls_context_; } @@ -400,10 +405,10 @@ class RestClientImpl : public RestClient { private: Request::Properties::ptr_t default_connection_properties_ = make_shared<Request::Properties>(); - unique_ptr<boost::asio::io_service> ioservice_instance_; - boost::asio::io_service *io_service_ = nullptr; + unique_ptr<boost_io_service> ioservice_instance_; + boost_io_service *io_service_ = nullptr; ConnectionPool::ptr_t pool_; - unique_ptr<boost::asio::io_service::work> work_; + std::unique_ptr<boost_work> work_; #ifdef RESTC_CPP_THREADED_CTX atomic_size_t current_tasks_{0}; #else @@ -441,20 +446,20 @@ unique_ptr<RestClient> RestClient::Create() { #ifdef RESTC_CPP_WITH_TLS unique_ptr<RestClient> RestClient::Create(std::shared_ptr<boost::asio::ssl::context> ctx) { boost::optional<Request::Properties> properties; - return make_unique<RestClientImpl>(properties, false, move(ctx)); + return make_unique<RestClientImpl>(properties, false, std::move(ctx)); } std::unique_ptr<RestClient> RestClient::Create(std::shared_ptr<boost::asio::ssl::context> ctx, const boost::optional<Request::Properties> &properties) { - return make_unique<RestClientImpl>(properties, false, move(ctx)); + return make_unique<RestClientImpl>(properties, false, std::move(ctx)); } std::unique_ptr<RestClient> RestClient::Create(std::shared_ptr<boost::asio::ssl::context> ctx, const boost::optional<Request::Properties> &properties, - boost::asio::io_service &ioservice) + boost_io_service &ioservice) { - return make_unique<RestClientImpl>(properties, false, move(ctx), ioservice); + return make_unique<RestClientImpl>(properties, false, std::move(ctx), ioservice); } #endif @@ -475,12 +480,12 @@ unique_ptr<RestClient> RestClient::CreateUseOwnThread() { std::unique_ptr<RestClient> RestClient::Create(const boost::optional<Request::Properties>& properties, - boost::asio::io_service& ioservice) { + boost_io_service& ioservice) { return make_unique<RestClientImpl>(properties, ioservice); } std::unique_ptr<RestClient> -RestClient::Create(boost::asio::io_service& ioservice) { +RestClient::Create(boost_io_service& ioservice) { return make_unique<RestClientImpl>(boost::optional<Request::Properties>{}, ioservice); } diff --git a/src/SocketImpl.h b/src/SocketImpl.h index 747debf..7cd8c48 100644 --- a/src/SocketImpl.h +++ b/src/SocketImpl.h @@ -6,7 +6,7 @@ #include <boost/utility/string_ref.hpp> -#include "restc-cpp/restc-cpp.h" +#include "restc-cpp/boost_compatibility.h" #include "restc-cpp/Socket.h" #include "restc-cpp/logging.h" @@ -15,7 +15,7 @@ namespace restc_cpp { class SocketImpl : public Socket, protected ExceptionWrapper { public: - SocketImpl(boost::asio::io_service& io_service) + SocketImpl(boost_io_service& io_service) : socket_{io_service} { } @@ -28,21 +28,21 @@ class SocketImpl : public Socket, protected ExceptionWrapper { return socket_; } - std::size_t AsyncReadSome(boost::asio::mutable_buffers_1 buffers, + std::size_t AsyncReadSome(boost_mutable_buffer buffers, boost::asio::yield_context& yield) override { return WrapException<std::size_t>([&] { return socket_.async_read_some(buffers, yield); }); } - std::size_t AsyncRead(boost::asio::mutable_buffers_1 buffers, + std::size_t AsyncRead(boost_mutable_buffer buffers, boost::asio::yield_context& yield) override { return WrapException<std::size_t>([&] { return boost::asio::async_read(socket_, buffers, yield); }); } - void AsyncWrite(const boost::asio::const_buffers_1& buffers, + void AsyncWrite(const boost_const_buffer& buffers, boost::asio::yield_context& yield) override { boost::asio::async_write(socket_, buffers, yield); } diff --git a/src/TlsSocketImpl.h b/src/TlsSocketImpl.h index a7033f5..7a4fa95 100644 --- a/src/TlsSocketImpl.h +++ b/src/TlsSocketImpl.h @@ -24,7 +24,7 @@ class TlsSocketImpl : public Socket, protected ExceptionWrapper { using ssl_socket_t = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>; - TlsSocketImpl(boost::asio::io_service& io_service, shared_ptr<boost::asio::ssl::context> ctx) + TlsSocketImpl(boost_io_service& io_service, shared_ptr<boost::asio::ssl::context> ctx) { ssl_socket_ = std::make_unique<ssl_socket_t>(io_service, *ctx); } @@ -39,21 +39,21 @@ class TlsSocketImpl : public Socket, protected ExceptionWrapper { ssl_socket_->lowest_layer()); } - std::size_t AsyncReadSome(boost::asio::mutable_buffers_1 buffers, + std::size_t AsyncReadSome(boost_mutable_buffer buffers, boost::asio::yield_context& yield) override { return WrapException<std::size_t>([&] { return ssl_socket_->async_read_some(buffers, yield); }); } - std::size_t AsyncRead(boost::asio::mutable_buffers_1 buffers, + std::size_t AsyncRead(boost_mutable_buffer buffers, boost::asio::yield_context& yield) override { return WrapException<std::size_t>([&] { return boost::asio::async_read(*ssl_socket_, buffers, yield); }); } - void AsyncWrite(const boost::asio::const_buffers_1& buffers, + void AsyncWrite(const boost_const_buffer& buffers, boost::asio::yield_context& yield) override { boost::asio::async_write(*ssl_socket_, buffers, yield); } @@ -66,7 +66,7 @@ class TlsSocketImpl : public Socket, protected ExceptionWrapper { } void AsyncConnect(const boost::asio::ip::tcp::endpoint& ep, - const string &host, + const std::string &host, bool tcpNodelay, boost::asio::yield_context& yield) override { return WrapException<void>([&] { diff --git a/src/Url.cpp b/src/Url.cpp index 6d43edf..4064ee8 100644 --- a/src/Url.cpp +++ b/src/Url.cpp @@ -46,8 +46,12 @@ Url& Url::operator = (const char *url) { remains.size() - (args_start + 1)}; remains = {remains.begin(), args_start}; } + auto path_start = remains.find('/'); const auto port_start = remains.find(':'); - if (port_start != string::npos) { + if (port_start != string::npos && + ( path_start == string::npos || + port_start < path_start ) + ) { if (remains.length() <= static_cast<decltype(host_.length())>(port_start + 2)) { throw ParseException("Invalid host (no port after column)"); } @@ -56,8 +60,9 @@ Url& Url::operator = (const char *url) { host_ = {remains.begin(), port_start}; remains = {remains.begin() + port_start + 1, remains.size() - (port_start + 1)}; - const auto path_start = remains.find('/'); if (path_start != string::npos) { + //path_start = remains.find('/'); + path_start -= port_start + 1; path_ = {remains.begin() + path_start, remains.size() - path_start};// &port_[path_start]; port_ = {remains.begin(), path_start}; remains = {}; @@ -65,7 +70,6 @@ Url& Url::operator = (const char *url) { port_ = remains; } } else { - const auto path_start = remains.find('/'); if (path_start != string::npos) { //path_ = &host_[path_start]; //host_ = boost::string_ref(host_.data(), path_start); diff --git a/src/ZipReaderImpl.cpp b/src/ZipReaderImpl.cpp index ebcd0ea..800d70e 100644 --- a/src/ZipReaderImpl.cpp +++ b/src/ZipReaderImpl.cpp @@ -14,7 +14,7 @@ class ZipReaderImpl : public DataReader { ZipReaderImpl(std::unique_ptr<DataReader>&& source, const Format format) - : source_{move(source)} + : source_{std::move(source)} { const auto wsize = (format == Format::GZIP) ? (MAX_WBITS | 16) : MAX_WBITS; @@ -34,20 +34,17 @@ class ZipReaderImpl : public DataReader { inflateEnd(&strm_); } - bool IsEof() const override { - return done_; - } + [[nodiscard]] bool IsEof() const override { return done_; } void Finish() override { - if (source_) + if (source_) { source_->Finish(); + } } - bool HaveMoreBufferedInput() const noexcept { - return strm_.avail_in > 0; - } + [[nodiscard]] bool HaveMoreBufferedInput() const noexcept { return strm_.avail_in > 0; } - boost::asio::const_buffers_1 ReadSome() override { + ::restc_cpp::boost_const_buffer ReadSome() override { size_t data_len = 0; @@ -58,7 +55,7 @@ class ZipReaderImpl : public DataReader { } else { const auto buffers = source_->ReadSome(); src = { - boost::asio::buffer_cast<const char *>(buffers), + boost_buffer_cast(buffers), boost::asio::buffer_size(buffers)}; if (src.empty()) { @@ -148,13 +145,13 @@ class ZipReaderImpl : public DataReader { std::unique_ptr<DataReader> DataReader::CreateZipReader(std::unique_ptr<DataReader>&& source) { - return make_unique<ZipReaderImpl>(move(source), + return make_unique<ZipReaderImpl>(std::move(source), ZipReaderImpl::Format::DEFLATE); } std::unique_ptr<DataReader> DataReader::CreateGzipReader(std::unique_ptr<DataReader>&& source) { - return make_unique<ZipReaderImpl>(move(source), + return make_unique<ZipReaderImpl>(std::move(source), ZipReaderImpl::Format::GZIP); } diff --git a/src/boost_compitability.cpp b/src/boost_compitability.cpp new file mode 100644 index 0000000..5e2beec --- /dev/null +++ b/src/boost_compitability.cpp @@ -0,0 +1,34 @@ + +#include "restc-cpp/boost_compatibility.h" + +namespace restc_cpp { +boost::asio::ip::tcp::endpoint boost_create_endpoint(const std::string& ip_address, unsigned short port) { +#if BOOST_VERSION >= 106600 + // For Boost 1.66.0 and later + return {boost::asio::ip::make_address(ip_address), port}; +#else + // For Boost versions earlier than 1.66.0 + return {boost::asio::ip::address::from_string(ip_address), port}; +#endif +} + +uint32_t boost_convert_ipv4_to_uint(const std::string& ip_address) { +#if BOOST_VERSION >= 106600 + // For Boost 1.66.0 and later + return boost::asio::ip::make_address_v4(ip_address).to_uint(); +#else + // For Boost versions earlier than 1.66.0 + return boost::asio::ip::address_v4::from_string(ip_address).to_ulong(); +#endif +} + +std::unique_ptr<boost_work> boost_make_work(boost_io_service& ioservice) { +#if BOOST_VERSION >= 106600 + return std::make_unique<boost_work>(boost::asio::make_work_guard(ioservice)); +#else + return std::make_unique<boost_work>(ioservice); +#endif +} + + +} // namespace restc_cpp diff --git a/stop-containers.sh b/stop-containers.sh index 77950e6..9eead9b 100755 --- a/stop-containers.sh +++ b/stop-containers.sh @@ -1,5 +1,33 @@ #!/bin/bash +# Check if Docker is running +docker ps > /dev/null +if [ $? -ne 0 ]; then + echo + echo "Cannot run docker commands. " + echo "Please install Docker or give this user access to run it" + popd + exit -1 +fi + +# Determine the correct Docker Compose command +DOCKER_COMPOSE="docker compose" +$DOCKER_COMPOSE version > /dev/null 2>&1 + +if [ $? -ne 0 ]; then + DOCKER_COMPOSE="docker-compose" + $DOCKER_COMPOSE version > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Neither 'docker compose' nor 'docker-compose' is available. Please install Docker Compose." + popd + exit -1 + fi +fi + +echo "Using Docker Compose command: $DOCKER_COMPOSE" + +# Run Docker Compose commands pushd ci/mock-backends -docker-compose down +$DOCKER_COMPOSE down +docker ps popd diff --git a/tests/functional/BasicTests.cpp b/tests/functional/BasicTests.cpp index 114a08a..2b487c5 100644 --- a/tests/functional/BasicTests.cpp +++ b/tests/functional/BasicTests.cpp @@ -5,6 +5,8 @@ #include <boost/lexical_cast.hpp> #include <boost/fusion/adapted.hpp> +#include <boost/exception/all.hpp> +#include <boost/exception/diagnostic_information.hpp> #include "restc-cpp/restc-cpp.h" #include "restc-cpp/RequestBuilder.h" @@ -19,14 +21,14 @@ using namespace restc_cpp; // For entries received from http://jsonplaceholder.typicode.com/posts struct Post { - int id = 0; + string id; string username; string motto; }; BOOST_FUSION_ADAPT_STRUCT( Post, - (int, id) + (string, id) (string, username) (string, motto) ) @@ -55,7 +57,7 @@ TEST(Future, GetData) { return post; }).get(); ); // EXPECT_NO_THROW - EXPECT_EQ(my_post.id, 1); + EXPECT_EQ(my_post.id, "1"); EXPECT_FALSE(my_post.username.empty()); EXPECT_FALSE(my_post.motto.empty()); } @@ -72,7 +74,7 @@ TEST(ExampleWorkflow, DISABLED_SequentialRequests) { EXPECT_GE(posts_list.size(), 1); // Asynchronously connect to server and POST data. - auto repl = ctx.Post(GetDockerUrl(http_url), "{\"test\":\"teste\"}"); + auto repl = ctx.Post(GetDockerUrl(http_url), R"({"test":"teste"})"); // Asynchronously fetch the entire data-set and return it as a string. auto json = repl->GetBodyAsString(); @@ -87,8 +89,8 @@ TEST(ExampleWorkflow, DISABLED_SequentialRequests) { .Header("Accept", "*/*") .Execute(); - string body = repl->GetBodyAsString(); - cout << "Got compressed list: " << body << endl; + string const body = repl->GetBodyAsString(); + cout << "Got compressed list: " << body << '\n'; EXPECT_HTTP_OK(repl->GetHttpResponse().status_code); EXPECT_FALSE(body.empty()); @@ -107,7 +109,7 @@ TEST(ExampleWorkflow, DISABLED_SequentialRequests) { EXPECT_HTTP_OK(repl->GetHttpResponse().status_code); EXPECT_FALSE(body.empty()); - cout << "Got: " << repl->GetBodyAsString() << endl; + cout << "Got: " << repl->GetBodyAsString() << '\n'; repl.reset(); // Use RequestBuilder to fetch a record without compression @@ -120,7 +122,7 @@ TEST(ExampleWorkflow, DISABLED_SequentialRequests) { .Argument("id", 2) .Execute(); - cout << "Got: " << repl->GetBodyAsString() << endl; + cout << "Got: " << repl->GetBodyAsString() << '\n'; repl.reset(); // Use RequestBuilder to post a record diff --git a/tests/functional/CRUD_test.cpp b/tests/functional/CRUD_test.cpp index 3160e72..4ce486b 100644 --- a/tests/functional/CRUD_test.cpp +++ b/tests/functional/CRUD_test.cpp @@ -14,19 +14,19 @@ using namespace restc_cpp; /* These url's points to a local Docker container with nginx, linked to * a jsonserver docker container with mock data. - * The scripts to build and run these containers are in the ./tests directory. + * You can run /.create-and-run-containers.sh to start them. */ const string http_url = "http://localhost:3000/posts"; struct Post { - int id = 0; + string id; string username; string motto; }; BOOST_FUSION_ADAPT_STRUCT( Post, - (int, id) + (string, id) (string, username) (string, motto) ) @@ -40,7 +40,7 @@ TEST(CRUD, Crud) { post.username = "catch22"; post.motto = "Carpe Diem!"; - EXPECT_EQ(0, post.id); + EXPECT_EQ("", post.id); auto reply = RequestBuilder(ctx) .Post(GetDockerUrl(http_url)) // URL @@ -54,35 +54,38 @@ TEST(CRUD, Crud) { EXPECT_EQ(post.username, svr_post.username); EXPECT_EQ(post.motto, svr_post.motto); - EXPECT_TRUE(svr_post.id > 0); + EXPECT_FALSE(svr_post.id.empty()); // Change the data post = svr_post; post.motto = "Change!"; reply = RequestBuilder(ctx) - .Put(GetDockerUrl(http_url) + "/" + to_string(post.id)) // URL + .Put(GetDockerUrl(http_url) + "/" + post.id) // URL .Data(post) // Data object to update .Execute(); + // Get the reply before we validate the operation. Else it may run in parallel + reply->fetchAndIgnore(); + // Fetch again reply = RequestBuilder(ctx) - .Get(GetDockerUrl(http_url) + "/" + to_string(post.id)) // URL + .Get(GetDockerUrl(http_url) + "/" + post.id) // URL .Execute(); SerializeFromJson(svr_post, *reply); EXPECT_EQ(post.motto, svr_post.motto); // Delete - reply = RequestBuilder(ctx) - .Delete(GetDockerUrl(http_url) + "/" + to_string(post.id)) // URL - .Execute(); + RequestBuilder(ctx) + .Delete(GetDockerUrl(http_url) + "/" + post.id) // URL + .Execute()->fetchAndIgnore(); // Verify that it's gone EXPECT_THROW( RequestBuilder(ctx) - .Get(GetDockerUrl(http_url) + "/" + to_string(post.id)) // URL - .Execute(), HttpNotFoundException); - - + .Get(GetDockerUrl(http_url) + "/" + post.id) // URL + .Execute(), + HttpNotFoundException + ); }).get(); } @@ -119,7 +122,7 @@ TEST(CRUD, HEAD) { int main( int argc, char * argv[] ) { - RESTC_CPP_TEST_LOGGING_SETUP("debug"); + RESTC_CPP_TEST_LOGGING_SETUP("info"); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();; } diff --git a/tests/functional/ConnectionCacheTests.cpp b/tests/functional/ConnectionCacheTests.cpp index 54a2400..822a896 100644 --- a/tests/functional/ConnectionCacheTests.cpp +++ b/tests/functional/ConnectionCacheTests.cpp @@ -3,6 +3,7 @@ #include "restc-cpp/restc-cpp.h" #include "restc-cpp/logging.h" #include "restc-cpp/ConnectionPool.h" +#include "restc-cpp/boost_compatibility.h" #include "../src/ReplyImpl.h" @@ -73,8 +74,7 @@ TEST(ConnectionCache, MaxConnectionsToEndpoint) { auto config = rest_client->GetConnectionProperties(); std::vector<Connection::ptr_t> connections; - boost::asio::ip::tcp::endpoint ep{ - boost::asio::ip::address::from_string("127.0.0.1"), 80}; + const auto ep = boost_create_endpoint("127.0.0.1", 80); for(size_t i = 0; i < config->cacheMaxConnectionsPerEndpoint; ++i) { connections.push_back(pool->GetConnection(ep, restc_cpp::Connection::Type::HTTP)); } @@ -88,7 +88,7 @@ TEST(ConnectionCache, MaxConnections) { auto pool = rest_client->GetConnectionPool(); auto config = rest_client->GetConnectionProperties(); - auto addr = boost::asio::ip::address_v4::from_string("127.0.0.1").to_ulong(); + auto addr = boost_convert_ipv4_to_uint("127.0.0.1"); std::vector<Connection::ptr_t> connections; decltype(addr) i = 0; @@ -153,8 +153,7 @@ TEST(ConnectionCache, OverrideMaxConnectionsToEndpoint) { auto config = rest_client->GetConnectionProperties(); std::vector<Connection::ptr_t> connections; - boost::asio::ip::tcp::endpoint ep{ - boost::asio::ip::address::from_string("127.0.0.1"), 80}; + auto const ep = boost_create_endpoint("127.0.0.1", 80); for(size_t i = 0; i < config->cacheMaxConnectionsPerEndpoint; ++i) { connections.push_back(pool->GetConnection(ep, restc_cpp::Connection::Type::HTTP)); } diff --git a/tests/functional/ConnectionPoolInstancesTest.cpp b/tests/functional/ConnectionPoolInstancesTest.cpp index 043762f..15ebc1b 100644 --- a/tests/functional/ConnectionPoolInstancesTest.cpp +++ b/tests/functional/ConnectionPoolInstancesTest.cpp @@ -38,7 +38,7 @@ TEST(ConnectionPoolInstances, UseAfterDelete) { }).get(); if ((i % 100) == 0) { - clog << '#' << (i +1) << endl; + clog << '#' << (i + 1) << '\n'; } } } diff --git a/tests/functional/CookieTests.cpp b/tests/functional/CookieTests.cpp index 21392e4..d251d6f 100644 --- a/tests/functional/CookieTests.cpp +++ b/tests/functional/CookieTests.cpp @@ -29,7 +29,7 @@ TEST(Cookies, HaveCookies) set<string> allowed = {"test1=yes", "test2=maybe", "test3=no"}; - for(const auto c : cookies) { + for (const auto &c : cookies) { EXPECT_EQ(true, allowed.find(c) != allowed.end()); allowed.erase(c); } diff --git a/tests/functional/HttpsTest.cpp b/tests/functional/HttpsTest.cpp index f3a0914..6d8eb7c 100644 --- a/tests/functional/HttpsTest.cpp +++ b/tests/functional/HttpsTest.cpp @@ -47,7 +47,8 @@ BOOST_FUSION_ADAPT_STRUCT( string https_url = "https://lastviking.eu/files/api"; TEST(Https, GetJson) { - shared_ptr<boost::asio::ssl::context> tls_ctx = make_shared<boost::asio::ssl::context>(boost::asio::ssl::context{ boost::asio::ssl::context::tlsv12_client}); + shared_ptr<boost::asio::ssl::context> const tls_ctx = make_shared<boost::asio::ssl::context>( + boost::asio::ssl::context{boost::asio::ssl::context::tlsv12_client}); EXPECT_NO_THROW(tls_ctx->set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 diff --git a/tests/functional/InsertSerializerTest.cpp b/tests/functional/InsertSerializerTest.cpp index d7fc592..b77eb78 100644 --- a/tests/functional/InsertSerializerTest.cpp +++ b/tests/functional/InsertSerializerTest.cpp @@ -1,5 +1,7 @@ // Include before boost::log headers +#include <utility> + #include "restc-cpp/restc-cpp.h" #include "restc-cpp/logging.h" #include "restc-cpp/RequestBuilder.h" @@ -14,7 +16,9 @@ using namespace restc_cpp; struct Post { Post() = default; Post(string u, string m) - : username{u}, motto{m} {} + : username{std::move(u)} + , motto{std::move(m)} + {} int id = 0; string username; diff --git a/tests/functional/ManyConnectionsTest.cpp b/tests/functional/ManyConnectionsTest.cpp index 8f2ce94..46988c4 100644 --- a/tests/functional/ManyConnectionsTest.cpp +++ b/tests/functional/ManyConnectionsTest.cpp @@ -38,21 +38,44 @@ const string http_url = "http://localhost:3000/manyposts"; * works as expected with many co-routines in parallel. */ -#define CONNECTIONS 100 +enum { CONNECTIONS = 100 }; struct Post { - int id = 0; + string id; string username; string motto; }; BOOST_FUSION_ADAPT_STRUCT( Post, - (int, id) + (string, id) (string, username) (string, motto) ) +struct Locker { + Locker(mutex& m) : m_{m} {} + ~Locker() { + if (locked_) { + m_.unlock(); + } + } + + bool try_lock() { + assert(!locked_); + locked_ = m_.try_lock(); + return locked_; + } + + void unlock() { + assert(locked_); + m_.unlock(); + locked_ = false; + } + + mutex& m_; + bool locked_{}; +}; TEST(ManyConnections, CRUD) { mutex mutex; @@ -75,35 +98,41 @@ TEST(ManyConnections, CRUD) { futures.push_back(promises.back().get_future()); rest_client->Process([i, &promises, &rest_client, &mutex](Context& ctx) { - - auto reply = RequestBuilder(ctx) - .Get(GetDockerUrl(http_url)) - .Execute(); - - // Use an iterator to make it simple to fetch some data and - // then wait on the mutex before we finish. - IteratorFromJsonSerializer<Post> results(*reply); - - auto it = results.begin(); - RESTC_CPP_LOG_DEBUG_("Iteration #" << i - << " Read item # " << it->id); - - promises[i].set_value(i); - // Wait for all connections to be ready - - // We can't just wait on the lock since we are in a co-routine. - // So we use the async_wait() to poll in stead. - while(!mutex.try_lock()) { - boost::asio::deadline_timer timer(rest_client->GetIoService(), - boost::posix_time::milliseconds(1)); - timer.async_wait(ctx.GetYield()); + Locker locker(mutex); + try { + auto reply = RequestBuilder(ctx) + .Get(GetDockerUrl(http_url)) + .Execute(); + + // Use an iterator to make it simple to fetch some data and + // then wait on the mutex before we finish. + IteratorFromJsonSerializer<Post> results(*reply); + + auto it = results.begin(); + RESTC_CPP_LOG_DEBUG_("Iteration #" << i + << " Read item # " << it->id); + + promises[i].set_value(i); + // Wait for all connections to be ready + + // We can't just wait on the lock since we are in a co-routine. + // So we use the async_wait() to poll in stead. + while(!locker.try_lock()) { + boost::asio::deadline_timer timer(rest_client->GetIoService(), + boost::posix_time::milliseconds(1)); + timer.async_wait(ctx.GetYield()); + } + locker.unlock(); + + // Fetch the rest + for (; it != results.end(); ++it) { + ; + } + + } catch (const std::exception& ex) { + RESTC_CPP_LOG_ERROR_("Failed to fetch data: " << ex.what()); + promises[i].set_exception(std::current_exception()); } - mutex.unlock(); - - // Fetch the rest - for(; it != results.end(); ++it) - ; - }); } @@ -131,7 +160,7 @@ TEST(ManyConnections, CRUD) { int main( int argc, char * argv[] ) { - RESTC_CPP_TEST_LOGGING_SETUP("debug"); + RESTC_CPP_TEST_LOGGING_SETUP("info"); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();; } diff --git a/tests/functional/OwnIoserviceTests.cpp b/tests/functional/OwnIoserviceTests.cpp index d397a7b..82da15e 100644 --- a/tests/functional/OwnIoserviceTests.cpp +++ b/tests/functional/OwnIoserviceTests.cpp @@ -15,18 +15,18 @@ using namespace std; using namespace restc_cpp; -#define CONNECTIONS 20 +enum { CONNECTIONS = 20 }; //#define CONNECTIONS 1 struct Post { - int id = 0; + string id; string username; string motto; }; BOOST_FUSION_ADAPT_STRUCT( Post, - (int, id) + (string, id) (string, username) (string, motto) ) @@ -36,7 +36,7 @@ const string http_url = "http://localhost:3000/manyposts"; TEST(OwnIoservice, All) { - boost::asio::io_service ioservice; + boost_io_service ioservice; mutex mutex; mutex.lock(); @@ -82,8 +82,9 @@ TEST(OwnIoservice, All) mutex.unlock(); // Fetch the rest - for(; it != results.end(); ++it) + for (; it != results.end(); ++it) { ; + } promises[i].set_value(i); } RESTC_CPP_IN_COROUTINE_CATCH_ALL { @@ -95,9 +96,9 @@ TEST(OwnIoservice, All) } thread worker([&ioservice]() { - cout << "ioservice is running" << endl; + cout << "ioservice is running" << '\n'; ioservice.run(); - cout << "ioservice is done" << endl; + cout << "ioservice is done" << '\n'; }); mutex.unlock(); diff --git a/tests/functional/ProxyTests.cpp b/tests/functional/ProxyTests.cpp index cda65f9..8c6695f 100644 --- a/tests/functional/ProxyTests.cpp +++ b/tests/functional/ProxyTests.cpp @@ -51,7 +51,7 @@ TEST(Proxy, WithHttpProxy) .Execute(); EXPECT_HTTP_OK(reply->GetResponseCode()); - cout << "Got: " << reply->GetBodyAsString() << endl; + cout << "Got: " << reply->GetBodyAsString() << '\n'; }); EXPECT_NO_THROW(f.get()); @@ -72,7 +72,7 @@ TEST(Proxy, WithSocks5Proxy) .Execute(); EXPECT_HTTP_OK(reply->GetResponseCode()); - cout << "Got: " << reply->GetBodyAsString() << endl; + cout << "Got: " << reply->GetBodyAsString() << '\n'; }); EXPECT_NO_THROW(f.get()); diff --git a/tests/functional/ReadmeTests.cpp b/tests/functional/ReadmeTests.cpp index 6af0589..29e3c76 100644 --- a/tests/functional/ReadmeTests.cpp +++ b/tests/functional/ReadmeTests.cpp @@ -2,8 +2,9 @@ #define RESTC_CPP_ENABLE_URL_TEST_MAPPING 1 -#include <boost/lexical_cast.hpp> #include <boost/fusion/adapted.hpp> +#include <boost/lexical_cast.hpp> +#include <utility> #include "restc-cpp/restc-cpp.h" #include "restc-cpp/IteratorFromJsonSerializer.h" @@ -44,7 +45,7 @@ void first() { auto rest_client = RestClient::Create(); // Create and instantiate a Post from data received from the server. - Post my_post = rest_client->ProcessWithPromiseT<Post>([&](Context& ctx) { + Post const my_post = rest_client->ProcessWithPromiseT<Post>([&](Context& ctx) { // This is a co-routine, running in a worker-thread // Instantiate a Post structure. @@ -88,7 +89,7 @@ void DoSomethingInteresting(Context& ctx) { auto json = reply->GetBodyAsString(); // Just dump the data. - cout << "Received data: " << json << endl; + cout << "Received data: " << json << '\n'; } void second() { @@ -179,7 +180,7 @@ void fifth() { // Iterate over the data, fetch data asyncrounesly as we go. for(const auto& post : data) { - cout << "Item #" << post.id << " Title: " << post.title << endl; + cout << "Item #" << post.id << " Title: " << post.title << '\n'; } }); } @@ -210,7 +211,7 @@ void sixth() { // Start the io-service, using this thread. rest_client->GetIoService().run(); - cout << "Done. Exiting normally." << endl; + cout << "Done. Exiting normally." << '\n'; } // Use our own RequestBody implementation to supply @@ -222,7 +223,7 @@ void seventh() { public: MyBody() = default; - Type GetType() const noexcept override { + [[nodiscard]] Type GetType() const noexcept override { // This mode causes the request to use chunked data, // allowing us to send data without knowing the exact @@ -230,7 +231,7 @@ void seventh() { return Type::CHUNKED_LAZY_PULL; } - std::uint64_t GetFixedSize() const override { + [[nodiscard]] std::uint64_t GetFixedSize() const override { throw runtime_error("Not implemented"); } @@ -291,7 +292,9 @@ void seventh() { struct DataItem { DataItem() = default; DataItem(string u, string m) - : username{u}, motto{m} {} + : username{std::move(u)} + , motto{std::move(m)} + {} int id = 0; string username; @@ -414,16 +417,16 @@ void ninth() { void tenth() { // Construct our own ioservice. - boost::asio::io_service ioservice; + boost_io_service ioservice; // Give it some work so it don't end prematurely - boost::asio::io_service::work work(ioservice); + auto work = boost_make_work(ioservice); // Start it in a worker-thread thread worker([&ioservice]() { - cout << "ioservice is running" << endl; + cout << "ioservice is running" << '\n'; ioservice.run(); - cout << "ioservice is done" << endl; + cout << "ioservice is done" << '\n'; }); // Now we have our own io-service running in a worker thread. @@ -443,7 +446,7 @@ void tenth() { auto json = reply->GetBodyAsString(); // Just dump the data. - cout << "Received data: " << json << endl; + cout << "Received data: " << json << '\n'; }) // Wait for the co-routine to end .get(); @@ -457,7 +460,7 @@ void tenth() { // Wait for the worker thread to end worker.join(); - cout << "Done." << endl; + cout << "Done." << '\n'; } void eleventh() { @@ -470,7 +473,7 @@ void eleventh() { data.title = "Hi there"; data.body = "This is the body."; - excluded_names_t exclusions{"id", "userId"}; + excluded_names_t const exclusions{"id", "userId"}; auto reply = RequestBuilder(ctx) .Post("http://localhost:3000/posts") @@ -563,51 +566,51 @@ void fourteenth() { } TEST(ReadmeTests, All) { - cout << "First: " << endl; + cout << "First: " << '\n'; EXPECT_NO_THROW(first()); - cout << "Second: " << endl; + cout << "Second: " << '\n'; EXPECT_NO_THROW(second()); - cout << "Third: " << endl; + cout << "Third: " << '\n'; EXPECT_NO_THROW(third()); - cout << "Forth: " << endl; + cout << "Forth: " << '\n'; EXPECT_NO_THROW(forth()); - cout << "Fifth: " << endl; + cout << "Fifth: " << '\n'; EXPECT_NO_THROW(fifth()); - cout << "Sixth: " << endl; + cout << "Sixth: " << '\n'; EXPECT_NO_THROW(sixth()); - cout << "Seventh: " << endl; + cout << "Seventh: " << '\n'; EXPECT_NO_THROW(seventh()); - cout << "Eight: " << endl; + cout << "Eight: " << '\n'; EXPECT_NO_THROW(eight()); - cout << "Ninth: " << endl; + cout << "Ninth: " << '\n'; EXPECT_NO_THROW(ninth()); - cout << "Tenth: " << endl; + cout << "Tenth: " << '\n'; EXPECT_NO_THROW(tenth()); - cout << "Eleventh: " << endl; + cout << "Eleventh: " << '\n'; EXPECT_NO_THROW(eleventh()); - cout << "Twelfth: " << endl; + cout << "Twelfth: " << '\n'; EXPECT_NO_THROW(twelfth()); - cout << "Thirtheenth: " << endl; + cout << "Thirtheenth: " << '\n'; EXPECT_NO_THROW(thirtheenth()); - cout << "Fourteenth: " << endl; + cout << "Fourteenth: " << '\n'; EXPECT_NO_THROW(fourteenth()); } int main(int argc, char * argv[]) { - RESTC_CPP_TEST_LOGGING_SETUP("debug"); + RESTC_CPP_TEST_LOGGING_SETUP("info"); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();; } diff --git a/tests/functional/UploadTests.cpp b/tests/functional/UploadTests.cpp index 785b054..05c8f73 100644 --- a/tests/functional/UploadTests.cpp +++ b/tests/functional/UploadTests.cpp @@ -41,7 +41,7 @@ int main( int argc, char * argv[] ) { ofstream file(temp_path.string()); for(int i = 0; i < 1000; i++) { - file << "This is line #" << i << endl; + file << "This is line #" << i << '\n'; } } diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index bcc2580..bb717e0 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -33,7 +33,9 @@ target_link_libraries(json_serialize_tests restc-cpp ${DEFAULT_LIBRARIES} ) -add_dependencies(json_serialize_tests externalRapidJson ${DEPENDS_GTEST} ${RESTC_CPP_EXTERNAL_DEPS}) + +add_dependencies(json_serialize_tests restc-cpp ${DEPENDS_GTEST}) + ADD_AND_RUN_UNITTEST(JSON_UNITTESTS json_serialize_tests) @@ -45,7 +47,9 @@ target_link_libraries(json_iostream_tests restc-cpp ${DEFAULT_LIBRARIES} ) -add_dependencies(json_iostream_tests externalRapidJson ${DEPENDS_GTEST} ${RESTC_CPP_EXTERNAL_DEPS}) + +add_dependencies(json_iostream_tests restc-cpp ${DEPENDS_GTEST}) + ADD_AND_RUN_UNITTEST(JSON_IOSTREAM_UNITTESTS json_iostream_tests) diff --git a/tests/unit/HttpReplyTests.cpp b/tests/unit/HttpReplyTests.cpp index 5d1be2d..3bc5ece 100644 --- a/tests/unit/HttpReplyTests.cpp +++ b/tests/unit/HttpReplyTests.cpp @@ -10,8 +10,7 @@ using namespace std; using namespace restc_cpp; -namespace restc_cpp{ -namespace unittests { +namespace restc_cpp::unittests { using test_buffers_t = std::list<std::string>; @@ -26,16 +25,14 @@ class MockReader : public DataReader { void Finish() override { } - bool IsEof() const override { - return next_buffer_ == test_buffers_.end(); - } + [[nodiscard]] bool IsEof() const override { return next_buffer_ == test_buffers_.end(); } - boost::asio::const_buffers_1 ReadSome() override { + ::restc_cpp::boost_const_buffer ReadSome() override { if (IsEof()) { return {nullptr, 0}; } - size_t data_len = next_buffer_->size(); + size_t const data_len = next_buffer_->size(); const char * const data = next_buffer_->c_str(); ++next_buffer_; return {data, data_len}; @@ -61,431 +58,407 @@ class TestReply : public ReplyImpl test_buffers_t& buffers_; }; - -} // unittests -} // restc_cpp - +} // namespace restc_cpp::unittests +// restc_cpp TEST(HttpReply, SimpleHeader) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "X-Powered-By: Express\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Cache-Control: no-cache\r\n" - "Pragma: no-cache\r\n" - "Expires: -1\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Content-Length: 0\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); - EXPECT_EQ("Express", *reply.GetHeader("X-Powered-By")); - EXPECT_EQ("Origin, Accept-Encoding", *reply.GetHeader("Vary")); - EXPECT_EQ("no-cache", *reply.GetHeader("Cache-Control")); - EXPECT_EQ("no-cache", *reply.GetHeader("Pragma")); - EXPECT_EQ("-1", *reply.GetHeader("Expires")); - EXPECT_EQ("application/json; charset=utf-8", *reply.GetHeader("Content-Type")); - EXPECT_EQ("Thu, 21 Apr 2016 13:44:36 GMT", *reply.GetHeader("Date")); - EXPECT_EQ("0", *reply.GetHeader("Content-Length")); - - }); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "X-Powered-By: Express\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n" + "Expires: -1\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: 0\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); + EXPECT_EQ("Express", *reply.GetHeader("X-Powered-By")); + EXPECT_EQ("Origin, Accept-Encoding", *reply.GetHeader("Vary")); + EXPECT_EQ("no-cache", *reply.GetHeader("Cache-Control")); + EXPECT_EQ("no-cache", *reply.GetHeader("Pragma")); + EXPECT_EQ("-1", *reply.GetHeader("Expires")); + EXPECT_EQ("application/json; charset=utf-8", *reply.GetHeader("Content-Type")); + EXPECT_EQ("Thu, 21 Apr 2016 13:44:36 GMT", *reply.GetHeader("Date")); + EXPECT_EQ("0", *reply.GetHeader("Content-Length")); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, SimpleSegmentedHeader) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n"); - buffer.push_back("Server: Cowboy\r\n"); - buffer.push_back("Connection: keep-alive\r\n"); - buffer.push_back("X-Powered-By: Express\r\n"); - buffer.push_back("Vary: Origin, Accept-Encoding\r\n"); - buffer.push_back("Cache-Control: no-cache\r\n"); - buffer.push_back("Pragma: no-cache\r\n"); - buffer.push_back("Expires: -1\r\n"); - buffer.push_back("Content-Type: application/json; charset=utf-8\r\n"); - buffer.push_back("Content-Length: 0\r\n"); - buffer.push_back("Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n"); - buffer.push_back("\r\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - - EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); - EXPECT_EQ("0", *reply.GetHeader("Content-Length")); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n"); + buffer.emplace_back("Server: Cowboy\r\n"); + buffer.emplace_back("Connection: keep-alive\r\n"); + buffer.emplace_back("X-Powered-By: Express\r\n"); + buffer.emplace_back("Vary: Origin, Accept-Encoding\r\n"); + buffer.emplace_back("Cache-Control: no-cache\r\n"); + buffer.emplace_back("Pragma: no-cache\r\n"); + buffer.emplace_back("Expires: -1\r\n"); + buffer.emplace_back("Content-Type: application/json; charset=utf-8\r\n"); + buffer.emplace_back("Content-Length: 0\r\n"); + buffer.emplace_back("Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n"); + buffer.emplace_back("\r\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + + EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); + EXPECT_EQ("0", *reply.GetHeader("Content-Length")); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, SimpleVerySegmentedHeader) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\nSer"); - buffer.push_back("ver: Cowboy\r\n"); - buffer.push_back("Connection: keep-alive\r"); - buffer.push_back("\nX-Powered-By: Express\r\nV"); - buffer.push_back("ary"); - buffer.push_back(": Origin, Accept-Encoding\r\nCache-Control: no-cache\r\n"); - buffer.push_back("Pragma: no-cache\r\n"); - buffer.push_back("Expires: -1\r\n"); - buffer.push_back("Content-Type: application/json; charset=utf-8\r\n"); - buffer.push_back("Content-Length: 0\r\n"); - buffer.push_back("Date: Thu, 21 Apr 2016 13:44:36 GMT"); - buffer.push_back("\r"); - buffer.push_back("\n"); - buffer.push_back("\r"); - buffer.push_back("\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - - EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); - EXPECT_EQ("0", *reply.GetHeader("Content-Length")); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\nSer"); + buffer.emplace_back("ver: Cowboy\r\n"); + buffer.emplace_back("Connection: keep-alive\r"); + buffer.emplace_back("\nX-Powered-By: Express\r\nV"); + buffer.emplace_back("ary"); + buffer.emplace_back(": Origin, Accept-Encoding\r\nCache-Control: no-cache\r\n"); + buffer.emplace_back("Pragma: no-cache\r\n"); + buffer.emplace_back("Expires: -1\r\n"); + buffer.emplace_back("Content-Type: application/json; charset=utf-8\r\n"); + buffer.emplace_back("Content-Length: 0\r\n"); + buffer.emplace_back("Date: Thu, 21 Apr 2016 13:44:36 GMT"); + buffer.emplace_back("\r"); + buffer.emplace_back("\n"); + buffer.emplace_back("\r"); + buffer.emplace_back("\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + + EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); + EXPECT_EQ("0", *reply.GetHeader("Content-Length")); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, SimpleBody) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Content-Length: 10\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n" - "1234567890"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("10", *reply.GetHeader("Content-Length")); - EXPECT_EQ(10, (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: 10\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n" + "1234567890"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("10", *reply.GetHeader("Content-Length")); + EXPECT_EQ(10, (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, SimpleBody2) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Content-Length: 10\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - buffer.push_back("1234567890"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("10", *reply.GetHeader("Content-Length")); - EXPECT_EQ(10, (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: 10\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + buffer.emplace_back("1234567890"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("10", *reply.GetHeader("Content-Length")); + EXPECT_EQ(10, (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, SimpleBody3) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Content-Length: 10\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - buffer.push_back("1234567"); - buffer.push_back("890"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("10", *reply.GetHeader("Content-Length")); - EXPECT_EQ(10, (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: 10\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + buffer.emplace_back("1234567"); + buffer.emplace_back("890"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("10", *reply.GetHeader("Content-Length")); + EXPECT_EQ(10, (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, SimpleBody4) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Content-Length: 10\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n12"); - buffer.push_back("34567"); - buffer.push_back("890"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("10", *reply.GetHeader("Content-Length")); - EXPECT_EQ(10, (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: 10\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n12"); + buffer.emplace_back("34567"); + buffer.emplace_back("890"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("10", *reply.GetHeader("Content-Length")); + EXPECT_EQ(10, (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, ChunkedBody) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Transfer-Encoding: chunked\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n" - "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks." - "\r\n0\r\n\r\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - EXPECT_EQ((0x4 + 0x5 + 0xE), (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Transfer-Encoding: chunked\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n" + "4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks." + "\r\n0\r\n\r\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + EXPECT_EQ((0x4 + 0x5 + 0xE), (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, ChunkedBody2) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Transfer-Encoding: chunked\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - buffer.push_back("4\r\nWiki\r\n"); - buffer.push_back("5\r\npedia\r\n"); - buffer.push_back("E\r\n in\r\n\r\nchunks.\r\n"); - buffer.push_back("0\r\n\r\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - EXPECT_EQ((0x4 + 0x5 + 0xE), (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Transfer-Encoding: chunked\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + buffer.emplace_back("4\r\nWiki\r\n"); + buffer.emplace_back("5\r\npedia\r\n"); + buffer.emplace_back("E\r\n in\r\n\r\nchunks.\r\n"); + buffer.emplace_back("0\r\n\r\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + EXPECT_EQ((0x4 + 0x5 + 0xE), (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, ChunkedBody4) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Transfer-Encoding: chunked\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - buffer.push_back("4\r\nW"); - buffer.push_back("iki\r\n5\r\npedi"); - buffer.push_back("a\r\nE\r\n in\r\n\r\nchunks.\r"); - buffer.push_back("\n"); - buffer.push_back("0"); - buffer.push_back("\r"); - buffer.push_back("\n"); - buffer.push_back("\r"); - buffer.push_back("\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - EXPECT_EQ((0x4 + 0x5 + 0xE), (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Transfer-Encoding: chunked\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + buffer.emplace_back("4\r\nW"); + buffer.emplace_back("iki\r\n5\r\npedi"); + buffer.emplace_back("a\r\nE\r\n in\r\n\r\nchunks.\r"); + buffer.emplace_back("\n"); + buffer.emplace_back("0"); + buffer.emplace_back("\r"); + buffer.emplace_back("\n"); + buffer.emplace_back("\r"); + buffer.emplace_back("\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + EXPECT_EQ((0x4 + 0x5 + 0xE), (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, ChunkedTrailer) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Transfer-Encoding: chunked\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - buffer.push_back("4\r\nWiki\r\n"); - buffer.push_back("5\r\npedia\r\n"); - buffer.push_back("E\r\n in\r\n\r\nchunks.\r\n"); - buffer.push_back("0\r\n"); - buffer.push_back("Server: Indian\r\n"); - buffer.push_back("Connection: close\r\n"); - buffer.push_back("\r\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Indian", *reply.GetHeader("Server")); - EXPECT_EQ("close", *reply.GetHeader("Connection")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - EXPECT_EQ((0x4 + 0x5 + 0xE), (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Transfer-Encoding: chunked\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + buffer.emplace_back("4\r\nWiki\r\n"); + buffer.emplace_back("5\r\npedia\r\n"); + buffer.emplace_back("E\r\n in\r\n\r\nchunks.\r\n"); + buffer.emplace_back("0\r\n"); + buffer.emplace_back("Server: Indian\r\n"); + buffer.emplace_back("Connection: close\r\n"); + buffer.emplace_back("\r\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Indian", *reply.GetHeader("Server")); + EXPECT_EQ("close", *reply.GetHeader("Connection")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + EXPECT_EQ((0x4 + 0x5 + 0xE), (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } TEST(HttpReply, ChunkedParameterAndTrailer) { ::restc_cpp::unittests::test_buffers_t buffer; - buffer.push_back("HTTP/1.1 200 OK\r\n" - "Server: Cowboy\r\n" - "Connection: keep-alive\r\n" - "Vary: Origin, Accept-Encoding\r\n" - "Content-Type: application/json; charset=utf-8\r\n" - "Transfer-Encoding: chunked\r\n" - "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" - "\r\n"); - buffer.push_back("4;test=1;tset=\"yyyy\"\r\nWiki\r\n"); - buffer.push_back("5;more-to-follow\r\npedia\r\n"); - buffer.push_back("E;77\r\n in\r\n\r\nchunks.\r\n"); - buffer.push_back("0;this-is-the-end\r\n"); - buffer.push_back("Server: Indian\r\n"); - buffer.push_back("Connection: close\r\n"); - buffer.push_back("\r\n"); - - auto rest_client = RestClient::Create(); - auto f = rest_client->ProcessWithPromise([&](Context& ctx) { - - ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); - - reply.SimulateServerReply(); - - EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); - EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - - auto body = reply.GetBodyAsString(); - - EXPECT_EQ("Indian", *reply.GetHeader("Server")); - EXPECT_EQ("close", *reply.GetHeader("Connection")); - EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); - EXPECT_EQ((0x4 + 0x5 + 0xE), (int)body.size()); - - }); - - EXPECT_NO_THROW(f.get()); + buffer.emplace_back("HTTP/1.1 200 OK\r\n" + "Server: Cowboy\r\n" + "Connection: keep-alive\r\n" + "Vary: Origin, Accept-Encoding\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Transfer-Encoding: chunked\r\n" + "Date: Thu, 21 Apr 2016 13:44:36 GMT\r\n" + "\r\n"); + buffer.emplace_back("4;test=1;tset=\"yyyy\"\r\nWiki\r\n"); + buffer.emplace_back("5;more-to-follow\r\npedia\r\n"); + buffer.emplace_back("E;77\r\n in\r\n\r\nchunks.\r\n"); + buffer.emplace_back("0;this-is-the-end\r\n"); + buffer.emplace_back("Server: Indian\r\n"); + buffer.emplace_back("Connection: close\r\n"); + buffer.emplace_back("\r\n"); + + auto rest_client = RestClient::Create(); + auto f = rest_client->ProcessWithPromise([&](Context &ctx) { + ::restc_cpp::unittests::TestReply reply(ctx, *rest_client, buffer); + + reply.SimulateServerReply(); + + EXPECT_EQ("Cowboy", *reply.GetHeader("Server")); + EXPECT_EQ("keep-alive", *reply.GetHeader("Connection")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + + auto body = reply.GetBodyAsString(); + + EXPECT_EQ("Indian", *reply.GetHeader("Server")); + EXPECT_EQ("close", *reply.GetHeader("Connection")); + EXPECT_EQ("chunked", *reply.GetHeader("Transfer-Encoding")); + EXPECT_EQ((0x4 + 0x5 + 0xE), (int) body.size()); + }); + + EXPECT_NO_THROW(f.get()); } int main( int argc, char * argv[] ) diff --git a/tests/unit/Iostream2JsonTests.cpp b/tests/unit/Iostream2JsonTests.cpp index 4affd8f..e57b247 100644 --- a/tests/unit/Iostream2JsonTests.cpp +++ b/tests/unit/Iostream2JsonTests.cpp @@ -36,9 +36,9 @@ BOOST_FUSION_ADAPT_STRUCT( // ---------------------- # 58 -typedef vector<unsigned char> LOCAL; -typedef vector<unsigned char> GLOBAL; -typedef vector<unsigned char> ADDRESS; +using LOCAL = vector<unsigned char>; +using GLOBAL = vector<unsigned char>; +using ADDRESS = vector<unsigned char>; struct MAC { ADDRESS address; @@ -47,7 +47,7 @@ BOOST_FUSION_ADAPT_STRUCT( MAC, (ADDRESS, address) ) -typedef vector<MAC> MACLIST; +using MACLIST = vector<MAC>; struct DeviceList{ LOCAL local; @@ -60,10 +60,10 @@ BOOST_FUSION_ADAPT_STRUCT( (GLOBAL, global) (MACLIST, maclst) ) -typedef vector<DeviceList> DeviceLst; +using DeviceLst = vector<DeviceList>; struct Config2 { int nIdSchedule = {}; - int nDCUNo; + int nDCUNo{}; DeviceLst lst; }; BOOST_FUSION_ADAPT_STRUCT( @@ -83,11 +83,11 @@ TEST(IOstream2Json, ReadConfigurationFromFile) { { ofstream json_out(tmpname.native()); - json_out << '{' << endl - << R"("max_something":100,)" << endl - << R"("name":"Test Data",)" << endl - << R"("url":"https://www.example.com")" << endl - << '}'; + json_out << '{' << '\n' + << R"("max_something":100,)" << '\n' + << R"("name":"Test Data",)" << '\n' + << R"("url":"https://www.example.com")" << '\n' + << '}'; } ifstream ifs(tmpname.native()); @@ -130,15 +130,14 @@ TEST(IOstream2Json, issue58) { { // Read the ;config file into the config object. SerializeFromJson(config, ifs); - cout<<"done"<<endl; + cout << "done" << '\n'; } ofstream ofs(tmpname.c_str()); config.lst[0].maclst[0].address[2] = 11; config.lst[0].maclst[0].address[3] = 11; config.lst[0].maclst[0].address[4] = 11; SerializeToJson(config, ofs); - cout<<"done"<<endl; - + cout << "done" << '\n'; } int main( int argc, char * argv[] ) diff --git a/tests/unit/JsonSerializeTests.cpp b/tests/unit/JsonSerializeTests.cpp index 314b2eb..5c1f593 100644 --- a/tests/unit/JsonSerializeTests.cpp +++ b/tests/unit/JsonSerializeTests.cpp @@ -141,7 +141,7 @@ BOOST_FUSION_ADAPT_STRUCT( ) struct Quotes { - int id; + int id{}; string origin; string quote; }; @@ -179,13 +179,13 @@ struct Group { std::list<Person> more_members_ = {}, std::deque<Person> even_more_members_ = {}) : name{std::move(name_)}, gid{gid_}, leader{std::move(leader_)} - , members{move(members_)}, more_members{move(more_members_)} - , even_more_members{move(even_more_members_)} + , members{std::move(members_)}, more_members{std::move(more_members_)} + , even_more_members{std::move(even_more_members_)} {} Group() = default; Group(const Group&) = default; - Group(Group&&) = default; + Group(Group &&) noexcept = default; std::string name; int gid = 0; @@ -207,7 +207,7 @@ BOOST_FUSION_ADAPT_STRUCT( ) TEST(JsonSerialize, SerializeSimpleObject) { - Person person = { 100, "John Doe"s, 123.45 }; + Person const person = {100, "John Doe"s, 123.45}; StringBuffer s; Writer<StringBuffer> writer(s); @@ -223,7 +223,7 @@ TEST(JsonSerialize, SerializeSimpleObject) { } TEST(JsonSerialize, SerializeNestedObject) { - Group group = Group(string("Group name"), 99, Person( 100, string("John Doe"), 123.45 )); + Group const group = Group(string("Group name"), 99, Person(100, string("John Doe"), 123.45)); StringBuffer s; Writer<StringBuffer> writer(s); @@ -241,7 +241,7 @@ TEST(JsonSerialize, SerializeNestedObject) { TEST(JsonSerialize, SerializeVector) { - std::vector<int> ints = {-1,2,3,4,5,6,7,8,9,-10}; + std::vector<int> const ints = {-1, 2, 3, 4, 5, 6, 7, 8, 9, -10}; StringBuffer s; Writer<StringBuffer> writer(s); @@ -257,7 +257,7 @@ TEST(JsonSerialize, SerializeVector) } TEST(JsonSerialize, SerializeList) { - std::list<unsigned int> ints = {1,2,3,4,5,6,7,8,9,10}; + std::list<unsigned int> const ints = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; StringBuffer s; Writer<StringBuffer> writer(s); @@ -274,7 +274,7 @@ TEST(JsonSerialize, SerializeList) { TEST(JsonSerialize, SerializeNestedVector) { - std::vector<std::vector<int>> nested_ints = {{-1,2,3},{4,5,-6}}; + std::vector<std::vector<int>> const nested_ints = {{-1, 2, 3}, {4, 5, -6}}; StringBuffer s; Writer<StringBuffer> writer(s); @@ -291,7 +291,7 @@ TEST(JsonSerialize, SerializeNestedVector) TEST(JsonSerialize, DeserializeSimpleObject) { Person person; - std::string json = R"({ "id" : 100, "name" : "John Longdue Doe", "balance" : 123.45 })"; + std::string const json = R"({ "id" : 100, "name" : "John Longdue Doe", "balance" : 123.45 })"; RapidJsonDeserializer<Person> handler(person); Reader reader; @@ -308,12 +308,12 @@ TEST(JsonSerialize, DeserializeNestedObject) { assert(boost::fusion::traits::is_sequence<Person>::value); Group group; - std::string json = - R"({"name" : "qzar", "gid" : 1, "leader" : { "id" : 100, "name" : "Dolly Doe", "balance" : 123.45 },)" - R"("members" : [{ "id" : 101, "name" : "m1", "balance" : 0.0}, { "id" : 102, "name" : "m2", "balance" : 1.0}],)" - R"("more_members" : [{ "id" : 103, "name" : "m3", "balance" : 0.1}, { "id" : 104, "name" : "m4", "balance" : 2.0}],)" - R"("even_more_members" : [{ "id" : 321, "name" : "m10", "balance" : 0.1}, { "id" : 322, "name" : "m11", "balance" : 22.0}])" - R"(})"; + std::string const json + = R"({"name" : "qzar", "gid" : 1, "leader" : { "id" : 100, "name" : "Dolly Doe", "balance" : 123.45 },)" + R"("members" : [{ "id" : 101, "name" : "m1", "balance" : 0.0}, { "id" : 102, "name" : "m2", "balance" : 1.0}],)" + R"("more_members" : [{ "id" : 103, "name" : "m3", "balance" : 0.1}, { "id" : 104, "name" : "m4", "balance" : 2.0}],)" + R"("even_more_members" : [{ "id" : 321, "name" : "m10", "balance" : 0.1}, { "id" : 322, "name" : "m11", "balance" : 22.0}])" + R"(})"; RapidJsonDeserializer<Group> handler(group); Reader reader; @@ -348,7 +348,7 @@ TEST(JsonSerialize, DeserializeNestedObject) { } TEST(JsonSerialize, DeserializeIntVector) { - std::string json = R"([1,2,3,4,5,6,7,8,9,10])"; + std::string const json = R"([1,2,3,4,5,6,7,8,9,10])"; std::vector<int> ints; RapidJsonDeserializer<decltype(ints)> handler(ints); @@ -365,7 +365,7 @@ TEST(JsonSerialize, DeserializeIntVector) { } TEST(JsonSerialize, DeserializeNestedArray) { - std::string json = R"([[1, 2, 3],[4, 5, 6]])"; + std::string const json = R"([[1, 2, 3],[4, 5, 6]])"; std::vector<std::vector<int>> nested_ints; RapidJsonDeserializer<decltype(nested_ints)> handler(nested_ints); @@ -385,7 +385,7 @@ TEST(JsonSerialize, DeserializeNestedArray) { } TEST(JsonSerialize, DeserializeKeyValueMap) { - std::string json = R"({"key1":"value1", "key2":"value2"})"; + std::string const json = R"({"key1":"value1", "key2":"value2"})"; std::map<string, string> keyvalue; RapidJsonDeserializer<decltype(keyvalue)> handler(keyvalue); @@ -399,7 +399,8 @@ TEST(JsonSerialize, DeserializeKeyValueMap) { } TEST(JsonSerialize, DeserializeKeyValueMapWithObject) { - string json = R"({"dog1":{ "id" : 1, "name" : "Ares", "balance" : 123.45}, "dog2":{ "id" : 2, "name" : "Nusse", "balance" : 234.56}})"; + string const json + = R"({"dog1":{ "id" : 1, "name" : "Ares", "balance" : 123.45}, "dog2":{ "id" : 2, "name" : "Nusse", "balance" : 234.56}})"; map<string, Person> keyvalue; RapidJsonDeserializer<decltype(keyvalue)> handler(keyvalue); @@ -431,7 +432,7 @@ TEST(JsonSerialize, DeserializeMemoryLimit) RapidJsonSerializer<decltype(quotes), decltype(writer)> serializer(quotes, writer); serializer.Serialize(); - std::string json = s.GetString(); + std::string const json = s.GetString(); quotes.clear(); @@ -499,7 +500,8 @@ TEST(JsonSerialize, MissingObjectName) { TEST(JsonSerialize, MissingPropertyName) { Person person; - std::string json = R"({ "id" : 100, "name" : "John Longdue Doe", "balance" : 123.45, "foofoo":"foo", "oofoof":"oof" })"; + std::string const json + = R"({ "id" : 100, "name" : "John Longdue Doe", "balance" : 123.45, "foofoo":"foo", "oofoof":"oof" })"; RapidJsonDeserializer<Person> handler(person); Reader reader; @@ -532,7 +534,8 @@ TEST(JsonSerialize, SkipMissingObjectNameNotAllowed) { TEST(JsonSerialize, MissingPropertyNameNotAllowed) { Person person; - std::string json = R"({ "id" : 100, "name" : "John Longdue Doe", "balance" : 123.45, "foofoo":"foo", "oofoof":"oof" })"; + std::string const json + = R"({ "id" : 100, "name" : "John Longdue Doe", "balance" : 123.45, "foofoo":"foo", "oofoof":"oof" })"; serialize_properties_t sprop; sprop.ignore_unknown_properties = false; @@ -545,7 +548,7 @@ TEST(JsonSerialize, MissingPropertyNameNotAllowed) { #if (__cplusplus >= 201703L) TEST(JsonSerialize, DesearializeOptionalBoolEmpty) { House house; - std::string json = R"({ "is_enabled": null })"; // No value + std::string const json = R"({ "is_enabled": null })"; // No value serialize_properties_t sprop; sprop.ignore_unknown_properties = false; @@ -558,7 +561,7 @@ TEST(JsonSerialize, DesearializeOptionalBoolEmpty) { TEST(JsonSerialize, DesearializeOptionalBoolTrue) { House house; - std::string json = R"({ "is_enabled": true })"; // No value + std::string const json = R"({ "is_enabled": true })"; // No value serialize_properties_t sprop; sprop.ignore_unknown_properties = false; @@ -572,7 +575,7 @@ TEST(JsonSerialize, DesearializeOptionalBoolTrue) { TEST(JsonSerialize, DesearializeOptionalBoolFalse) { House house; - std::string json = R"({ "is_enabled": false })"; // No value + std::string const json = R"({ "is_enabled": false })"; // No value serialize_properties_t sprop; sprop.ignore_unknown_properties = false; @@ -587,7 +590,7 @@ TEST(JsonSerialize, DesearializeOptionalBoolFalse) { TEST(JsonSerialize, DesearializeOptionalObjctEmpty) { House house; house.person = Person{1, "foo", 0.0}; - std::string json = R"({ "person": null })"; // No value + std::string const json = R"({ "person": null })"; // No value serialize_properties_t sprop; sprop.ignore_unknown_properties = false; @@ -600,7 +603,8 @@ TEST(JsonSerialize, DesearializeOptionalObjctEmpty) { TEST(JsonSerialize, DesearializeOptionalObjctAssign) { House house; - std::string json = R"({ "person": { "id" : 100, "name" : "John Doe", "balance" : 123.45 }})"; + std::string const json + = R"({ "person": { "id" : 100, "name" : "John Doe", "balance" : 123.45 }})"; serialize_properties_t sprop; sprop.ignore_unknown_properties = false; @@ -613,7 +617,7 @@ TEST(JsonSerialize, DesearializeOptionalObjctAssign) { } TEST(JsonSerialize, SerializeOptionalAllEmptyShowEmpty) { - House house; + House const house; StringBuffer s; Writer<StringBuffer> writer(s); @@ -631,7 +635,7 @@ TEST(JsonSerialize, SerializeOptionalAllEmptyShowEmpty) { TEST(JsonSerialize, SerializeOptionalAllEmpty) { - House house; + House const house; StringBuffer s; Writer<StringBuffer> writer(s); @@ -754,7 +758,7 @@ TEST(JsonSerialize, SerializeOptionalObjectWithRecursiveOptionalData) { } TEST(JsonSerialize, SerializeIgnoreEmptyString) { - Pet pet; + Pet const pet; StringBuffer s; Writer<StringBuffer> writer(s); @@ -771,8 +775,7 @@ TEST(JsonSerialize, SerializeIgnoreEmptyString) { } TEST(JsonSerialize, SerializeEmptyOptionalWithZeroValue) { - - Number data; + Number const data; StringBuffer s; Writer<StringBuffer> writer(s); @@ -826,7 +829,7 @@ TEST(JsonSerialize, SerializeOptionalWithEmptyStringValue) { TEST(JsonSerialize, DeserializeBoolFromStringTrue) { Bool bval; - std::string json = R"({ "value" : "true" })"; + std::string const json = R"({ "value" : "true" })"; RapidJsonDeserializer<Bool> handler(bval); Reader reader; @@ -838,7 +841,7 @@ TEST(JsonSerialize, DeserializeBoolFromStringTrue) { TEST(JsonSerialize, DeserializeBoolFromStringFalse) { Bool bval{true}; - std::string json = R"({ "value" : "false" })"; + std::string const json = R"({ "value" : "false" })"; RapidJsonDeserializer<Bool> handler(bval); Reader reader; @@ -851,7 +854,7 @@ TEST(JsonSerialize, DeserializeBoolFromStringFalse) { TEST(JsonSerialize, DeserializeBoolFromIntTrue) { Bool bval; - std::string json = R"({ "value" : 10 })"; + std::string const json = R"({ "value" : 10 })"; RapidJsonDeserializer<Bool> handler(bval); Reader reader; @@ -863,7 +866,7 @@ TEST(JsonSerialize, DeserializeBoolFromIntTrue) { TEST(JsonSerialize, DeserializeBoolFromIntFalse) { Bool bval{true}; - std::string json = R"({ "value" : 0 })"; + std::string const json = R"({ "value" : 0 })"; RapidJsonDeserializer<Bool> handler(bval); Reader reader; @@ -875,7 +878,7 @@ TEST(JsonSerialize, DeserializeBoolFromIntFalse) { TEST(JsonSerialize, DeserializeIntFromString1) { Int ival; - std::string json = R"({ "value" : "1" })"; + std::string const json = R"({ "value" : "1" })"; RapidJsonDeserializer<Int> handler(ival); Reader reader; @@ -887,7 +890,8 @@ TEST(JsonSerialize, DeserializeIntFromString1) { TEST(JsonSerialize, DeserializeNumbersFromStrings) { Numbers numbers; - std::string json = R"({ "intval" : "-123", "sizetval": "55", "uint32": "123456789", "int64val": "-9876543212345", "uint64val": "123451234512345" })"; + std::string const json + = R"({ "intval" : "-123", "sizetval": "55", "uint32": "123456789", "int64val": "-9876543212345", "uint64val": "123451234512345" })"; RapidJsonDeserializer<Numbers> handler(numbers); Reader reader; @@ -902,8 +906,7 @@ TEST(JsonSerialize, DeserializeNumbersFromStrings) { } TEST(JsonSerialize, DeserializeWithStdToStringSpecialization) { - - DataHolder obj; + DataHolder const obj; StringBuffer s; Writer<StringBuffer> writer(s); @@ -919,8 +922,7 @@ TEST(JsonSerialize, DeserializeWithStdToStringSpecialization) { TEST(JsonSerialize, DeserializeWithoutStdToStringSpecialization) { - - NoDataHolder obj; + NoDataHolder const obj; StringBuffer s; Writer<StringBuffer> writer(s); diff --git a/tests/unit/UrlTests.cpp b/tests/unit/UrlTests.cpp index 58f2464..834415c 100644 --- a/tests/unit/UrlTests.cpp +++ b/tests/unit/UrlTests.cpp @@ -15,7 +15,7 @@ using namespace restc_cpp; TEST(Url, Simple) { - Url url("http://github.com"); + Url const url("http://github.com"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("80"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -24,7 +24,7 @@ TEST(Url, Simple) TEST(Url, UrlSimpleSlash) { - Url url("http://github.com/"); + Url const url("http://github.com/"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("80"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -33,7 +33,7 @@ TEST(Url, UrlSimpleSlash) TEST(Url, UrlWithPath) { - Url url("http://github.com/jgaa"); + Url const url("http://github.com/jgaa"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("80"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -42,16 +42,25 @@ TEST(Url, UrlWithPath) TEST(Url, UrlWithPathAndSlash) { - Url url("http://github.com/jgaa/"); + Url const url("http://github.com/jgaa/"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("80"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); EXPECT_EQ("/jgaa/"s, url.GetPath()); } +TEST(Url, UrlWithPathInclColon) +{ + Url const url("http://github.com/jgaa:test"); + EXPECT_EQ("github.com"s, url.GetHost()); + EXPECT_EQ("80"s, url.GetPort()); + EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); + EXPECT_EQ("/jgaa:test"s, url.GetPath()); +} + TEST(Url, HttpWithPort) { - Url url("http://github.com:56"); + Url const url("http://github.com:56"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("56"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -60,7 +69,7 @@ TEST(Url, HttpWithPort) TEST(Url, HttpWithLongPort) { - Url url("http://github.com:1234567789"); + Url const url("http://github.com:1234567789"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("1234567789"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -69,7 +78,7 @@ TEST(Url, HttpWithLongPort) TEST(Url, HttpWithPortAndSlash) { - Url url("http://github.com:56/"); + Url const url("http://github.com:56/"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("56"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -78,16 +87,25 @@ TEST(Url, HttpWithPortAndSlash) TEST(Url, HttpWithPortAndPath) { - Url url("http://github.com:12345/jgaa"); + Url const url("http://github.com:12345/jgaa"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("12345"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); EXPECT_EQ("/jgaa"s, url.GetPath()); } +TEST(Url, HttpWithPortAndPathInclColon) +{ + Url const url("http://github.com:12345/jgaa:test"); + EXPECT_EQ("github.com"s, url.GetHost()); + EXPECT_EQ("12345"s, url.GetPort()); + EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); + EXPECT_EQ("/jgaa:test"s, url.GetPath()); +} + TEST(Url, HttpWithPortAndPathPath) { - Url url("http://github.com:12345/jgaa/andmore"); + Url const url("http://github.com:12345/jgaa/andmore"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("12345"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTP, url.GetProtocol()); @@ -96,7 +114,7 @@ TEST(Url, HttpWithPortAndPathPath) TEST(Url, UrlSimpleHttps) { - Url url("https://github.com"); + Url const url("https://github.com"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("443"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -106,7 +124,7 @@ TEST(Url, UrlSimpleHttps) ///// TEST(Url, HttpsUrlSimpleSlash) { - Url url("https://github.com/"); + Url const url("https://github.com/"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("443"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -115,7 +133,7 @@ TEST(Url, HttpsUrlSimpleSlash) TEST(Url, HttpsUrlWithPath) { - Url url("https://github.com/jgaa"); + Url const url("https://github.com/jgaa"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("443"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -124,7 +142,7 @@ TEST(Url, HttpsUrlWithPath) TEST(Url, HttpsUrlWithPathAndSlash) { - Url url("https://github.com/jgaa/"); + Url const url("https://github.com/jgaa/"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("443"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -133,7 +151,7 @@ TEST(Url, HttpsUrlWithPathAndSlash) TEST(Url, HttpsWithPort) { - Url url("https://github.com:56"); + Url const url("https://github.com:56"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("56"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -142,7 +160,7 @@ TEST(Url, HttpsWithPort) TEST(Url, HttpsWithLongPort) { - Url url("https://github.com:1234567789"); + Url const url("https://github.com:1234567789"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("1234567789"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -151,7 +169,7 @@ TEST(Url, HttpsWithLongPort) TEST(Url, HttpsWithPortAndSlash) { - Url url("https://github.com:56/"); + Url const url("https://github.com:56/"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("56"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -160,7 +178,7 @@ TEST(Url, HttpsWithPortAndSlash) TEST(Url, HttpsWithPortAndPath) { - Url url("https://github.com:12345/jgaa"); + Url const url("https://github.com:12345/jgaa"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("12345"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -169,7 +187,7 @@ TEST(Url, HttpsWithPortAndPath) TEST(Url, HttpsWithPortAndPathPath) { - Url url("https://github.com:12345/jgaa/andmore"); + Url const url("https://github.com:12345/jgaa/andmore"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("12345"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -178,7 +196,7 @@ TEST(Url, HttpsWithPortAndPathPath) TEST(Url, HttpsUrlSimple) { - Url url("https://github.com"); + Url const url("https://github.com"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("443"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -189,7 +207,7 @@ TEST(Url, HttpsUrlSimple) TEST(Url, HttpsWithPortAndPathAndArgs) { - Url url("https://github.com:12345/jgaa?arg=abc:5432"); + Url const url("https://github.com:12345/jgaa?arg=abc:5432"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("12345"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol()); @@ -199,7 +217,7 @@ TEST(Url, HttpsWithPortAndPathAndArgs) TEST(Url, HttpsWithArgsOnly) { - Url url("https://github.com?arg=abc:123"); + Url const url("https://github.com?arg=abc:123"); EXPECT_EQ("github.com"s, url.GetHost()); EXPECT_EQ("443"s, url.GetPort()); EXPECT_EQ_ENUM(Url::Protocol::HTTPS, url.GetProtocol());