diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index b062acf..22c7c5a 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -27,7 +27,7 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_INCLUDE_DIR=${{ runner.temp }}/avro-cpp-1.10.2-install/include -DAVRO_LIBRARY_RELEASE=${{ runner.temp }}/avro-cpp-1.10.2-install/lib/avrocpp_s.lib -DWITH_ETP_SSL=FALSE ${{ github.workspace }} + cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-1.10.2-install -DWITH_ETP_SSL=FALSE ${{ github.workspace }} cmake --build . --config Release -j2 windows-2019-with-fesapi: runs-on: windows-2019 @@ -66,7 +66,7 @@ jobs: cmake --build . --config Release --target INSTALL - name: FESAPI install run: | - git clone --branch v2.6.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src + git clone --branch v2.8.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src cd ${{ runner.temp }} mkdir fesapi-build cd fesapi-build @@ -78,7 +78,7 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_INCLUDE_DIR=${{ runner.temp }}/avro-cpp-1.10.2-install/include -DAVRO_LIBRARY_RELEASE=${{ runner.temp }}/avro-cpp-1.10.2-install/lib/avrocpp_s.lib -DWITH_FESAPI=TRUE -DFESAPI_INCLUDE_DIR=${{ runner.temp }}/fesapi-install/include -DFESAPI_LIBRARY_RELEASE=${{ runner.temp }}/fesapi-install/lib/FesapiCpp.2.6.0.0.lib -DWITH_ETP_SSL=FALSE ${{ github.workspace }} + cmake -G"Visual Studio 16 2019" -A x64 -T host=x64 -Wno-dev -Wno-deprecated -DBoost_INCLUDE_DIR=${{ runner.temp }}\boost-install -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-1.10.2-install -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi-install -DWITH_ETP_SSL=FALSE ${{ github.workspace }} cmake --build . --config Release -j2 ubuntu-20: runs-on: ubuntu-20.04 @@ -105,7 +105,7 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -DAVRO_INCLUDE_DIR=${{ runner.temp }}/avro-cpp-1.11.0-install/include -DAVRO_LIBRARY_RELEASE=${{ runner.temp }}/avro-cpp-1.11.0-install/lib/libavrocpp_s.a ${{ github.workspace }} + cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-1.11.0-install ${{ github.workspace }} cmake --build . --config Release -j2 ubuntu-20-java8: runs-on: ubuntu-20.04 @@ -136,7 +136,7 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -DAVRO_INCLUDE_DIR=${{ runner.temp }}/avro-cpp-1.11.0-install/include -DAVRO_LIBRARY_RELEASE=${{ runner.temp }}/avro-cpp-1.11.0-install/lib/libavrocpp_s.a -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} + cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-1.11.0-install -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} cmake --build . --config Release -j2 ubuntu-20-java8-with-fesapi: runs-on: ubuntu-20.04 @@ -167,7 +167,7 @@ jobs: sudo apt install -y ${{ matrix.xcc_pkg }} libhdf5-dev libminizip-dev libboost-all-dev - name: FESAPI install run: | - git clone --branch v2.6.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src + git clone --branch v2.8.0.0 --single-branch https://github.com/F2I-Consulting/fesapi.git ${{ runner.temp }}/fesapi-src cd ${{ runner.temp }} mkdir fesapi-build cd fesapi-build @@ -191,5 +191,5 @@ jobs: cd ${{ github.workspace }}/.. mkdir build cd build - cmake -DAVRO_INCLUDE_DIR=${{ runner.temp }}/avro-cpp-1.11.0-install/include -DAVRO_LIBRARY_RELEASE=${{ runner.temp }}/avro-cpp-1.11.0-install/lib/libavrocpp_s.a -DWITH_FESAPI=TRUE -DFESAPI_INCLUDE_DIR=${{ runner.temp }}/fesapi-install/include -DFESAPI_LIBRARY_RELEASE=${{ runner.temp }}/fesapi-install/lib/libFesapiCpp.so -DFESAPI_JAR=${{ runner.temp }}/fesapi-install/lib/fesapiJava-2.6.0.0.jar -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} + cmake -DAVRO_ROOT=${{ runner.temp }}/avro-cpp-1.11.0-install -DWITH_FESAPI=TRUE -DFESAPI_ROOT=${{ runner.temp }}/fesapi-install -DFESAPI_JAR=${{ runner.temp }}/fesapi-install/lib/fesapiJava-2.8.0.0.jar -DWITH_JAVA_WRAPPING=TRUE ${{ github.workspace }} -DCMAKE_C_COMPILER=${{ matrix.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} cmake --build . --config Release -j2 diff --git a/CMakeLists.txt b/CMakeLists.txt index c71d770..f5f2457 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.12) project(Fetpapi) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) + set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "DEBUG_POSTFIX property is initialized when the target is created to the value of this variable except for executable targets") # Allow to have all executables generated in the same binary directory. Otherwise there would be in a directory different for each add_subdirectory cmake commande. @@ -9,20 +11,21 @@ set (FETPAPI_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) # version mechanism set (Fetpapi_VERSION_MAJOR 0) -set (Fetpapi_VERSION_MINOR 1) +set (Fetpapi_VERSION_MINOR 2) set (Fetpapi_VERSION_PATCH 0) set (Fetpapi_VERSION_TWEAK 0) + set (Fetpapi_VERSION ${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}.${Fetpapi_VERSION_PATCH}.${Fetpapi_VERSION_TWEAK}) +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set (CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install CACHE PATH "${PROJECT_NAME} install prefix" FORCE) +endif (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -IF (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set (CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/install CACHE PATH "${PROJECT_NAME} install prefix" FORCE) -ENDIF (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - # required to define CMAKE_INSTALL_BINDIR, CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_INCLUDEDIR include(GNUInstallDirs) @@ -83,8 +86,8 @@ endif (WIN32) # some useful variables # ============================================================================ -set (WITH_FESAPI OFF CACHE BOOL "Build Fetpapi with Fesapi support.") -set (WITH_ETP_SSL ON CACHE BOOL "Build Fesapi with ETP SSL support.") +set (WITH_FESAPI OFF CACHE BOOL "Build FETPAPI with FESAPI support.") +set (WITH_ETP_SSL ON CACHE BOOL "Build FETPAPI with ETP SSL support.") # ============================================================================ # checking for required dependencies @@ -92,33 +95,21 @@ set (WITH_ETP_SSL ON CACHE BOOL "Build Fesapi with ETP SSL support.") find_package (Threads) -# avro DEPENDENCY -set (AVRO_INCLUDE_DIR AVRO_INCLUDE_DIR-NOTFOUND CACHE PATH "Path to the directory which contains the avro header files") -if (NOT IS_DIRECTORY ${AVRO_INCLUDE_DIR}) - message(SEND_ERROR "The Avro include dir (AVRO_INCLUDE_DIR variable) does not look to be a valid directory. Please modify it.") -endif () -set (AVRO_LIBRARY_RELEASE AVRO_LIBRARY_RELEASE-NOTFOUND CACHE FILEPATH "Path to the file which contains the avro library release") -if (NOT EXISTS ${AVRO_LIBRARY_RELEASE}) - message(WARNING "The avro library (AVRO_LIBRARY_RELEASE variable) does not look to be a valid file. Please modify it.") -endif () -set (AVRO_LIBRARY_DEBUG AVRO_LIBRARY_DEBUG-NOTFOUND CACHE FILEPATH "Path to the file which contains the AVRO library DEBUG") -if (NOT EXISTS ${AVRO_LIBRARY_DEBUG}) - message(WARNING "The AVRO library (AVRO_LIBRARY_DEBUG variable) does not look to be a valid file. Please modify it.") +# Boost DEPENDENCY +find_package(Boost 1.70) +if (NOT Boost_FOUND) + # Boost system is required for Beast untill version 1.70 : https://www.boost.org/doc/libs/1_69_0/libs/beast/doc/html/beast/introduction.html + find_package(Boost 1.66 REQUIRED system) +endif() +if (WIN32 AND (Boost_VERSION_MAJOR EQUAL 1) AND (Boost_VERSION_MINOR LESS 74) AND (Boost_VERSION_MINOR GREATER 71)) + message(WARNING "You may experience min/max issue with this boost version : See https://github.com/boostorg/beast/issues/1980") endif () +# avro DEPENDENCY +find_package(AVRO REQUIRED) + if (WITH_FESAPI) - set (FESAPI_INCLUDE_DIR FESAPI_INCLUDE_DIR-NOTFOUND CACHE PATH "Path to the directory which contains the FESAPI header files") - if (NOT IS_DIRECTORY ${FESAPI_INCLUDE_DIR}) - message(SEND_ERROR "The FESAPI include dir (FESAPI_INCLUDE_DIR variable) does not look to be a valid directory. Please modify it.") - endif () - set (FESAPI_LIBRARY_RELEASE FESAPI_LIBRARY_RELEASE-NOTFOUND CACHE FILEPATH "Path to the file which contains the FESAPI library release") - if (NOT EXISTS ${FESAPI_LIBRARY_RELEASE}) - message(WARNING "The FESAPI library (FESAPI_LIBRARY_RELEASE variable) does not look to be a valid file. Please modify it.") - endif () - set (FESAPI_LIBRARY_DEBUG FESAPI_LIBRARY_DEBUG-NOTFOUND CACHE FILEPATH "Path to the file which contains the FESAPI library DEBUG") - if (NOT EXISTS ${FESAPI_LIBRARY_DEBUG}) - message(WARNING "The FESAPI library (FESAPI_LIBRARY_DEBUG variable) does not look to be a valid file. Please modify it.") - endif () + find_package(FESAPI REQUIRED) if (WITH_JAVA_WRAPPING) set (FESAPI_JAR FESAPI_JAR-NOTFOUND CACHE FILEPATH "Path to the jar of FESAPI") if (NOT EXISTS ${FESAPI_JAR}) @@ -127,27 +118,14 @@ if (WITH_FESAPI) endif (WITH_JAVA_WRAPPING) endif (WITH_FESAPI) - -# Boost DEPENDENCY -find_package(Boost 1.66.0 REQUIRED system) -if (WIN32 AND (Boost_VERSION_MAJOR EQUAL 1) AND (Boost_VERSION_MINOR LESS 74) AND (Boost_VERSION_MINOR GREATER 71)) - message(WARNING "You may experience min/max issue with this boost version : See https://github.com/boostorg/beast/issues/1980") -endif () - target_compile_definitions(${PROJECT_NAME} PRIVATE BOOST_ALL_NO_LIB) - -target_link_libraries (${PROJECT_NAME} PRIVATE ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries (${PROJECT_NAME} PRIVATE AVRO::AVRO ${CMAKE_THREAD_LIBS_INIT}) +if (DEFINED Boost_SYSTEM_LIBRARY) + target_link_libraries (${PROJECT_NAME} PRIVATE ${Boost_SYSTEM_LIBRARY}) +endif() if (WIN32) target_link_libraries (${PROJECT_NAME} PRIVATE bcrypt.lib) - if (EXISTS ${AVRO_LIBRARY_RELEASE} AND EXISTS ${AVRO_LIBRARY_DEBUG}) - target_link_libraries (${PROJECT_NAME} PRIVATE optimized ${AVRO_LIBRARY_RELEASE} debug ${AVRO_LIBRARY_DEBUG}) - elseif (EXISTS ${AVRO_LIBRARY_RELEASE}) - target_link_libraries (${PROJECT_NAME} PRIVATE ${AVRO_LIBRARY_RELEASE}) - elseif (EXISTS ${AVRO_LIBRARY_DEBUG}) - target_link_libraries (${PROJECT_NAME} PRIVATE ${AVRO_LIBRARY_DEBUG}) - endif () - set_target_properties(${PROJECT_NAME} PROPERTIES PDB_NAME ${PROJECT_NAME}.${Fetpapi_VERSION} PDB_NAME_DEBUG ${PROJECT_NAME}${CMAKE_DEBUG_POSTFIX}.${Fetpapi_VERSION} @@ -158,8 +136,6 @@ if (WIN32) VERSION ${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR} ) else (WIN32) - target_link_libraries (${PROJECT_NAME} PRIVATE ${AVRO_LIBRARY_RELEASE}) - set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${Fetpapi_VERSION} SOVERSION ${Fetpapi_VERSION_MAJOR}.${Fetpapi_VERSION_MINOR}) @@ -174,17 +150,7 @@ if (WITH_ETP_SSL) endif () if (WITH_FESAPI) - if (WIN32) - if (EXISTS ${FESAPI_LIBRARY_RELEASE} AND EXISTS ${FESAPI_LIBRARY_DEBUG}) - target_link_libraries (${PROJECT_NAME} PRIVATE optimized ${FESAPI_LIBRARY_RELEASE} debug ${FESAPI_LIBRARY_DEBUG}) - elseif (EXISTS ${FESAPI_LIBRARY_RELEASE}) - target_link_libraries (${PROJECT_NAME} PRIVATE ${FESAPI_LIBRARY_RELEASE}) - elseif (EXISTS ${FESAPI_LIBRARY_DEBUG}) - target_link_libraries (${PROJECT_NAME} PRIVATE ${FESAPI_LIBRARY_DEBUG}) - endif () - else (WIN32) - target_link_libraries (${PROJECT_NAME} PRIVATE ${FESAPI_LIBRARY_RELEASE}) - endif (WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE FESAPI::FESAPI) endif (WITH_FESAPI) # Namespaces definitions @@ -292,6 +258,16 @@ if (WITH_FESAPI) endif (WITH_TEST) endif (WITH_FESAPI) +set (WITH_EXAMPLE OFF CACHE BOOL "Also builds and installs an ETP1.2 client example.") +if (WITH_EXAMPLE) + if (WITH_FESAPI) + add_subdirectory(example/withFesapi) + else () + add_subdirectory(example/withoutFesapi) + endif (WITH_FESAPI) +endif (WITH_EXAMPLE) + + # ============================================================================ # Install Fetpapi library # ============================================================================ diff --git a/README.md b/README.md index 851c30e..ba15892 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - dependencies - The following compilers are known to work (used in CI) - gcc from version 4.8 - - visual studio from version 2017 + - visual studio from version 2019 # Prepare the dependencies Download (build and install if necessary) third party libraries: - BOOST : All versions from version 1.66 should be ok but you may experience some [min/max build issues](https://github.com/boostorg/beast/issues/1980) using version 1.72 or 1.73. @@ -27,10 +27,8 @@ FETPAPI uses cmake as its build tool. A 3.12 version or later of cmake is requir - give real path and files to the following cmake variables: - BOOST - Boost_INCLUDE_DIR : the directory where you can find the directory named "boost" which contain all BOOST headers - - AVRO - - AVRO_INCLUDE_DIR : where the "avro" directory containing all AVRO headers is located - - (ONLY FOR WINDOWS) AVRO_LIBRARY_DEBUG : Optional, only used by Visual studio Debug configuration, the Avro debug library you want to link to. - - AVRO_LIBRARY_RELEASE : the AVRO system library you want to link to. + - AVRO (using [our own cmake find module](./cmake/modules/FindAVRO.cmake)) + - (ONLY IF NOT AUTOMATICALLY FOUND) AVRO_ROOT : The path to the folder containing include and lib folders of AVRO - Click again on "Configure" button. You should no more have errors so you can now click on "Generate" button. - You can now build your solution with your favorite compiler (and linker) using the generated solution in yourPath/fesapiEnv/build/theNameYouWant - OPTIONALLY, you can also set the variables WITH_DOTNET_WRAPPING, WITH_PYTHON_WRAPPING to true if you want to also generate wrappers on top of FETPAPI for these two other programming languages. Don't forget to click again on "Configure" button once you changed the value of these two variables. @@ -40,10 +38,8 @@ FETPAPI uses cmake as its build tool. A 3.12 version or later of cmake is requir - OPENSSL_INCLUDE_DIR : the OpenSSL include directory - LIB_EAY_RELEASE : the OpenSSL crypto library you want to link with. - SSL_EAY_RELEASE : the OpenSSL ssl library you want to link with. -- OPTIONALLY, for FESAPI (v2.7.0.0 as a minimal version) support (see [here](https://github.com/F2I-Consulting/fesapi) for documentation on how to build fesapi), please enable the WITH_FESAPI variable and set the following variables : - - FESAPI_INCLUDE_DIR : the directory where the FESAPI headers are located (generally the include subdirectory of the fesapi installation directory). - - (ONLY FOR WINDOWS) FESAPI_LIBRARY_DEBUG : Optional, only used by Visual studio Debug configuration, the FESAPI debug library you want to link to. - - FESAPI_LIBRARY_RELEASE : the FESAPI library you want to link to. +- OPTIONALLY, for FESAPI (v2.7.0.0 as a minimal version) support (see [here](https://github.com/F2I-Consulting/fesapi) for documentation on how to build fesapi), please enable the WITH_FESAPI variable and usually set the following variable : + - FESAPI_ROOT : The path to the folder containing include and lib folders of FESAPI (using [our own cmake find module](./cmake/modules/FindFESAPI.cmake)) Remark : you can choose where FETPAPI will be installed (using "make install" on Linux or by generating the "INSTALL" project on Visual Studio) by setting the cmake variable called CMAKE_INSTALL_PREFIX # How to start diff --git a/cmake/modules/FindAVRO.cmake b/cmake/modules/FindAVRO.cmake new file mode 100644 index 0000000..fafa406 --- /dev/null +++ b/cmake/modules/FindAVRO.cmake @@ -0,0 +1,108 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +AVRO +-------- + +Find the native AVRO includes and library. +Heavily inspired by the official FindZLIB cmake module v3.27. + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines :prop_tgt:`IMPORTED` target ``AVRO::AVRO``, if +AVRO has been found. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module defines the following variables: + +:: + + AVRO_INCLUDE_DIRS - where to find zip.h, unzip.h, etc. + AVRO_LIBRARIES - List of libraries when using avro. + AVRO_FOUND - True if avro found. + +Hints +^^^^^ + +A user may set ``AVRO_ROOT`` to a avro installation root to tell this +module where to look. +#]=======================================================================] +include(SelectLibraryConfigurations) + +set(_AVRO_SEARCHES) + +# Search AVRO_ROOT first if it is set. +if(AVRO_ROOT) + set(_AVRO_SEARCH_ROOT PATHS ${AVRO_ROOT} NO_DEFAULT_PATH) + list(APPEND _AVRO_SEARCHES _AVRO_SEARCH_ROOT) +endif() + +# Normal search. +set(_AVRO_x86 "(x86)") +set(_AVRO_SEARCH_NORMAL + PATHS "$ENV{ProgramFiles}/avro" + "$ENV{ProgramFiles${_AVRO_x86}}/avro") +unset(_AVRO_x86) +list(APPEND _AVRO_SEARCHES _AVRO_SEARCH_NORMAL) + +set(AVRO_NAMES avrocpp avrocpp_s) +set(AVRO_NAMES_DEBUG avrod avrocpp_d avrocpp_s_d) + +# Try each search configuration. +foreach(search ${_AVRO_SEARCHES}) + find_path(AVRO_INCLUDE_DIR NAMES avro/AvroParse.hh ${${search}} PATH_SUFFIXES include) +endforeach() + +# Allow AVRO_LIBRARY to be set manually, as the location of the zlib library +if(NOT AVRO_LIBRARY) + foreach(search ${_AVRO_SEARCHES}) + find_library(AVRO_LIBRARY_RELEASE NAMES ${AVRO_NAMES} NAMES_PER_DIR ${${search}} PATH_SUFFIXES lib) + find_library(AVRO_LIBRARY_DEBUG NAMES ${AVRO_NAMES_DEBUG} NAMES_PER_DIR ${${search}} PATH_SUFFIXES lib) + endforeach() + + select_library_configurations(AVRO) +endif() + +unset(AVRO_NAMES) +unset(AVRO_NAMES_DEBUG) + +mark_as_advanced(AVRO_INCLUDE_DIR) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVRO REQUIRED_VARS AVRO_LIBRARY AVRO_INCLUDE_DIR) + +if(AVRO_FOUND) + set(AVRO_INCLUDE_DIRS ${AVRO_INCLUDE_DIR}) + + if(NOT AVRO_LIBRARIES) + set(AVRO_LIBRARIES ${AVRO_LIBRARY}) + endif() + + if(NOT TARGET AVRO::AVRO) + add_library(AVRO::AVRO UNKNOWN IMPORTED) + set_target_properties(AVRO::AVRO PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${AVRO_INCLUDE_DIRS}") + + if(AVRO_LIBRARY_RELEASE) + set_property(TARGET AVRO::AVRO APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(AVRO::AVRO PROPERTIES + IMPORTED_LOCATION_RELEASE "${AVRO_LIBRARY_RELEASE}") + endif() + + if(AVRO_LIBRARY_DEBUG) + set_property(TARGET AVRO::AVRO APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(AVRO::AVRO PROPERTIES + IMPORTED_LOCATION_DEBUG "${AVRO_LIBRARY_DEBUG}") + endif() + + if(NOT AVRO_LIBRARY_RELEASE AND NOT AVRO_LIBRARY_DEBUG) + set_property(TARGET AVRO::AVRO APPEND PROPERTY + IMPORTED_LOCATION "${AVRO_LIBRARY}") + endif() + endif() +endif() diff --git a/cmake/modules/FindFESAPI.cmake b/cmake/modules/FindFESAPI.cmake new file mode 100644 index 0000000..64c0276 --- /dev/null +++ b/cmake/modules/FindFESAPI.cmake @@ -0,0 +1,118 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying +# file Copyright.txt or https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +FESAPI +-------- + +Find the native FESAPI includes and library. +Heavily inspired by the official FindZLIB cmake module v3.27. + +IMPORTED Targets +^^^^^^^^^^^^^^^^ + +This module defines :prop_tgt:`IMPORTED` target ``FESAPI::FESAPI``, if +FESAPI has been found. + +Result Variables +^^^^^^^^^^^^^^^^ + +This module defines the following variables: + +:: + + FESAPI_INCLUDE_DIRS - where to find zip.h, unzip.h, etc. + FESAPI_LIBRARIES - List of libraries when using fesapi. + FESAPI_FOUND - True if fesapi found. + +Hints +^^^^^ + +A user may set ``FESAPI_ROOT`` to a fesapi installation root to tell this +module where to look. +#]=======================================================================] +include(SelectLibraryConfigurations) + +set(_FESAPI_SEARCHES) + +# Search FESAPI_ROOT first if it is set. +if(FESAPI_ROOT) + set(_FESAPI_SEARCH_ROOT PATHS ${FESAPI_ROOT} NO_DEFAULT_PATH) + list(APPEND _FESAPI_SEARCHES _FESAPI_SEARCH_ROOT) +endif() + +# Normal search. +set(_FESAPI_x86 "(x86)") +set(_FESAPI_SEARCH_NORMAL + PATHS "$ENV{ProgramFiles}/fesapi" + "$ENV{ProgramFiles${_FESAPI_x86}}/fesapi") +unset(_FESAPI_x86) +list(APPEND _FESAPI_SEARCHES _FESAPI_SEARCH_NORMAL) + +# On Windows, library names contain the version +# The maximum of ranges are defined totally arbitrarily +set(FESAPI_NAMES FesapiCpp) +set(FESAPI_NAMES_DEBUG FesapiCppd) +foreach(minorVer RANGE 7 15) + foreach(patchVer RANGE 0 5) + foreach(tweakVer RANGE 0 5) + list(APPEND FESAPI_NAMES FesapiCpp.2.${minorVer}.${patchVer}.${tweakVer}) + list(APPEND FESAPI_NAMES_DEBUG FesapiCppd.2.${minorVer}.${patchVer}.${tweakVer}) + endforeach() + endforeach() +endforeach() + +# Try each search configuration. +foreach(search ${_FESAPI_SEARCHES}) + find_path(FESAPI_INCLUDE_DIR NAMES fesapi/common/DataObjectRepository.h ${${search}} PATH_SUFFIXES include) +endforeach() + +# Allow FESAPI_LIBRARY to be set manually, as the location of the zlib library +if(NOT FESAPI_LIBRARY) + foreach(search ${_FESAPI_SEARCHES}) + find_library(FESAPI_LIBRARY_RELEASE NAMES ${FESAPI_NAMES} NAMES_PER_DIR ${${search}} PATH_SUFFIXES lib) + find_library(FESAPI_LIBRARY_DEBUG NAMES ${FESAPI_NAMES_DEBUG} NAMES_PER_DIR ${${search}} PATH_SUFFIXES lib) + endforeach() + + select_library_configurations(FESAPI) +endif() + +unset(FESAPI_NAMES) +unset(FESAPI_NAMES_DEBUG) + +mark_as_advanced(FESAPI_INCLUDE_DIR) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FESAPI REQUIRED_VARS FESAPI_LIBRARY FESAPI_INCLUDE_DIR) + +if(FESAPI_FOUND) + set(FESAPI_INCLUDE_DIRS ${FESAPI_INCLUDE_DIR}) + + if(NOT FESAPI_LIBRARIES) + set(FESAPI_LIBRARIES ${FESAPI_LIBRARY}) + endif() + + if(NOT TARGET FESAPI::FESAPI) + add_library(FESAPI::FESAPI UNKNOWN IMPORTED) + set_target_properties(FESAPI::FESAPI PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${FESAPI_INCLUDE_DIRS}") + + if(FESAPI_LIBRARY_RELEASE) + set_property(TARGET FESAPI::FESAPI APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(FESAPI::FESAPI PROPERTIES + IMPORTED_LOCATION_RELEASE "${FESAPI_LIBRARY_RELEASE}") + endif() + + if(FESAPI_LIBRARY_DEBUG) + set_property(TARGET FESAPI::FESAPI APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(FESAPI::FESAPI PROPERTIES + IMPORTED_LOCATION_DEBUG "${FESAPI_LIBRARY_DEBUG}") + endif() + + if(NOT FESAPI_LIBRARY_RELEASE AND NOT FESAPI_LIBRARY_DEBUG) + set_property(TARGET FESAPI::FESAPI APPEND PROPERTY + IMPORTED_LOCATION "${FESAPI_LIBRARY}") + endif() + endif() +endif() diff --git a/cmake/swigEtp1_2Include.i.in b/cmake/swigEtp1_2Include.i.in index a996a07..1c2b9d6 100644 --- a/cmake/swigEtp1_2Include.i.in +++ b/cmake/swigEtp1_2Include.i.in @@ -161,15 +161,12 @@ typedef long long time_t; %nspace ETP_NS::TransactionHandlers; %nspace ETP_NS::DataspaceHandlers; %nspace ETP_NS::AbstractSession; - %nspace ETP_NS::PlainClientSession; + %nspace ETP_NS::ClientSession; %nspace ETP_NS::InitializationParameters; #ifdef WITH_FESAPI %nspace ETP_NS::FesapiHdfProxyFactory; #endif -#ifdef WITH_ETP_SSL - %nspace ETP_NS::SslClientSession; -#endif - + %nspace Energistics::Etp::v12::Datatypes::SupportedDataObject; %nspace Energistics::Etp::v12::Datatypes::Uuid; %nspace Energistics::Etp::v12::Datatypes::Version; @@ -1057,10 +1054,7 @@ namespace Energistics { %shared_ptr(ETP_NS::TransactionHandlers) %shared_ptr(ETP_NS::DataspaceHandlers) %shared_ptr(ETP_NS::AbstractSession) -%shared_ptr(ETP_NS::PlainClientSession) -#ifdef WITH_ETP_SSL -%shared_ptr(ETP_NS::SslClientSession) -#endif +%shared_ptr(ETP_NS::ClientSession) %feature("director") ETP_NS::CoreHandlers; %feature("director") ETP_NS::DiscoveryHandlers; @@ -1874,8 +1868,8 @@ namespace ETP_NS /******************* CLIENT ***************************/ - %nodefaultctor PlainClientSession; - class PlainClientSession : public AbstractSession + %nodefaultctor ClientSession; + class ClientSession : public AbstractSession { public: /** @@ -1885,27 +1879,43 @@ namespace ETP_NS bool run(); }; -#ifdef WITH_ETP_SSL - %nodefaultctor SslClientSession; - class SslClientSession : public AbstractSession + class InitializationParameters { public: /** - * Run the websocket and then the ETP session. - * Everything related to this session (including the completion handlers) will operate on the same unique thread in a single event loop. + * @param instanceUuid The UUID of the client instance. + * @param etpServerUrl Must follow the syntax ws://:/ or wss://:/ or simply :/ + * where port is optional and is defaulted to 80 if scheme is "ws" or if no scheme is provided. + * In "wss" schema cases, port is defaulted to 443. + * @param proxyUrl The proxy URL. It must follow the syntax http://: or simply :. + * Leave it empty if your connection to eptServerUrl is direct and does not pass throughr any proxy. */ - bool run(); - }; -#endif - - class InitializationParameters - { - public: - InitializationParameters(const std::string& instanceUuid, const std::string & etpUrl); + InitializationParameters(const std::string& instanceUuid, + const std::string& etpServerUrl, const std::string& proxyUrl = ""); InitializationParameters(const std::string& instanceUuid, const std::string & host, unsigned short port, const std::string & urlPath = ""); virtual ~InitializationParameters(); void setMaxWebSocketMessagePayloadSize(int64_t value); + uint64_t getMaxWebSocketMessagePayloadSize() const; + + void setPreferredMaxFrameSize(uint64_t value); + uint64_t getPreferredMaxFrameSize() const; + + void setAdditionalHandshakeHeaderFields(const std::map& extraHandshakeHeaderFields); + const std::map& getAdditionalHandshakeHeaderFields() const; + + void setAdditionalCertificates(const std::string& extraCertificates); + const std::string& getAdditionalCertificates() const; + + const std::string& getEtpServerHost() const; + uint16_t getEtpServerPort() const; + const std::string& getEtpServerUrlPath() const; + + const std::string& getProxyHost() const; + uint16_t getProxyPort() const; + + void setForceTls(bool force); + bool isTlsForced(); virtual std::string getApplicationName() const; virtual std::string getApplicationVersion() const; @@ -1921,13 +1931,8 @@ namespace ETP_NS namespace ClientSessionLaunchers { - std::shared_ptr createWsClientSession(InitializationParameters* initializationParams, const std::string & authorization, - const std::map& additionalHandshakeHeaderFields = {}, std::size_t preferredMaxFrameSize = 4096); - -#ifdef WITH_ETP_SSL - std::shared_ptr createWssClientSession(InitializationParameters* initializationParams, const std::string & authorization, - const std::map& additionalHandshakeHeaderFields = {}, std::size_t preferredMaxFrameSize = 4096, const std::string & additionalCertificates = ""); -#endif + std::shared_ptr createClientSession(InitializationParameters* initializationParams, + const std::string & etpServerAuthorization, const std::string& proxyAuthorization = ""); } #ifdef WITH_FESAPI diff --git a/example/withFesapi/CMakeLists.txt b/example/withFesapi/CMakeLists.txt new file mode 100644 index 0000000..c03f51b --- /dev/null +++ b/example/withFesapi/CMakeLists.txt @@ -0,0 +1,64 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 3.12) + +project (etpClient) + +# ============================================================================ +# build etpClient +# ============================================================================ + +# use, i.e. don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH already +# (but later on when installing) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +set(CMAKE_INSTALL_RPATH "\$ORIGIN/${CMAKE_INSTALL_LIBDIR}") + +# add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +add_executable (${PROJECT_NAME}) +target_sources(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/etpClient.cpp) + +target_include_directories (${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/) + +add_dependencies (${PROJECT_NAME} Fetpapi) +target_link_libraries (${PROJECT_NAME} PRIVATE Fetpapi) + +target_compile_definitions(${PROJECT_NAME} PRIVATE BOOST_ALL_NO_LIB) +target_link_libraries (${PROJECT_NAME} PRIVATE AVRO::AVRO ${CMAKE_THREAD_LIBS_INIT}) +if (DEFINED Boost_SYSTEM_LIBRARY) + target_link_libraries (${PROJECT_NAME} PRIVATE ${Boost_SYSTEM_LIBRARY}) +endif() +if (WIN32) + target_link_libraries (${PROJECT_NAME} PRIVATE bcrypt.lib) +endif() +if (WITH_ETP_SSL) + target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_ETP_SSL) + target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto) +endif () +target_link_libraries(${PROJECT_NAME} PRIVATE FESAPI::FESAPI) + +target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${AVRO_INCLUDE_DIR} ${Boost_INCLUDE_DIR}) +if (WITH_ETP_SSL) + target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) +endif () +target_include_directories(${PROJECT_NAME} PRIVATE ${FESAPI_INCLUDE_DIR}) + +if (WIN32) + set_target_properties (${PROJECT_NAME} PROPERTIES + LINK_FLAGS "/INCREMENTAL:NO" + RUNTIME_OUTPUT_DIRECTORY ${FETPAPI_BINARY_DIR}) +endif (WIN32) + +# The value of DEBUG_POSTFIX property is initialized when the target is created to the value of the variable CMAKE__POSTFIX +# except for executable targets because earlier CMake versions which did not use this variable for executables. +set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + +install ( + TARGETS ${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX} +) diff --git a/example/withFesapi/etpClient.cpp b/example/withFesapi/etpClient.cpp new file mode 100644 index 0000000..8eca4e5 --- /dev/null +++ b/example/withFesapi/etpClient.cpp @@ -0,0 +1,512 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#include +#include + +#include +#include +#include +#include + +#include "etp/ClientSessionLaunchers.h" +#include "etp/fesapi/FesapiHdfProxy.h" +#include "etp/fesapi/FesapiHelpers.h" + +void printHelp() +{ + std::cout << "List of available commands :" << std::endl; + std::cout << "\tBlockingImport" << std::endl << "\t\tList all dataobjects from the project/study named dataspace (or the first dataspace) and get the first one in a blocking way" << std::endl << std::endl; + std::cout << "\tBlockingExport" << std::endl << "\t\tPut a dummy horizon feature into a dummy dataspace which is also created" << std::endl << std::endl; + std::cout << "\tPing" << std::endl << "\t\tPing the server" << std::endl << std::endl; + std::cout << "\tList" << std::endl << "\t\tList the objects which have been got from ETP to the in-memory Dataobject repository" << std::endl << std::endl; + std::cout << "\tPutXmlAndHdfAtOnce" << std::endl << "\t\tPut a dummy point set representation to the store sending XML and HDF5 points at once." << std::endl << std::endl; + std::cout << "\tGetDataspaces" << std::endl << "\t\tGet all store dataspaces" << std::endl << std::endl; + std::cout << "\tGetResources URI scope(default self) depth(default 1) countObjects(true or false, default is true) includeSecondaryTargets(true or false, default is false) includeSecondarySources(true or false, default is false) dataTypeFilter,dataTypeFilter,...(default noFilter)" << std::endl << std::endl; + std::cout << "\tGetDataObjects dataObjectURI,dataObjectURI,..." << std::endl << "\t\tGet the objects from an ETP store and store them into the in memory Dataobject repository (only create partial TARGET relationships, not any SOURCE relationships)" << std::endl << std::endl; + std::cout << "\tGetXYZPoints URI" << std::endl << "\t\tGet the XYZ points of a rep from store and print some of them." << std::endl << std::endl; + std::cout << "\tPutDataObject UUID" << std::endl << "\t\tPut the XML part of a dataobject which is on the client side (use \"Load\" command to laod some dataobjects on client side) to the store" << std::endl << std::endl; + std::cout << "\tGetDataArrayMetadata epcExternalPartURI datasetPathInEpcExternalPart" << std::endl << "\t\tGet the metadata of a dataset included in an EpcExternalPart over ETP." << std::endl << std::endl; + std::cout << "\tGetDataArray epcExternalPartURI datasetPathInEpcExternalPart" << std::endl << "\t\tGet the numerical values from a dataset included in an EpcExternalPart over ETP." << std::endl << std::endl; + std::cout << "\tPutDataArray epcExternalPartURI datasetPathInEpcExternalPart" << std::endl << "\t\tPut a dummy {0,1,2,3,4,5,6,7,8,9} integer array in a particular store epcExternalPartURI at a particular dataset path" << std::endl << std::endl; + std::cout << "\tSubscribeNotif URI scope(default self) depth(default 1) receiveXML(true or false, default is true) dataTypeFilter,dataTypeFilter,...(default noFilter)" << std::endl << "\t\tSubscribe to notifications." << std::endl << std::endl; + std::cout << "\tLoad epcDocument" << std::endl << "\t\tLoad an EPC document into the Dataobject repository." << std::endl << std::endl; + std::cout << "\tSetVerbosity 0or1" << std::endl << "\t\tSet the sesion verbosity to true or false" << std::endl << std::endl; + std::cout << "\tDeleteDataObject URI" << std::endl << "\t\tDelete a dataobject" << std::endl << std::endl; + std::cout << "\tDeleteDataspace URI" << std::endl << "\t\tDelete a dataspace" << std::endl << std::endl; + std::cout << "\tGetDeletedResources dataspaceURI" << std::endl << "\t\tGet all deleted resources" << std::endl << std::endl; + std::cout << "\tquit" << std::endl << "\t\tQuit the session." << std::endl << std::endl; +} + +void setFetpapiHandlers(std::shared_ptr session) { + session->setDiscoveryProtocolHandlers(std::make_shared(session.get())); + session->setStoreProtocolHandlers(std::make_shared(session.get())); + session->setDataArrayProtocolHandlers(std::make_shared(session.get())); + session->setStoreNotificationProtocolHandlers(std::make_shared(session.get())); + session->setDataspaceProtocolHandlers(std::make_shared(session.get())); + session->setTransactionProtocolHandlers(std::make_shared(session.get())); +} + +void askUser(std::shared_ptr session, COMMON_NS::DataObjectRepository& repo) +{ + std::string buffer; + + std::cout << "What is your command (\"quit\" for closing connection)?" << std::endl; + std::string command; + while (command != "quit") + { + if (session->isEtpSessionClosed()) { + command = "quit"; + } + else { + std::getline(std::cin, command); + } + auto commandTokens = tokenize(command, ' '); + + if (commandTokens.empty()) { + printHelp(); + continue; + } + if (commandTokens[0] == "quit") { + continue; + } + + if (commandTokens[0] == "Load") { + COMMON_NS::EpcDocument epcDoc(commandTokens[1]); + epcDoc.deserializeInto(repo); + std::cout << "LOADED!" << std::endl; + continue; + } + else if (commandTokens[0] == "SetVerbosity") { + if (commandTokens[1].size() == 1 && commandTokens[1][0] == '1') { + std::cout << "Set the verbosity ON" << std::endl; + session->setVerbose(true); + } + else { + std::cout << "Set the verbosity OFF" << std::endl; + } + session->setVerbose(commandTokens[1].size() == 1 && commandTokens[1][0] == '1'); + } + else if (commandTokens[0] == "GetDeletedResources") { + const auto resources = session->getDeletedResources(commandTokens[1]); + for (auto& resource : resources) { + std::cout << resource.uri << " deleted on " << resource.deletedTime << std::endl; + } + continue; + } + else if (commandTokens[0] == "DeleteDataObject" && commandTokens.size() > 1) { + std::map< std::string, std::string > query; + query["0"] = commandTokens[1]; + const auto deleted = session->deleteDataObjects(query); + if (deleted.empty()) { + std::cout << "NOT deleted" << std::endl; + } + else { + std::cout << "DELETED" << std::endl; + } + } + else if (commandTokens[0] == "DeleteDataspace" && commandTokens.size() > 1) { + std::map< std::string, std::string > query; + query["0"] = commandTokens[1]; + const auto deleted = session->deleteDataspaces(query); + if (deleted.empty()) { + std::cout << "NOT deleted" << std::endl; + } + else { + std::cout << "DELETED" << std::endl; + } + } + else if (commandTokens[0] == "GetResources") { + Energistics::Etp::v12::Protocol::Discovery::GetResources mb; + mb.context.uri = commandTokens[1]; + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + mb.context.depth = 1; + mb.context.navigableEdges = Energistics::Etp::v12::Datatypes::Object::RelationshipKind::Primary; + mb.countObjects = true; + + if (commandTokens.size() > 2) { + if (commandTokens[2] == "self") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + else if (commandTokens[2] == "sources") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sources; + else if (commandTokens[2] == "sourcesOrSelf") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sourcesOrSelf; + else if (commandTokens[2] == "targets") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; + else if (commandTokens[2] == "targetsOrSelf") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targetsOrSelf; + + if (commandTokens.size() > 3) { + mb.context.depth = std::stoi(commandTokens[3]); + + if (commandTokens.size() > 4) { + if (commandTokens[4] == "false" || commandTokens[4] == "False" || commandTokens[4] == "FALSE") { + mb.countObjects = false; + } + + if (commandTokens.size() > 5) { + if (commandTokens[5] == "true" || commandTokens[5] == "True" || commandTokens[5] == "TRUE") { + mb.context.includeSecondaryTargets = true; + } + + if (commandTokens.size() > 6) { + + if (commandTokens[6] == "true" || commandTokens[6] == "True" || commandTokens[6] == "TRUE") { + mb.context.includeSecondarySources = true; + } + + if (commandTokens.size() > 7) { + mb.context.dataObjectTypes = tokenize(commandTokens[7], ','); + } + } + } + } + } + } + + const auto resources = session->getResources(mb.context, mb.scope); + for (auto& resource : resources) { + std::cout << resource.uri << std::endl; + if (resource.has_sourceCount()) std::cout << "Source count: " << resource.sourceCount.get() << std::endl; + if (resource.has_targetCount()) std::cout << "Target count: " << resource.targetCount.get() << std::endl; + } + continue; + } + else if (commandTokens[0] == "GetDataObjects") { + if (commandTokens.size() == 1) { + std::cerr << "Please provide some ETP URIs" << std::endl; + continue; + } + std::vector tokens = tokenize(commandTokens[1], ','); + std::map< std::string, std::string > query; + for (size_t i = 0; i < tokens.size(); ++i) { + query[std::to_string(i)] = tokens[i]; + } + const auto dataobjects = session->getDataObjects(query); + for (const auto& graphResource : dataobjects) { + std::cout << "*************************************************" << std::endl; + std::cout << "Resource received : " << std::endl; + std::cout << "uri : " << graphResource.second.resource.uri << std::endl; + std::cout << "name : " << graphResource.second.resource.name << std::endl; + std::cout << "xml : " << graphResource.second.data << std::endl; + + repo.addOrReplaceGsoapProxy(graphResource.second.data, ETP_NS::EtpHelpers::getDataObjectType(graphResource.second.resource.uri), ETP_NS::EtpHelpers::getDataspaceUri(graphResource.second.resource.uri)); + } + } + else if (commandTokens[0] == "GetXYZPoints") { + if (commandTokens.size() == 1) { + std::cerr << "Please provide some ETP URIs of a RESQML representation" << std::endl; + continue; + } + /* This works in a blocking way i.e. getXyzPointCountOfPatch will return only when the store would have answered back. + HDF proxy factory and custom HDF proxy are used for that. See main.cpp for setting the custom HDF proxy factory. + You should also look at MyOwnStoreProtocolHandlers::on_GetDataObjectsResponse which allows to set the session information to the HDF proxy. + We could have hard set those information thanks to HDF proxy factory. + + If you would want non blocking approach, please see GetDataArrays which require more work to fill in the arguments. + */ + std::string uuid = commandTokens[1].substr(commandTokens[1].rfind("(") + 1, 36); + auto* rep = repo.getDataObjectByUuid(uuid); + if (rep == nullptr) { + std::cerr << " The UUID " << uuid << " from URI " << commandTokens[1] << " does not correspond to a representation which is on client side. Please get first this dataobject from the store before to call GetXYZPoints on it." << std::endl; + continue; + } + auto xyzPointCount = rep->getXyzPointCountOfPatch(0); + std::unique_ptr xyzPoints(new double[xyzPointCount * 3]); + rep->getXyzPointsOfPatch(0, xyzPoints.get()); + for (auto xyzPointIndex = 0; xyzPointIndex < xyzPointCount && xyzPointIndex < 20; ++xyzPointIndex) { + std::cout << "XYZ Point Index " << xyzPointIndex << " : " << xyzPoints[xyzPointIndex * 3] << "," << xyzPoints[xyzPointIndex * 3 + 1] << "," << xyzPoints[xyzPointIndex * 3 + 2] << std::endl; + } + } + else if (commandTokens[0] == "PutDataObject") { + auto* dataObj = repo.getDataObjectByUuid(commandTokens[1]); + if (dataObj != nullptr) { + Energistics::Etp::v12::Protocol::Store::PutDataObjects putDataObjects; + Energistics::Etp::v12::Datatypes::Object::DataObject dataObject = ETP_NS::FesapiHelpers::buildEtpDataObjectFromEnergisticsObject(dataObj); + putDataObjects.dataObjects["0"] = dataObject; + + session->send(putDataObjects, 0, 0x10 | 0x02); // 0x10 requires Acknowledge from the store + } + } + else if (commandTokens[0] == "SubscribeNotif") { + Energistics::Etp::v12::Protocol::StoreNotification::SubscribeNotifications mb; + Energistics::Etp::v12::Datatypes::Object::SubscriptionInfo subscriptionInfo; + subscriptionInfo.context.uri = commandTokens[1]; + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + subscriptionInfo.context.depth = 1; + boost::uuids::random_generator gen; + boost::uuids::uuid uuid = gen(); + std::move(std::begin(uuid.data), std::end(uuid.data), subscriptionInfo.requestUuid.array.begin()); + + if (commandTokens.size() > 2) { + if (commandTokens[2] == "self") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + else if (commandTokens[2] == "sources") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sources; + else if (commandTokens[2] == "sourcesOrSelf") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sourcesOrSelf; + else if (commandTokens[2] == "targets") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; + else if (commandTokens[2] == "targetsOrSelf") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targetsOrSelf; + + if (commandTokens.size() > 3) { + subscriptionInfo.context.depth = std::stoi(commandTokens[3]); + + if (commandTokens.size() > 4) { + subscriptionInfo.includeObjectData = commandTokens[4] == "true"; + + if (commandTokens.size() > 5) { + subscriptionInfo.context.dataObjectTypes = tokenize(commandTokens[5], ','); + } + } + } + } + + mb.request["0"] = subscriptionInfo; + + session->send(mb, 0, 0x02); + + continue; + } + + if (commandTokens.size() == 1) { + if (commandTokens[0] == "BlockingImport") { + std::cout << "************ LIST DATASPACES ************" << std::endl; + const auto dataspaces = session->getDataspaces(); + for (auto& dataspace : dataspaces) { + std::cout << dataspace.uri << std::endl; + } + std::cout << "************ LIST RESOURCES ************" << std::endl; + const auto dsIter = std::find_if(dataspaces.begin(), dataspaces.end(), + [](const Energistics::Etp::v12::Datatypes::Object::Dataspace& ds) { return ds.uri == "eml:///dataspace('project/study')"; }); + Energistics::Etp::v12::Datatypes::Object::ContextInfo ctxInfo; + ctxInfo.uri = dsIter == dataspaces.end() + ? dataspaces[0].uri + : dsIter->uri; + ctxInfo.depth = 0; + ctxInfo.navigableEdges = Energistics::Etp::v12::Datatypes::Object::RelationshipKind::Both; + ctxInfo.includeSecondaryTargets = false; + ctxInfo.includeSecondarySources = false; + const auto resources = session->getResources(ctxInfo, Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets); + for (auto& resource : resources) { + std::cout << resource.uri << std::endl; + } + std::cout << "************ GET FIRST DATAOBJECT ************" << std::endl; + if (!resources.empty()) { + std::map< std::string, std::string > query = { { "0", resources[0].uri } }; + const auto dataobject = session->getDataObjects(query); + if (dataobject.size() == 1) { + std::cout << dataobject.at("0").data << std::endl; + } + } + else { + std::cout << "There is no dataobject in this dataspace" << std::endl; + } + } + else if (commandTokens[0] == "BlockingExport") { + const auto existingDataspaces = session->getDataspaces(); + if (std::find_if(existingDataspaces.begin(), existingDataspaces.end(), + [](const Energistics::Etp::v12::Datatypes::Object::Dataspace& ds) { return ds.uri == "eml:///dataspace('project/study')"; }) == existingDataspaces.end()) { + std::cout << "************ PUT DUMMY DATASPACE ************" << std::endl; + Energistics::Etp::v12::Datatypes::Object::Dataspace dataspace; + dataspace.uri = "eml:///dataspace('project/study')"; + dataspace.path = "project/study"; + dataspace.storeCreated = 0; + dataspace.storeLastWrite = 0; + std::map dataspaces = { {"0", dataspace} }; + auto dsAnswer = session->putDataspaces(dataspaces); + if (std::find(dsAnswer.begin(), dsAnswer.end(), "0") == dsAnswer.end()) { + std::cerr << "Error when putting the dataspace into the store" << std::endl; + continue; + } + } + std::cout << "************ PUT DUMMY DATAOBJECT ************" << std::endl; + COMMON_NS::AbstractObject* horizon = repo.getDataObjectByUuid("6f8c8aa5-7472-4433-a309-74113a419948"); + if (horizon == nullptr) { + horizon = repo.createHorizon("6f8c8aa5-7472-4433-a309-74113a419948", "dummy horizon"); + } + horizon->setUriSource("eml:///dataspace('project/study')"); + std::map dataObjects = { {"0", ETP_NS::FesapiHelpers::buildEtpDataObjectFromEnergisticsObject(horizon)} }; + const auto doAnswer = session->putDataObjects(dataObjects); + if (std::find(doAnswer.begin(), doAnswer.end(), "0") == doAnswer.end()) { + std::cerr << "Error when putting the dataobject into the store" << std::endl; + } + + // comment/uncomment below two lines as you want + //std::cout << "************ DELETE DATASPACE ************" << std::endl; + //session->deleteDataspaces({ {"0", "eml:///dataspace('project/study')"} }); + } + else if (commandTokens[0] == "Ping") { + Energistics::Etp::v12::Protocol::Core::Ping ping; + ping.currentDateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + session->send(ping, 0, 0x02); + std::cout << "PING at " << ping.currentDateTime << std::endl; + std::cout << "Please Set Verbosity to 1 if you don't see anything" << std::endl; + } + else if (commandTokens[0] == "GetDataspaces") { + const auto dataspaces = session->getDataspaces(); + for (auto& dataspace : dataspaces) { + std::cout << dataspace.uri << std::endl; + } + } + else if (commandTokens[0] == "List") { + std::cout << "*** START LISTING ***" << std::endl; + for (const auto& entryPair : repo.getDataObjects()) { + for (const auto* obj : entryPair.second) { + if (!obj->isPartial()) { + std::cout << "******************" << entryPair.first << " : " << obj->getTitle() << "******************" << std::endl; + std::cout << "*** SOURCE REL ***" << std::endl; + for (auto srcObj : obj->getRepository()->getSourceObjects(obj)) { + std::cout << srcObj->getUuid() << " : " << srcObj->getXmlTag() << std::endl; + } + std::cout << "*** TARGET REL ***" << std::endl; + for (auto targetObj : obj->getRepository()->getTargetObjects(obj)) { + std::cout << targetObj->getUuid() << " : " << targetObj->getXmlTag() << std::endl; + } + std::cout << std::endl; + } + else { + std::cout << "PARTIAL " << entryPair.first << " : " << obj->getTitle() << std::endl; + } + } + } + std::cout << "*** END LISTING ***" << std::endl; + } + else if (commandTokens[0] == "PutXmlAndHdfAtOnce") { + // Create the point set representation, an ETP HDF proxy if necessary and a partial crs + RESQML2_NS::PointSetRepresentation* h1i1PointSetRep = repo.createPointSetRepresentation("", "Horizon1 Interp1 PointSetRep"); + auto* crs = repo.createPartial("", ""); + if (repo.getDefaultHdfProxy() == nullptr) { + auto* etpHdfProxy = repo.createHdfProxy("", "", "", "", COMMON_NS::DataObjectRepository::openingMode::READ_WRITE); + repo.setDefaultHdfProxy(etpHdfProxy); + } + + // Create and push the numerical values to the store + // Internally it uses the ETP Hdf proxy set as the default HDF proxy of the repository in main.cpp. + // pushBackGeometryPatch is a blocking method. If you want non blocking method, you need to use PutDataArray directly. + double pointCoords[18] = { 10, 70, 301, 11, 21, 299, 150, 30, 301, 400, 0, 351, 450, 75, 340, 475, 100, 350 }; + h1i1PointSetRep->pushBackGeometryPatch(6, pointCoords, nullptr, crs); + + // Now send the XML part + Energistics::Etp::v12::Protocol::Store::PutDataObjects putDataObjects; + Energistics::Etp::v12::Datatypes::Object::DataObject dataObject = ETP_NS::FesapiHelpers::buildEtpDataObjectFromEnergisticsObject(h1i1PointSetRep); + putDataObjects.dataObjects["0"] = dataObject; + + session->send(putDataObjects, 0, 0x02 | 0x10); // 0x10 requires Acknowledge from the store + } + } + else if (commandTokens.size() == 3) { + if (commandTokens[0] == "GetDataArray") { + Energistics::Etp::v12::Protocol::DataArray::GetDataArrays gda; + gda.dataArrays["0"].uri = commandTokens[1]; + gda.dataArrays["0"].pathInResource = commandTokens[2]; + std::cout << gda.dataArrays["0"].pathInResource << std::endl; + session->send(gda, 0, 0x02); + std::cout << "Please Set Verbosity to 1 if you don't see anything" << std::endl; + } + if (commandTokens[0] == "GetDataArrayMetadata") { + Energistics::Etp::v12::Protocol::DataArray::GetDataArrayMetadata msg; + msg.dataArrays["0"].uri = commandTokens[1]; + msg.dataArrays["0"].pathInResource = commandTokens[2]; + std::cout << msg.dataArrays["0"].pathInResource << std::endl; + session->send(msg, 0, 0x02); + std::cout << "Please Set Verbosity to 1 if you don't see anything" << std::endl; + } + else if (commandTokens[0] == "PutDataArray") { + Energistics::Etp::v12::Protocol::DataArray::PutDataArrays pda; + pda.dataArrays["0"].uid.uri = commandTokens[1]; + pda.dataArrays["0"].uid.pathInResource = commandTokens[2]; + + std::vector dimensions = { 10 }; + pda.dataArrays["0"].array.dimensions = dimensions; + + Energistics::Etp::v12::Datatypes::AnyArray data; + Energistics::Etp::v12::Datatypes::ArrayOfInt arrayOfInt; + arrayOfInt.values = { 0,1,2,3,4,5,6,7,8,9 }; + data.item.set_ArrayOfInt(arrayOfInt); + pda.dataArrays["0"].array.data = data; + + session->send(pda, 0, 0x02); + } + } + } + + for (auto* hdfProxy : repo.getHdfProxySet()) { + hdfProxy->close(); + } + session->close(); +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + std::cerr << "The command must be : fetpapiClient ipAddress port [target]" << std::endl; + std::cerr << "The command must be : fetpapiClient etpServerUrl (where etpServerUrl must start with ws(s)://...)" << std::endl; + return 1; + } + + if (argc == 4 && (argv[3][0] != '/')) { + std::cerr << "Please put a slash at the start of the target " << argv[3] << std::endl; + return 1; + } + + std::cout << "Give your authorization to pass to the server " << argv[1] << " (or hit enter if no authorization)" << std::endl; + std::string authorization; + std::getline(std::cin, authorization); + + bool successfulConnection = false; + + COMMON_NS::DataObjectRepository repo; + repo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::RESQML2_0_1); + repo.setDefaultStandard(COMMON_NS::DataObjectRepository::EnergisticsStandard::EML2_0); + + boost::uuids::random_generator gen; + ETP_NS::InitializationParameters initializationParams = argc == 2 + ? ETP_NS::InitializationParameters(gen(), argv[1]) // URL based + : ETP_NS::InitializationParameters(gen(), argv[1], std::stoi(argv[2]), argc < 4 ? "/" : argv[3]); // IP Port and target based + std::map< std::string, std::string > additionalHeaderField = { {"data-partition-id", "osdu"} }; // Example for OSDU RDDMS + initializationParams.setAdditionalHandshakeHeaderFields(additionalHeaderField); + + std::cerr << "Creating a client session..." << std::endl; + auto clientSession = ETP_NS::ClientSessionLaunchers::createClientSession(&initializationParams, authorization); + + setFetpapiHandlers(clientSession); + repo.setHdfProxyFactory(new ETP_NS::FesapiHdfProxyFactory(clientSession.get())); + + std::thread sessionThread(&ETP_NS::ClientSession::run, clientSession); + sessionThread.detach(); + + // Wait for the ETP session to be opened + auto t_start = std::chrono::high_resolution_clock::now(); + while (clientSession->isEtpSessionClosed()) { + auto timeOut = std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count(); + if (timeOut > 5000) { + throw std::invalid_argument("Time out : " + std::to_string(timeOut) + " ms.\n"); + } + } + + clientSession->setTimeOut(1500000); + askUser(clientSession, repo); + +#ifdef _WIN32 + _CrtDumpMemoryLeaks(); +#endif + + return 0; +} diff --git a/example/withoutFesapi/CMakeLists.txt b/example/withoutFesapi/CMakeLists.txt new file mode 100644 index 0000000..75f3805 --- /dev/null +++ b/example/withoutFesapi/CMakeLists.txt @@ -0,0 +1,70 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 3.12) + +project (etpClient) + +# ============================================================================ +# build etpClient +# ============================================================================ + +# use, i.e. don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH already +# (but later on when installing) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +set(CMAKE_INSTALL_RPATH "\$ORIGIN/${CMAKE_INSTALL_LIBDIR}") + +# add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +add_executable (${PROJECT_NAME}) +target_sources(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/etpClient.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnCoreProtocolHandlers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnCoreProtocolHandlers.h + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnDiscoveryProtocolHandlers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnDiscoveryProtocolHandlers.h + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnStoreNotificationProtocolHandlers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnStoreNotificationProtocolHandlers.h + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnStoreProtocolHandlers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/MyOwnStoreProtocolHandlers.h) + +target_include_directories (${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/src/) + +add_dependencies (${PROJECT_NAME} Fetpapi) +target_link_libraries (${PROJECT_NAME} PRIVATE Fetpapi) + +target_compile_definitions(${PROJECT_NAME} PRIVATE BOOST_ALL_NO_LIB) +target_link_libraries (${PROJECT_NAME} PRIVATE AVRO::AVRO ${CMAKE_THREAD_LIBS_INIT}) +if (DEFINED Boost_SYSTEM_LIBRARY) + target_link_libraries (${PROJECT_NAME} PRIVATE ${Boost_SYSTEM_LIBRARY}) +endif() +if (WIN32) + target_link_libraries (${PROJECT_NAME} PRIVATE bcrypt.lib) +endif() +if (WITH_ETP_SSL) + target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_ETP_SSL) + target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto) +endif () + +target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${AVRO_INCLUDE_DIR} ${Boost_INCLUDE_DIR}) +if (WITH_ETP_SSL) + target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) +endif () + +if (WIN32) + set_target_properties (${PROJECT_NAME} PROPERTIES + LINK_FLAGS "/INCREMENTAL:NO" + RUNTIME_OUTPUT_DIRECTORY ${FETPAPI_BINARY_DIR}) +endif (WIN32) + +# The value of DEBUG_POSTFIX property is initialized when the target is created to the value of the variable CMAKE__POSTFIX +# except for executable targets because earlier CMake versions which did not use this variable for executables. +set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + +install ( + TARGETS ${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_PREFIX} +) diff --git a/example/withoutFesapi/MyOwnCoreProtocolHandlers.cpp b/example/withoutFesapi/MyOwnCoreProtocolHandlers.cpp new file mode 100644 index 0000000..19d5a91 --- /dev/null +++ b/example/withoutFesapi/MyOwnCoreProtocolHandlers.cpp @@ -0,0 +1,219 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agceements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agceed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#include "MyOwnCoreProtocolHandlers.h" + +#include + +#include +#include + +#include "etp/EtpHelpers.h" +#include "etp/PlainClientSession.h" + +#include "MyOwnDiscoveryProtocolHandlers.h" +#include "MyOwnStoreProtocolHandlers.h" + +void printHelp() +{ + std::cout << "List of available commands :" << std::endl; + std::cout << "\tPing" << std::endl << "\t\tPing the server" << std::endl << std::endl; + std::cout << "\tList" << std::endl << "\t\tList the objects which have been got from ETP to the in-memory epc" << std::endl << std::endl; + std::cout << "\tPutXmldAndHdfAtOnce" << std::endl << "\t\tPut a dummy point set representation to the store sending XML and HDF5 points at once." << std::endl << std::endl; + std::cout << "\tGetResources URI scope(default self) depth(default 1) countObjects(true or false, default is true) dataTypeFilter,dataTypeFilter,...(default noFilter)" << std::endl << std::endl; + std::cout << "\tGetDataObjects dataObjectURI,dataObjectURI,..." << std::endl << "\t\tGet the objects from an ETP store and store them into the in memory epc (only create partial TARGET relationships, not any SOURCE relationships)" << std::endl << std::endl; + std::cout << "\tGetXYZPoints URI" << std::endl << "\t\tGet the XYZ points of a rep from store and print some of them." << std::endl << std::endl; + std::cout << "\tPutDataObject UUID" << std::endl << "\t\tPut the XML part of a dataobject which is on the client side (use \"Load\" command to laod some dataobjects on client side) to the store" << std::endl << std::endl; + std::cout << "\tGetDataArray epcExternalPartURI datasetPathInEpcExternalPart" << std::endl << "\t\tGet the numerical values from a dataset included in an EpcExternalPart over ETP." << std::endl << std::endl; + std::cout << "\tPutDataArray epcExternalPartURI datasetPathInEpcExternalPart" << std::endl << "\t\tPut a dummy {0,1,2,3,4,5,6,7,8,9} integer array in a particular store epcExternalPartURI at a particular dataset path" << std::endl << std::endl; + std::cout << "\tSubscribeNotif URI scope(default self) depth(default 1) receiveXML(true or false, default is true) dataTypeFilter,dataTypeFilter,...(default noFilter)" << std::endl << "\t\tSubscribe to notifications." << std::endl << std::endl; + std::cout << "\tLoad epcDocument" << std::endl << "\t\tLoad an EPC document into the DataObjectRepository." << std::endl << std::endl; + std::cout << "\tSetVerbosity 0or1" << std::endl << "\t\tSet the sesion verbosity to true or false" << std::endl << std::endl; + std::cout << "\tquit" << std::endl << "\t\tQuit the session." << std::endl << std::endl; +} + +void askUser(ETP_NS::AbstractSession* session) +{ + std::string buffer; + + std::cout << "What is your command (\"quit\" for closing connection)?" << std::endl; + std::string command; + while (command != "quit") + { + if (session->isEtpSessionClosed()) { + command = "quit"; + } + else { + std::getline(std::cin, command); + } + auto commandTokens = tokenize(command, ' '); + + if (commandTokens.empty()) { + printHelp(); + continue; + } + if (commandTokens[0] == "quit") { + continue; + } + + if (commandTokens[0] == "GetResources") { + Energistics::Etp::v12::Protocol::Discovery::GetResources mb; + mb.context.uri = commandTokens[1]; + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + mb.context.depth = 1; + mb.context.navigableEdges = Energistics::Etp::v12::Datatypes::Object::RelationshipKind::Primary; + mb.countObjects = true; + + if (commandTokens.size() > 2) { + if (commandTokens[2] == "self") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + else if (commandTokens[2] == "sources") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sources; + else if (commandTokens[2] == "sourcesOrSelf") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sourcesOrSelf; + else if (commandTokens[2] == "targets") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; + else if (commandTokens[2] == "targetsOrSelf") + mb.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targetsOrSelf; + + if (commandTokens.size() > 3) { + mb.context.depth = std::stoi(commandTokens[3]); + + if (commandTokens.size() > 4) { + if (commandTokens[4] == "false" || commandTokens[4] == "False" || commandTokens[4] == "FALSE") { + mb.countObjects = false; + } + + if (commandTokens.size() > 5) { + mb.context.dataObjectTypes = tokenize(commandTokens[5], ','); + } + } + } + } + + session->send(mb, 0, 0x02); + + continue; + } + else if (commandTokens[0] == "SetVerbosity") { + if (commandTokens[1].size() == 1 && commandTokens[1][0] == '1') { + std::cout << "Set the verbosity ON" << std::endl; + session->setVerbose(true); + } + else { + std::cout << "Set the verbosity OFF" << std::endl; + } + session->setVerbose(commandTokens[1].size() == 1 && commandTokens[1][0] == '1'); + } + else if (commandTokens[0] == "GetDataObjects") { + Energistics::Etp::v12::Protocol::Store::GetDataObjects getO; + std::vector tokens = tokenize(commandTokens[1], ','); + std::map tokenMaps; + for (size_t i = 0; i < tokens.size(); ++i) { + tokenMaps[std::to_string(i)] = tokens[i]; + } + getO.uris = tokenMaps; + session->send(getO, 0, 0x02); + } + else if (commandTokens[0] == "SubscribeNotif") { + Energistics::Etp::v12::Protocol::StoreNotification::SubscribeNotifications mb; + Energistics::Etp::v12::Datatypes::Object::SubscriptionInfo subscriptionInfo; + subscriptionInfo.context.uri = commandTokens[1]; + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + subscriptionInfo.context.depth = 1; + boost::uuids::random_generator gen; + boost::uuids::uuid uuid = gen(); + std::move(std::begin(uuid.data), std::end(uuid.data), subscriptionInfo.requestUuid.array.begin()); + + if (commandTokens.size() > 2) { + if (commandTokens[2] == "self") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::self; + else if (commandTokens[2] == "sources") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sources; + else if (commandTokens[2] == "sourcesOrSelf") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::sourcesOrSelf; + else if (commandTokens[2] == "targets") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targets; + else if (commandTokens[2] == "targetsOrSelf") + subscriptionInfo.scope = Energistics::Etp::v12::Datatypes::Object::ContextScopeKind::targetsOrSelf; + + if (commandTokens.size() > 3) { + subscriptionInfo.context.depth = std::stoi(commandTokens[3]); + + if (commandTokens.size() > 4) { + subscriptionInfo.includeObjectData = commandTokens[4] == "true"; + + if (commandTokens.size() > 5) { + subscriptionInfo.context.dataObjectTypes = tokenize(commandTokens[5], ','); + } + } + } + } + + mb.request["0"] = subscriptionInfo; + + session->send(mb, 0, 0x02); + + continue; + } + + if (commandTokens.size() == 1) { + if (commandTokens[0] == "Ping") { + Energistics::Etp::v12::Protocol::Core::Ping ping; + ping.currentDateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + session->send(ping, 0, 0x02); // 0x10 requires Acknowledge from the store + std::cout << "PING at " << ping.currentDateTime << std::endl; + } + } + else if (commandTokens.size() == 3) { + if (commandTokens[0] == "GetDataArray") { + Energistics::Etp::v12::Protocol::DataArray::GetDataArrays gda; + gda.dataArrays["0"].uri = commandTokens[1]; + gda.dataArrays["0"].pathInResource = commandTokens[2]; + std::cout << gda.dataArrays["0"].pathInResource << std::endl; + session->send(gda); + } + else if (commandTokens[0] == "PutDataArray") { + Energistics::Etp::v12::Protocol::DataArray::PutDataArrays pda; + pda.dataArrays["0"].uid.uri = commandTokens[1]; + pda.dataArrays["0"].uid.pathInResource = commandTokens[2]; + + std::vector dimensions = { 10 }; + pda.dataArrays["0"].array.dimensions = dimensions; + + Energistics::Etp::v12::Datatypes::AnyArray data; + Energistics::Etp::v12::Datatypes::ArrayOfInt arrayOfInt; + arrayOfInt.values = { 0,1,2,3,4,5,6,7,8,9 }; + data.item.set_ArrayOfInt(arrayOfInt); + pda.dataArrays["0"].array.data = data; + + session->send(pda, 0, 0x02); + } + } + } + + session->close(); +} + +void MyOwnCoreProtocolHandlers::on_OpenSession(const Energistics::Etp::v12::Protocol::Core::OpenSession & os, int64_t correlationId) +{ + // Ask the user about what he wants to do on another thread + // The main thread is on reading mode + std::thread askUserThread(askUser, session); + askUserThread.detach(); // Detach the thread since we don't want it to be a blocking one. +} diff --git a/example/withoutFesapi/MyOwnCoreProtocolHandlers.h b/example/withoutFesapi/MyOwnCoreProtocolHandlers.h new file mode 100644 index 0000000..c7835a9 --- /dev/null +++ b/example/withoutFesapi/MyOwnCoreProtocolHandlers.h @@ -0,0 +1,30 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#pragma once + +#include "etp/ProtocolHandlers/CoreHandlers.h" + +class MyOwnCoreProtocolHandlers : public ETP_NS::CoreHandlers +{ +public: + MyOwnCoreProtocolHandlers(ETP_NS::AbstractSession* mySession): ETP_NS::CoreHandlers(mySession) {} + ~MyOwnCoreProtocolHandlers() = default; + + void on_OpenSession(const Energistics::Etp::v12::Protocol::Core::OpenSession & os, int64_t correlationId); +}; diff --git a/example/withoutFesapi/MyOwnDiscoveryProtocolHandlers.cpp b/example/withoutFesapi/MyOwnDiscoveryProtocolHandlers.cpp new file mode 100644 index 0000000..fc03720 --- /dev/null +++ b/example/withoutFesapi/MyOwnDiscoveryProtocolHandlers.cpp @@ -0,0 +1,45 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agceements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agceed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#include "MyOwnDiscoveryProtocolHandlers.h" + +#include + +#include "etp/AbstractSession.h" + +void MyOwnDiscoveryProtocolHandlers::on_GetResourcesResponse(const Energistics::Etp::v12::Protocol::Discovery::GetResourcesResponse & msg, int64_t correlationId) +{ + Energistics::Etp::v12::Protocol::Store::GetDataObjects getO; + unsigned int index = 0; + + std::cout << msg.resources.size() << " resources received." << std::endl; + for (const Energistics::Etp::v12::Datatypes::Object::Resource & resource : msg.resources) { + std::cout << "*************************************************" << std::endl; + std::cout << "uri : " << resource.uri << std::endl; + std::cout << "name : " << resource.name << std::endl; + if (resource.sourceCount) + std::cout << "sourceCount : " << resource.sourceCount.get() << std::endl; + if (resource.targetCount) + std::cout << "targetCount : " << resource.targetCount.get() << std::endl; + std::cout << "*************************************************" << std::endl; + } + + if (!getO.uris.empty()) { + session->send(getO, 0, 0x02); + } +} diff --git a/example/withoutFesapi/MyOwnDiscoveryProtocolHandlers.h b/example/withoutFesapi/MyOwnDiscoveryProtocolHandlers.h new file mode 100644 index 0000000..e5f5945 --- /dev/null +++ b/example/withoutFesapi/MyOwnDiscoveryProtocolHandlers.h @@ -0,0 +1,32 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#pragma once + +#include "etp/ProtocolHandlers/DiscoveryHandlers.h" + +class MyOwnDiscoveryProtocolHandlers : public ETP_NS::DiscoveryHandlers +{ +public: + MyOwnDiscoveryProtocolHandlers(ETP_NS::AbstractSession* mySession): ETP_NS::DiscoveryHandlers(mySession) {} + ~MyOwnDiscoveryProtocolHandlers() {} + + std::vector getObjectWhenDiscovered; // all message id in this vector will result in response where the objects are going to be get in addition to be discovered + + void on_GetResourcesResponse(const Energistics::Etp::v12::Protocol::Discovery::GetResourcesResponse & msg, int64_t correlationId); +}; diff --git a/example/withoutFesapi/MyOwnStoreNotificationProtocolHandlers.cpp b/example/withoutFesapi/MyOwnStoreNotificationProtocolHandlers.cpp new file mode 100644 index 0000000..7d88926 --- /dev/null +++ b/example/withoutFesapi/MyOwnStoreNotificationProtocolHandlers.cpp @@ -0,0 +1,19 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agceements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agceed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#include "MyOwnStoreNotificationProtocolHandlers.h" diff --git a/example/withoutFesapi/MyOwnStoreNotificationProtocolHandlers.h b/example/withoutFesapi/MyOwnStoreNotificationProtocolHandlers.h new file mode 100644 index 0000000..68c653b --- /dev/null +++ b/example/withoutFesapi/MyOwnStoreNotificationProtocolHandlers.h @@ -0,0 +1,28 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#pragma once + +#include "etp/ProtocolHandlers/StoreNotificationHandlers.h" + +class MyOwnStoreNotificationProtocolHandlers : public ETP_NS::StoreNotificationHandlers +{ +public: + MyOwnStoreNotificationProtocolHandlers(ETP_NS::AbstractSession* mySession) : ETP_NS::StoreNotificationHandlers(mySession) {} + ~MyOwnStoreNotificationProtocolHandlers() {} +}; diff --git a/example/withoutFesapi/MyOwnStoreProtocolHandlers.cpp b/example/withoutFesapi/MyOwnStoreProtocolHandlers.cpp new file mode 100644 index 0000000..d7f5915 --- /dev/null +++ b/example/withoutFesapi/MyOwnStoreProtocolHandlers.cpp @@ -0,0 +1,35 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agceements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agceed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#include "MyOwnStoreProtocolHandlers.h" + +#include + +#include "etp/PlainClientSession.h" + +void MyOwnStoreProtocolHandlers::on_GetDataObjectsResponse(const Energistics::Etp::v12::Protocol::Store::GetDataObjectsResponse & obj, int64_t correlationId) +{ + for (const auto & graphResource : obj.dataObjects) { + std::cout << "*************************************************" << std::endl; + std::cout << "Resource received : " << std::endl; + std::cout << "uri : " << graphResource.second.resource.uri << std::endl; + std::cout << "name : " << graphResource.second.resource.name << std::endl; + std::cout << "xml : " << graphResource.second.data << std::endl; + std::cout << "*************************************************" << std::endl; + } +} diff --git a/example/withoutFesapi/MyOwnStoreProtocolHandlers.h b/example/withoutFesapi/MyOwnStoreProtocolHandlers.h new file mode 100644 index 0000000..926df19 --- /dev/null +++ b/example/withoutFesapi/MyOwnStoreProtocolHandlers.h @@ -0,0 +1,30 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#pragma once + +#include "etp/ProtocolHandlers/StoreHandlers.h" + +class MyOwnStoreProtocolHandlers : public ETP_NS::StoreHandlers +{ +public: + MyOwnStoreProtocolHandlers(ETP_NS::AbstractSession* mySession) : ETP_NS::StoreHandlers(mySession) {} + ~MyOwnStoreProtocolHandlers() = default; + + void on_GetDataObjectsResponse(const Energistics::Etp::v12::Protocol::Store::GetDataObjectsResponse & obj, int64_t correlationId); +}; diff --git a/example/withoutFesapi/etpClient.cpp b/example/withoutFesapi/etpClient.cpp new file mode 100644 index 0000000..8ad60f2 --- /dev/null +++ b/example/withoutFesapi/etpClient.cpp @@ -0,0 +1,88 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#include +#include + +#include "etp/ClientSessionLaunchers.h" +#include "etp/InitializationParameters.h" +#include "etp/ProtocolHandlers/DataArrayHandlers.h" +#include "etp/ProtocolHandlers/StoreNotificationHandlers.h" + +#include "MyOwnCoreProtocolHandlers.h" +#include "MyOwnDiscoveryProtocolHandlers.h" +#include "MyOwnStoreProtocolHandlers.h" + +using namespace ETP_NS; + +void setProtocolHandlers(std::shared_ptr session) { + session->setCoreProtocolHandlers(std::make_shared(session.get())); + session->setDiscoveryProtocolHandlers(std::make_shared(session.get())); + session->setStoreProtocolHandlers(std::make_shared(session.get())); + session->setDataArrayProtocolHandlers(std::make_shared(session.get())); + session->setStoreNotificationProtocolHandlers(std::make_shared(session.get())); +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + std::cerr << "The command must be : fetpapiClient ipAddress port [target]" << std::endl; + std::cerr << "The command must be : fetpapiClient etpServerUrl (where etpServerUrl must start with ws(s)://...)" << std::endl; + return 1; + } + + if (argc == 4 && (argv[3][0] != '/')) { + std::cerr << "Please put a slash at the start of the target " << argv[3] << std::endl; + return 1; + } + + std::cout << "Give your authorization to pass to the server " << argv[1] << " (or hit enter if no authorization)" << std::endl; + std::string authorization; + std::getline(std::cin, authorization); + + bool successfulConnection = false; + + boost::uuids::random_generator gen; + ETP_NS::InitializationParameters initializationParams = argc == 2 + ? ETP_NS::InitializationParameters(gen(), argv[1]) // URL based + : ETP_NS::InitializationParameters(gen(), argv[1], std::stoi(argv[2]), argc < 4 ? "/" : argv[3]); // IP Port and target based + std::map< std::string, std::string > additionalHeaderField = { {"data-partition-id", "osdu"} }; // Example for OSDU RDDMS + initializationParams.setAdditionalHandshakeHeaderFields(additionalHeaderField); + + std::cerr << "Creating a client session..." << std::endl; + auto clientSession = ClientSessionLaunchers::createClientSession(&initializationParams, authorization); + setProtocolHandlers(clientSession); + + std::cerr << "Running the client session..." << std::endl; + successfulConnection = clientSession->run(); + + if (!successfulConnection) { + if (argc > 2) { + std::cout << "Could not connect to the server " << argv[1] << " " << argv[2] << std::endl; + } + else { + std::cout << "Could not connect to the server " << argv[1] << std::endl; + } + } + +#ifdef _WIN32 + _CrtDumpMemoryLeaks(); +#endif + + return 0; +} diff --git a/src/etp/AbstractClientSession.h b/src/etp/AbstractClientSessionCRTP.h similarity index 52% rename from src/etp/AbstractClientSession.h rename to src/etp/AbstractClientSessionCRTP.h index 74b05b3..511102f 100644 --- a/src/etp/AbstractClientSession.h +++ b/src/etp/AbstractClientSessionCRTP.h @@ -18,62 +18,18 @@ under the License. -----------------------------------------------------------------------*/ #pragma once -#include "AbstractSession.h" - -#include - -#include -#include -#include - -#include "InitializationParameters.h" +#include "ClientSession.h" namespace ETP_NS { // Echoes back all received WebSocket messages. // This uses the Curiously Recurring Template Pattern so that the same code works with both SSL streams and regular sockets. template - class AbstractClientSession : public ETP_NS::AbstractSession + class AbstractClientSessionCRTP : public ETP_NS::ClientSession { public: - virtual ~AbstractClientSession() = default; - - boost::asio::io_context& getIoContext() { - return ioc; - } - - const std::string& getHost() const { return host; } - const std::string& getPort() const { return port; } - const std::string& getTarget() const { return target; } - const std::string& getAuthorization() const { return authorization; } - - /** - * Run the websocket and then the ETP session in a processing loop. - * Everything related to this session (including the completion handlers) will operate on the current thread in a single event loop. - * Since this is a loop, you may want to operate this method on a dedicated thread not to block your program. - * This method returns only when the session is closed. - */ - bool run() { - successfulConnection = false; - - // Look up the domain name before to run the session - // It is important to do this before to run the io context. Otherwise running the io context would return immediately if nothing has to be done. - resolver.async_resolve( - host, - port, - std::bind( - &AbstractClientSession::on_resolve, - std::static_pointer_cast(shared_from_this()), - std::placeholders::_1, - std::placeholders::_2)); - - // Run the io_context to perform the resolver and all other binding functions - // Run will return only when there will no more be any uncomplete operations (such as a reading operation for example) - getIoContext().run(); - - return successfulConnection; - } + virtual ~AbstractClientSessionCRTP() = default; void on_connect(boost::system::error_code ec) { if (ec) { @@ -83,26 +39,32 @@ namespace ETP_NS #if BOOST_VERSION < 107000 // Perform the websocket handshake derived().ws().async_handshake_ex(responseType, - host + ":" + port, target, + etpServerHost + ":" + etpServerPort, etpServerTarget, [&](websocket::request_type& m) { m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); - m.insert(boost::beast::http::field::authorization, authorization); + m.insert(boost::beast::http::field::authorization, etpServerAuthorization); + if (!proxyHost.empty() && !isTls()) { + m.insert(boost::beast::http::field::proxy_authorization, proxyAuthorization); + } m.insert("etp-encoding", "binary"); for (const auto& mapEntry : additionalHandshakeHeaderFields_) { m.insert(mapEntry.first, mapEntry.second); } }, std::bind( - &AbstractClientSession::on_handshake, - std::static_pointer_cast(shared_from_this()), + &AbstractClientSessionCRTP::on_handshake, + std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); #else derived().ws().set_option(websocket::stream_base::decorator( [&](websocket::request_type& m) { m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); - m.insert(boost::beast::http::field::authorization, authorization); + m.insert(boost::beast::http::field::authorization, etpServerAuthorization); + if (!proxyHost.empty() && !isTls()) { + m.insert(boost::beast::http::field::proxy_authorization, proxyAuthorization); + } m.insert("etp-encoding", "binary"); for (const auto& mapEntry : additionalHandshakeHeaderFields_) { m.insert(mapEntry.first, mapEntry.second); @@ -111,10 +73,10 @@ namespace ETP_NS ); // Perform the websocket handshake derived().ws().async_handshake(responseType, - host + ":" + port, target, + etpServerHost + ":" + etpServerPort, etpServerTarget, std::bind( - &AbstractClientSession::on_handshake, - std::static_pointer_cast(shared_from_this()), + &AbstractClientSessionCRTP::on_handshake, + std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); #endif } @@ -147,8 +109,6 @@ namespace ETP_NS std::placeholders::_1, std::placeholders::_2)); } - - virtual void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) = 0; void on_handshake(boost::system::error_code ec) { @@ -175,63 +135,11 @@ namespace ETP_NS } protected: - boost::asio::io_context ioc; - tcp::resolver resolver; - std::string host; - std::string port; - std::string target; - std::string authorization; - std::map additionalHandshakeHeaderFields_; - websocket::response_type responseType; // In order to check handshake sec_websocket_protocol - Energistics::Etp::v12::Protocol::Core::RequestSession requestSession; - bool successfulConnection = false; + using ClientSession::ClientSession; // Access the derived class, this is part of the Curiously Recurring Template Pattern idiom. Derived& derived() { return static_cast(*this); } - AbstractClientSession() : - ioc(4), - resolver(ioc) { - messageId = 2; // The client side of the connection MUST use ONLY non-zero even-numbered messageIds. - } - - /** - * @param initializationParams The initialization parameters of the session including IP host, port, requestedProtocols, supportedDataObjects - * @param target usually "/" but a server can decide to serve etp on a particular target - * @param authorization The HTTP authorization attribute to send to the server. It may be empty if not needed. - */ - AbstractClientSession( - InitializationParameters* initializationParams, const std::string & target, const std::string & authorization) : - ioc(4), - resolver(ioc), - host(initializationParams->getHost()), - port(std::to_string(initializationParams->getPort())), - target(target), - authorization(authorization) - { - messageId = 2; // The client side of the connection MUST use ONLY non-zero even-numbered messageIds. - - initializationParams->postSessionCreationOperation(this); - - // Build the request session - requestSession.applicationName = initializationParams->getApplicationName(); - requestSession.applicationVersion = initializationParams->getApplicationVersion(); - - std::copy(std::begin(initializationParams->getInstanceId().data), std::end(initializationParams->getInstanceId().data), requestSession.clientInstanceId.array.begin()); - - requestSession.requestedProtocols = initializationParams->makeSupportedProtocols(); - requestSession.supportedDataObjects = initializationParams->makeSupportedDataObjects(); - requestSession.supportedFormats.push_back("xml"); - requestSession.currentDateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - auto caps = initializationParams->makeEndpointCapabilities(); - if (!caps.empty()) { - requestSession.endpointCapabilities = caps; - } - - maxWebSocketMessagePayloadSize = initializationParams->getMaxWebSocketMessagePayloadSize(); - } - void do_write() { const std::lock_guard specificProtocolHandlersLock(specificProtocolHandlersMutex); if (sendingQueue.empty()) { diff --git a/src/etp/AbstractPlainOrSslServerSession.h b/src/etp/AbstractPlainOrSslServerSession.h deleted file mode 100644 index 272eb42..0000000 --- a/src/etp/AbstractPlainOrSslServerSession.h +++ /dev/null @@ -1,206 +0,0 @@ -/*----------------------------------------------------------------------- -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"; you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. ------------------------------------------------------------------------*/ -#pragma once - -#include "AbstractSession.h" - -#include - -#include -#include -#include - -#include "ServerInitializationParameters.h" - -namespace ETP_NS -{ - // IMPORTANT : This class is no more maintained now that the OSDU RDDMS acts as a Reference Implementation - - - // Echoes back all received WebSocket messages. - // This uses the Curiously Recurring Template Pattern so that the same code works with both SSL streams and regular sockets. - template - class AbstractPlainOrSslServerSession : public ETP_NS::AbstractSession - { - private: - // Access the derived class, this is part of the Curiously Recurring Template Pattern idiom. - Derived& derived() { return static_cast(*this); } - - protected: - boost::asio::strand strand; - ServerInitializationParameters* serverInitializationParams_; - - void do_write() { - if (sendingQueue.empty()) { - fesapi_log("The sending queue is empty."); - return; - } - - bool previousSentMessageCompleted = specificProtocolHandlers.find(std::get<0>(sendingQueue.front())) == specificProtocolHandlers.end(); - - if (!previousSentMessageCompleted) { - fesapi_log("Cannot send Message id :", std::to_string(std::get<0>(sendingQueue.front())), " because the previous messgae has not finished to be sent."); - } - else { - derived().ws().async_write( - boost::asio::buffer(std::get<1>(sendingQueue.front())), - boost::asio::bind_executor( - strand, - std::bind( - &AbstractSession::on_write, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2))); - - // Register the handler to respond to the sent message - specificProtocolHandlers[std::get<0>(sendingQueue.front())] = std::get<2>(sendingQueue.front()); - } - } - - public: - AbstractPlainOrSslServerSession(boost::asio::io_context& ioc, ServerInitializationParameters* serverInitializationParams) : - strand(ioc.get_executor()), - serverInitializationParams_(serverInitializationParams) - { - messageId = 1; // The client side of the connection MUST use ONLY non-zero even-numbered messageIds. - boost::uuids::random_generator gen; - identifier = gen(); - } - - virtual ~AbstractPlainOrSslServerSession() = default; - - boost::asio::io_context& getIoContext() { - return static_cast (derived().ws().get_executor().context()); - } - - ServerInitializationParameters const* getServerInitializationParameters() const { return serverInitializationParams_; } - - /** - * The key is the UUID of the subscription. - * The vector allows to buffer the dataobjects which we want to be notified about - */ - std::vector subscriptions; - - virtual bool run(boost::beast::http::request req) = 0; - - /** - * This method is done after ssl handshake - * or directly if no SSL mode - */ - void on_handshake(boost::system::error_code ec, - boost::beast::http::request req) - { - if (ec) { - std::cerr << "on_handshake : " << ec.message() << std::endl; - } - -#ifndef LINUX - // Accept the websocket handshake -#if BOOST_VERSION < 107000 - derived().ws().async_accept_ex(req, - [](websocket::response_type& m) - { - m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); - }, -#else - derived().ws().set_option(websocket::stream_base::decorator( - [](websocket::response_type& m) - { - m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); - }) - ); - derived().ws().async_accept(req, -#endif - boost::asio::bind_executor( - strand, - std::bind( - &AbstractPlainOrSslServerSession::on_accept, - std::static_pointer_cast(shared_from_this()), - std::placeholders::_1))); -#else -#if BOOST_VERSION < 107000 - derived().ws().async_accept_ex( - [](websocket::response_type& m) - { - m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); - }, -#else - derived().ws().set_option(websocket::stream_base::decorator( - [](websocket::response_type& m) - { - m.insert(boost::beast::http::field::sec_websocket_protocol, "etp12.energistics.org"); - }) - ); - derived().ws().async_accept( -#endif - boost::asio::bind_executor( - strand, - std::bind( - &AbstractPlainOrSslServerSession::on_accept, - std::static_pointer_cast(shared_from_this()), - std::placeholders::_1))); -#endif - } - - void do_close() { - derived().ws().async_close(websocket::close_code::normal, - boost::asio::bind_executor( - strand, - std::bind( - &AbstractSession::on_close, - shared_from_this(), - std::placeholders::_1))); - } - - void on_accept(boost::system::error_code ec) { - if (ec) { - std::cerr << "on_accept : " << ec.message() << std::endl; - } - - webSocketSessionClosed = false; - - // Read a message - do_read(); - } - - void do_read() - { - if (webSocketSessionClosed) { - fesapi_log("CLOSED : NOTHING MORE TO DO"); - return; - } - - // Read a message into our buffer - derived().ws().async_read( - receivedBuffer, - boost::asio::bind_executor( - strand, - std::bind( - &AbstractSession::on_read, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2))); - } - - void setMaxWebSocketMessagePayloadSize(int64_t value) final { - maxWebSocketMessagePayloadSize = value; - derived().ws().read_message_max(value); - } - }; -} diff --git a/src/etp/AbstractSession.cpp b/src/etp/AbstractSession.cpp index 43105d1..cd5c4b9 100644 --- a/src/etp/AbstractSession.cpp +++ b/src/etp/AbstractSession.cpp @@ -16,7 +16,6 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -----------------------------------------------------------------------*/ - #include "AbstractSession.h" #include "EtpHelpers.h" diff --git a/src/etp/AbstractSession.h b/src/etp/AbstractSession.h index dd54abf..ad92efa 100644 --- a/src/etp/AbstractSession.h +++ b/src/etp/AbstractSession.h @@ -24,9 +24,9 @@ under the License. #include #include +#include #include #include -#include #include #include diff --git a/src/etp/ClientSession.h b/src/etp/ClientSession.h new file mode 100644 index 0000000..f1fea5a --- /dev/null +++ b/src/etp/ClientSession.h @@ -0,0 +1,150 @@ +/*----------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"; you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +-----------------------------------------------------------------------*/ +#pragma once + +#include "AbstractSession.h" + +#include "InitializationParameters.h" + +namespace ETP_NS +{ + class ClientSession : public ETP_NS::AbstractSession + { + public: + + virtual ~ClientSession() = default; + + boost::asio::io_context& getIoContext() { + return ioc; + } + + const std::string& getEtpServerHost() const { return etpServerHost; } + const std::string& getEtpServerPort() const { return etpServerPort; } + const std::string& getEtpServerTarget() const { return etpServerTarget; } + const std::string& getEtpServerAuthorization() const { return etpServerAuthorization; } + const std::string& getProxyHost() const { return proxyHost; } + const std::string& getProxyPort() const { return proxyPort; } + const std::string& getProxyAuthorization() const { return proxyAuthorization; } + + /** + * Run the websocket and then the ETP session in a processing loop. + * Everything related to this session (including the completion handlers) will operate on the current thread in a single event loop. + * Since this is a loop, you may want to operate this method on a dedicated thread not to block your program. + * This method returns only when the session is closed. + */ + bool run() { + successfulConnection = false; + + // Look up the domain name before to run the session + // It is important to do this before to run the io context. Otherwise running the io context would return immediately if nothing has to be done. + resolver.async_resolve( + proxyHost.empty() ? etpServerHost : proxyHost, + proxyHost.empty() ? etpServerPort : proxyPort, + std::bind( + &ClientSession::on_resolve, + std::static_pointer_cast(shared_from_this()), + std::placeholders::_1, + std::placeholders::_2)); + + // Run the io_context to perform the resolver and all other binding functions + // Run will return only when there will no more be any uncomplete operations (such as a reading operation for example) + getIoContext().run(); + + return successfulConnection; + } + + virtual void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) = 0; + virtual bool isTls() const = 0; + + void on_handshake(boost::system::error_code ec) + { + if (ec) { + std::cerr << "on_handshake : " << ec.message() << std::endl; + std::cerr << "Sometimes some ETP server require a trailing slash at the end of their URL. Did you also check your optional \"data-partition-id\" additional Header Field?" << std::endl; + return; + } + + if (!responseType.count(boost::beast::http::field::sec_websocket_protocol) || + responseType[boost::beast::http::field::sec_websocket_protocol] != "etp12.energistics.org") + std::cerr << "The client MUST specify the Sec-Websocket-Protocol header value of etp12.energistics.org, and the server MUST reply with the same" << std::endl; + + successfulConnection = true; + webSocketSessionClosed = false; + + send(requestSession, 0, 0x02); + do_read(); + } + + protected: + boost::asio::io_context ioc; + tcp::resolver resolver; + std::string etpServerHost; + std::string etpServerPort; + std::string etpServerTarget; + std::string etpServerAuthorization; + std::string proxyHost; + std::string proxyPort; + std::string proxyAuthorization; + std::map additionalHandshakeHeaderFields_; + websocket::response_type responseType; // In order to check handshake sec_websocket_protocol + Energistics::Etp::v12::Protocol::Core::RequestSession requestSession; + bool successfulConnection = false; + + /** + * @param initializationParams The initialization parameters of the session including IP host, port, requestedProtocols, supportedDataObjects + * @param target usually "/" but a server can decide to serve etp on a particular target + * @param etpServerAuth The HTTP authorization attribute to send to the ETP server. It may be empty if not needed. + * @param proxyAuth The HTTP authorization attribute to send to the proxy server. It may be empty if not needed. + */ + ClientSession( + InitializationParameters* initializationParams, const std::string& target, const std::string& etpServerAuth, const std::string& proxyAuth = "") : + ioc(4), + resolver(ioc), + etpServerHost(initializationParams->getEtpServerHost()), + etpServerPort(std::to_string(initializationParams->getEtpServerPort())), + etpServerTarget(target), + etpServerAuthorization(etpServerAuth), + proxyHost(initializationParams->getProxyHost()), + proxyPort(std::to_string(initializationParams->getProxyPort())), + proxyAuthorization(proxyAuth) + { + messageId = 2; // The client side of the connection MUST use ONLY non-zero even-numbered messageIds. + + initializationParams->postSessionCreationOperation(this); + + // Build the request session + requestSession.applicationName = initializationParams->getApplicationName(); + requestSession.applicationVersion = initializationParams->getApplicationVersion(); + + std::copy(std::begin(initializationParams->getInstanceId().data), std::end(initializationParams->getInstanceId().data), requestSession.clientInstanceId.array.begin()); + + requestSession.requestedProtocols = initializationParams->makeSupportedProtocols(); + requestSession.supportedDataObjects = initializationParams->makeSupportedDataObjects(); + requestSession.supportedFormats.push_back("xml"); + requestSession.currentDateTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + auto caps = initializationParams->makeEndpointCapabilities(); + if (!caps.empty()) { + requestSession.endpointCapabilities = caps; + } + + maxWebSocketMessagePayloadSize = initializationParams->getMaxWebSocketMessagePayloadSize(); + } + }; +} diff --git a/src/etp/ClientSessionLaunchers.cpp b/src/etp/ClientSessionLaunchers.cpp index 17a976f..6327d5b 100644 --- a/src/etp/ClientSessionLaunchers.cpp +++ b/src/etp/ClientSessionLaunchers.cpp @@ -21,6 +21,12 @@ under the License. #include #include "HttpClientSession.h" +#include "PlainClientSession.h" + +#ifdef WITH_ETP_SSL +#include "ssl/HttpsClientSession.h" +#include "ssl/SslClientSession.h" +#endif namespace { @@ -114,7 +120,7 @@ namespace return result; } - std::size_t getNegotiatedMaxWebSocketFramePayloadSize(const std::string & responseBody, std::size_t preferredMaxFrameSize) { + std::size_t getNegotiatedMaxWebSocketFramePayloadSize(const std::string& responseBody, std::size_t preferredMaxFrameSize) { const auto maxWebSocketFramePayloadSizePos = responseBody.find("MaxWebSocketFramePayloadSize"); if (maxWebSocketFramePayloadSizePos != std::string::npos) { std::istringstream iss(responseBody); @@ -137,71 +143,71 @@ namespace } } -std::shared_ptr ETP_NS::ClientSessionLaunchers::createWsClientSession(InitializationParameters* initializationParams, const std::string & authorization, - const std::map& additionalHandshakeHeaderFields, std::size_t preferredMaxFrameSize) +std::shared_ptr ETP_NS::ClientSessionLaunchers::createClientSession(InitializationParameters* initializationParams, + const std::string& authorization, const std::string& proxyAuthorization) { boost::asio::io_context ioc; - auto httpClientSession = std::make_shared(ioc); - std::string etpServerCapTarget = "/" + initializationParams->getUrlPath(); - if (etpServerCapTarget[etpServerCapTarget.size() - 1] != '/') { + + std::string etpServerCapTarget = "/" + initializationParams->getEtpServerUrlPath(); + if (etpServerCapTarget.back() != '/') { etpServerCapTarget += '/'; } etpServerCapTarget += ".well-known/etp-server-capabilities?GetVersion=etp12.energistics.org"; - httpClientSession->run(initializationParams->getHost().c_str(), initializationParams->getPort(), etpServerCapTarget.c_str(), 11, authorization); - // Run the I/O service. The call will return when the get operation is complete. - ioc.run(); - - preferredMaxFrameSize = getNegotiatedMaxWebSocketFramePayloadSize(httpClientSession->getResponse().body(), preferredMaxFrameSize); - - auto result = std::make_shared(initializationParams, "/" + initializationParams->getUrlPath(), authorization, additionalHandshakeHeaderFields, preferredMaxFrameSize); - initializationParams->postSessionCreationOperation(result.get()); - return result; -} #ifdef WITH_ETP_SSL - -#include "ssl/HttpsClientSession.h" - -namespace ssl = boost::asio::ssl; // from - -std::shared_ptr ETP_NS::ClientSessionLaunchers::createWssClientSession(InitializationParameters* initializationParams, const std::string & authorization, - const std::map& additionalHandshakeHeaderFields, std::size_t preferredMaxFrameSize, const std::string & additionalCertificates) -{ - // The SSL context is required, and holds certificates - ssl::context ctx{ ssl::context::sslv23_client }; - ctx.set_default_verify_paths(); - ctx.set_options( - ssl::context::default_workarounds - | ssl::context::no_sslv2 - | ssl::context::no_sslv3 - | ssl::context::single_dh_use - ); - - if (!additionalCertificates.empty()) { - boost::system::error_code ec; - ctx.add_certificate_authority( - boost::asio::buffer(additionalCertificates.data(), additionalCertificates.size()), ec); - if (ec) { - std::cerr << "Cannot add certificates : " << additionalCertificates << std::endl; - return nullptr; + if (initializationParams->getEtpServerPort() == 443 || initializationParams->isTlsForced()) { + // The SSL context is required, and holds certificates + boost::asio::ssl::context ctx{ boost::asio::ssl::context::sslv23_client }; + ctx.set_default_verify_paths(); + ctx.set_options( + boost::asio::ssl::context::default_workarounds + | boost::asio::ssl::context::no_sslv2 + | boost::asio::ssl::context::no_sslv3 + | boost::asio::ssl::context::single_dh_use + ); + + const std::string& additionalCertificates = initializationParams->getAdditionalCertificates(); + if (!additionalCertificates.empty()) { + boost::system::error_code ec; + ctx.add_certificate_authority( + boost::asio::buffer(additionalCertificates.data(), additionalCertificates.size()), ec); + if (ec) { + std::cerr << "Cannot add certificates : " << additionalCertificates << std::endl; + return nullptr; + } } + auto restClientSession = std::make_shared(ioc, ctx); + restClientSession->run( + initializationParams->getEtpServerHost(), initializationParams->getEtpServerPort(), etpServerCapTarget, 11, authorization, + initializationParams->getProxyHost(), initializationParams->getProxyPort(), proxyAuthorization); + // Run the I/O service. The call will return when the get operation is complete. + ioc.run(); + + std::size_t preferredMaxFrameSize = getNegotiatedMaxWebSocketFramePayloadSize(restClientSession->getResponse().body(), initializationParams->getPreferredMaxFrameSize()); + + auto result = std::make_shared(ctx, initializationParams, "/" + initializationParams->getEtpServerUrlPath(), + authorization, proxyAuthorization, + initializationParams->getAdditionalHandshakeHeaderFields(), preferredMaxFrameSize); + initializationParams->postSessionCreationOperation(result.get()); + return result; } - - boost::asio::io_context ioc; - auto httpsClientSession = std::make_shared(ioc, ctx); - std::string etpServerCapTarget = "/" + initializationParams->getUrlPath(); - if (etpServerCapTarget[etpServerCapTarget.size() - 1] != '/') { - etpServerCapTarget += '/'; + else { +#endif + auto restClientSession = std::make_shared(ioc); + restClientSession->run( + initializationParams->getEtpServerHost(), initializationParams->getEtpServerPort(), etpServerCapTarget, 11, authorization, + initializationParams->getProxyHost(), initializationParams->getProxyPort(), proxyAuthorization); + // Run the I/O service. The call will return when the get operation is complete. + ioc.run(); + + std::size_t preferredMaxFrameSize = getNegotiatedMaxWebSocketFramePayloadSize(restClientSession->getResponse().body(), initializationParams->getPreferredMaxFrameSize()); + + auto result = std::make_shared(initializationParams, "/" + initializationParams->getEtpServerUrlPath(), + authorization, proxyAuthorization, + initializationParams->getAdditionalHandshakeHeaderFields(), preferredMaxFrameSize); + initializationParams->postSessionCreationOperation(result.get()); + return result; +#ifdef WITH_ETP_SSL } - etpServerCapTarget += ".well-known/etp-server-capabilities?GetVersion=etp12.energistics.org"; - httpsClientSession->run(initializationParams->getHost().c_str(), initializationParams->getPort(), etpServerCapTarget.c_str(), 11, authorization); - // Run the I/O service. The call will return when the get operation is complete. - ioc.run(); - - preferredMaxFrameSize = getNegotiatedMaxWebSocketFramePayloadSize(httpsClientSession->getResponse().body(), preferredMaxFrameSize); - - auto result = std::make_shared(ctx, initializationParams, "/" + initializationParams->getUrlPath(), authorization, additionalHandshakeHeaderFields, preferredMaxFrameSize); - initializationParams->postSessionCreationOperation(result.get()); - return result; -} #endif +} diff --git a/src/etp/ClientSessionLaunchers.h b/src/etp/ClientSessionLaunchers.h index 87e0047..a6846bb 100644 --- a/src/etp/ClientSessionLaunchers.h +++ b/src/etp/ClientSessionLaunchers.h @@ -18,10 +18,7 @@ under the License. -----------------------------------------------------------------------*/ #pragma once -#include "PlainClientSession.h" -#ifdef WITH_ETP_SSL -#include "ssl/SslClientSession.h" -#endif +#include "ClientSession.h" #include "InitializationParameters.h" @@ -29,18 +26,7 @@ namespace ETP_NS { namespace ClientSessionLaunchers { - /** - * @param preferredFrameSize The preferred websocket frame payload to use by this client. - * If the ETP websocket server on the other end has a lower related MaxWebSocketFramePayloadSize capability, then the used websocket frame payload will be the server one. - * Default value corresponds to the default Boost.Beast value : https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/write_buffer_bytes/overload1.html. - */ - FETPAPI_DLL_IMPORT_OR_EXPORT std::shared_ptr createWsClientSession(InitializationParameters* initializationParams, const std::string & authorization, - const std::map& additionalHandshakeHeaderFields = {}, std::size_t preferredMaxFrameSize = 4096); - -#ifdef WITH_ETP_SSL - FETPAPI_DLL_IMPORT_OR_EXPORT std::shared_ptr createWssClientSession(InitializationParameters* initializationParams, const std::string & authorization, - const std::map& additionalHandshakeHeaderFields = {}, std::size_t preferredMaxFrameSize = 4096, const std::string & additionalCertificates = ""); -#endif - + FETPAPI_DLL_IMPORT_OR_EXPORT std::shared_ptr createClientSession(InitializationParameters* initializationParams, + const std::string & etpServerAuthorization, const std::string& proxyAuthorization = ""); } } diff --git a/src/etp/EtpMessages.h b/src/etp/EtpMessages.h index 2f2c62a..34274b3 100644 --- a/src/etp/EtpMessages.h +++ b/src/etp/EtpMessages.h @@ -1946,7 +1946,7 @@ namespace Energistics { namespace v12 { namespace Datatypes { struct Uuid{ - std::array array{}; + std::array array{{}}; }; } } diff --git a/src/etp/HttpClientSession.h b/src/etp/HttpClientSession.h index bc364a5..b3c4369 100644 --- a/src/etp/HttpClientSession.h +++ b/src/etp/HttpClientSession.h @@ -18,12 +18,13 @@ under the License. -----------------------------------------------------------------------*/ #pragma once +#include + #include #include #include #include #include -#include using tcp = boost::asio::ip::tcp; // from namespace http = boost::beast::http; // from @@ -51,26 +52,32 @@ namespace ETP_NS // Start the asynchronous operation void run( - char const* host, - unsigned short port, - char const* target, - int version, - std::string authorization = "") + const std::string& etpServerHost, + uint16_t etpServerPort, + const std::string& etpServerTarget, + uint32_t version, + const std::string& etpServerAuthorization = "", + const std::string& proxyHost = "", + uint16_t proxyPort = 80, + const std::string& proxyAuthorization = "") { // Set up an HTTP GET request message req_.version(version); req_.method(http::verb::get); - req_.target(target); - req_.set(http::field::host, host); + req_.target(etpServerTarget); + req_.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort)); req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - if (!authorization.empty()) { - req_.set(http::field::authorization, authorization); + if (!etpServerAuthorization.empty()) { + req_.set(http::field::authorization, etpServerAuthorization); + } + if (!proxyAuthorization.empty()) { + req_.set(http::field::proxy_authorization, proxyAuthorization); } // Look up the domain name resolver_.async_resolve( - host, - std::to_string(port).c_str(), + proxyHost.empty() ? etpServerHost : proxyHost, + std::to_string(proxyHost.empty() ? etpServerPort : proxyPort), std::bind( &HttpClientSession::on_resolve, shared_from_this(), diff --git a/src/etp/InitializationParameters.cpp b/src/etp/InitializationParameters.cpp index 8c2f98d..2b2727d 100644 --- a/src/etp/InitializationParameters.cpp +++ b/src/etp/InitializationParameters.cpp @@ -19,53 +19,70 @@ under the License. #include "InitializationParameters.h" #include +#include #include "AbstractSession.h" using namespace ETP_NS; -InitializationParameters::InitializationParameters(const std::string & etpUrl) -{ - const size_t schemeSeparatorPos = etpUrl.find("://"); +namespace { + std::tuple extractHostPortPathFromUrl(const std::string& url) { + std::tuple result; + const size_t schemeSeparatorPos = url.find("://"); - const size_t hostStart = schemeSeparatorPos == std::string::npos ? 0 : schemeSeparatorPos + 3; - size_t hostEnd; - size_t portStart = etpUrl.find(":", hostStart); - size_t portEnd; + const size_t hostStart = schemeSeparatorPos == std::string::npos ? 0 : schemeSeparatorPos + 3; + size_t hostEnd; + size_t portStart = url.find(":", hostStart); + size_t portEnd; - if (portStart == std::string::npos) { - hostEnd = etpUrl.find("/", hostStart + 3); - portEnd = hostEnd; - port_ = etpUrl.find("wss://") == 0 ? 443 : 80; - } - else { - hostEnd = portStart++; - portEnd = etpUrl.find("/", portStart); - int readPort = stoi(etpUrl.substr(portStart, portEnd - portStart)); - if (readPort < 1 || readPort >(std::numeric_limits::max)()) { - throw std::out_of_range("The port " + std::to_string(readPort) + " is out of the allowed range for TCP ports ]0..2^16]"); + if (portStart == std::string::npos) { + hostEnd = url.find("/", hostStart + 3); + portEnd = hostEnd; + std::get<1>(result) = url.find("wss://") == 0 || url.find("https://") == 0 ? 443 : 80; + } + else { + hostEnd = portStart++; + portEnd = url.find("/", portStart); + int readPort = stoi(url.substr(portStart, portEnd - portStart)); + if (readPort < 1 || readPort >(std::numeric_limits::max)()) { + throw std::out_of_range("The port " + std::to_string(readPort) + " is out of the allowed range for TCP ports ]0..2^16]"); + } + std::get<1>(result) = static_cast(readPort); } - port_ = static_cast(readPort); - } - if (hostEnd == std::string::npos) { - urlPath_ = ""; - host_ = etpUrl.substr(hostStart); - } - else { - urlPath_ = portEnd < etpUrl.size() - 1 ? etpUrl.substr(portEnd + 1) : ""; - host_ = etpUrl.substr(hostStart, hostEnd - hostStart); + if (hostEnd == std::string::npos) { + std::get<2>(result) = ""; + std::get<0>(result) = url.substr(hostStart); + } + else { + std::get<2>(result) = portEnd < url.size() - 1 ? url.substr(portEnd + 1) : ""; + std::get<0>(result) = url.substr(hostStart, hostEnd - hostStart); + } + + return result; } } +void InitializationParameters::initFromUrl(const std::string& etpUrl, const std::string& proxyUrl) +{ + std::tuple serverInfo = extractHostPortPathFromUrl(etpUrl); + etpServerHost = std::get<0>(serverInfo); + etpServerPort = std::get<1>(serverInfo); + etpServerUrlPath = std::get<2>(serverInfo); + + serverInfo = extractHostPortPathFromUrl(proxyUrl); + proxyHost = std::get<0>(serverInfo); + proxyPort = std::get<1>(serverInfo); +} + std::map InitializationParameters::makeEndpointCapabilities() const { std::map result; Energistics::Etp::v12::Datatypes::DataValue value; - if (maxWebSocketMessagePayloadSize_ > 0) { - value.item.set_long(maxWebSocketMessagePayloadSize_); + if (maxWebSocketMessagePayloadSize > 0) { + value.item.set_long(maxWebSocketMessagePayloadSize); result["MaxWebSocketFramePayloadSize"] = value; result["MaxWebSocketMessagePayloadSize"] = value; } @@ -97,8 +114,6 @@ std::vector Initializatio supportedDataObject.qualifiedType = "eml20.obj_EpcExternalPartReference"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "eml21.PropertyKind"; - result.push_back(supportedDataObject); supportedDataObject.qualifiedType = "eml23.Activity"; result.push_back(supportedDataObject); supportedDataObject.qualifiedType = "eml23.ActivityTemplate"; @@ -109,29 +124,27 @@ std::vector Initializatio result.push_back(supportedDataObject); supportedDataObject.qualifiedType = "eml23.TimeSeries"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "eml23.EpcExternalPartReference"; - result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.Channel"; + supportedDataObject.qualifiedType = "witsml21.Channel"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.Trajectory"; + supportedDataObject.qualifiedType = "witsml21.Trajectory"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.Well"; + supportedDataObject.qualifiedType = "witsml21.Well"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.Wellbore"; + supportedDataObject.qualifiedType = "witsml21.Wellbore"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.WellboreCompletion"; + supportedDataObject.qualifiedType = "witsml21.WellboreCompletion"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.WellboreGeometry"; + supportedDataObject.qualifiedType = "witsml21.WellboreGeometry"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "witsml20.WellCompletion"; + supportedDataObject.qualifiedType = "witsml21.WellCompletion"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "prodml21.FluidCharacterization"; + supportedDataObject.qualifiedType = "prodml22.FluidCharacterization"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "prodml21.FluidSystem"; + supportedDataObject.qualifiedType = "prodml22.FluidSystem"; result.push_back(supportedDataObject); - supportedDataObject.qualifiedType = "prodml21.TimeSeriesData"; + supportedDataObject.qualifiedType = "prodml22.TimeSeriesData"; result.push_back(supportedDataObject); return result; diff --git a/src/etp/InitializationParameters.h b/src/etp/InitializationParameters.h index 215d534..54a9627 100644 --- a/src/etp/InitializationParameters.h +++ b/src/etp/InitializationParameters.h @@ -40,42 +40,71 @@ namespace ETP_NS class InitializationParameters { private: - InitializationParameters(const std::string & etpUrl); + void initFromUrl(const std::string& etpUrl, const std::string& proxyUrl); protected: - boost::uuids::uuid identifier_; - std::string host_; - uint16_t port_; - std::string urlPath_; + boost::uuids::uuid identifier; + std::string etpServerHost; + uint16_t etpServerPort; + std::string etpServerUrlPath; + std::string proxyHost; + uint16_t proxyPort; + std::map additionalHandshakeHeaderFields; + std::string additionalCertificates; + bool forceTls = false; - // Capabilities - // https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/using_websocket/messages.html - // and https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/read_message_max/overload1.html - int64_t maxWebSocketMessagePayloadSize_ = 16000000; + /** + * @param preferredFrameSize The preferred websocket frame payload to use by this client. + * If the ETP websocket server on the other end has a lower related MaxWebSocketFramePayloadSize capability, then the used websocket frame payload will be the server one. + * Default value corresponds to the default Boost.Beast value : https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/write_buffer_bytes/overload1.html. + */ + uint64_t preferredMaxFrameSize = 4096; + + /** + * Capabilities + * see https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/using_websocket/messages.html + * and https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/read_message_max/overload1.html + * + * The default value is 16000000 according to Boost.Beast. + */ + uint64_t maxWebSocketMessagePayloadSize = 16000000; public: /** - * @param instanceUuid The UUID of the client or server instance. - * @param etpUrl Must follow the syntax ws://:/ or wss://:/ or simply :/ + * @param instanceUuid The UUID of the client instance. + * @param etpServerUrl Must follow the syntax ws://:/ or wss://:/ or simply :/ * where port is optional and is defaulted to 80 if scheme is "ws" or if no scheme is provided. * In "wss" schema cases, port is defaulted to 443. + * @param proxyUrl The proxy URL. It must follow the syntax http://: or simply :. + * Leave it empty if your connection to eptServerUrl is direct and does not pass throughr any proxy. */ - FETPAPI_DLL_IMPORT_OR_EXPORT InitializationParameters(boost::uuids::uuid instanceUuid, const std::string & etpUrl) : - InitializationParameters(etpUrl) { - identifier_ = instanceUuid; + FETPAPI_DLL_IMPORT_OR_EXPORT InitializationParameters(boost::uuids::uuid instanceUuid, + const std::string& etpServerUrl, const std::string& proxyUrl = "") + { + initFromUrl(etpServerUrl, proxyUrl); + identifier = instanceUuid; } /** * Mainly for use with SWIG i.e. boost uuid structure is not easily portable whereas strings are. + * @param instanceUuid The UUID of the client instance. + * @param etpServerUrl Must follow the syntax ws://:/ or wss://:/ or simply :/ + * where port is optional and is defaulted to 80 if scheme is "ws" or if no scheme is provided. + * In "wss" schema cases, port is defaulted to 443. + * @param proxyUrl The proxy URL. It must follow the syntax http://: or simply :. + * Leave it empty if your connection to eptServerUrl is direct and does not pass throughr any proxy. */ - FETPAPI_DLL_IMPORT_OR_EXPORT InitializationParameters(const std::string& instanceUuid, const std::string & etpUrl) : - InitializationParameters(etpUrl) { + FETPAPI_DLL_IMPORT_OR_EXPORT InitializationParameters(const std::string& instanceUuid, + const std::string& etpServerUrl, const std::string& proxyUrl = "") + { + initFromUrl(etpServerUrl, proxyUrl); std::stringstream ss(instanceUuid); - ss >> identifier_; + ss >> identifier; } /** + * Only to be used for direct connection to the ETP server URL (not whenpassing through a proxy) * @param instanceUuid The UUID of the client or server instance. * @param host The fully qualified domain name of a network host, or its IP address as a set of four decimal digit groups separated by ".". * @param port The port number to connect to. @@ -83,9 +112,13 @@ namespace ETP_NS * It supplies the details of how the specified resource can be accessed. * Note that the "/" between the host (or port) and the url-path is NOT part of the url-path. */ - InitializationParameters(boost::uuids::uuid instanceUuid, const std::string & host, unsigned short port, const std::string & urlPath = "") : identifier_(instanceUuid), host_(host), port_(port), urlPath_(urlPath) {} + InitializationParameters(boost::uuids::uuid instanceUuid, + const std::string& host, uint16_t port, const std::string& urlPath = "") : + identifier(instanceUuid), etpServerHost(host), etpServerPort(port), etpServerUrlPath(urlPath) + {} /** + * Only to be used for direct connection to the ETP server URL (not whenpassing through a proxy) * Mainly for use with SWIG i.e. boost uuid structure is not easily portable whereas strings are. * * @param instanceUuid The UUID of the client or server instance. @@ -95,22 +128,41 @@ namespace ETP_NS * It supplies the details of how the specified resource can be accessed. * Note that the "/" between the host (or port) and the url-path is NOT part of the url-path. */ - InitializationParameters(const std::string & instanceUuid, const std::string & host, unsigned short port, const std::string & urlPath = "") : host_(host), port_(port), urlPath_(urlPath) { + InitializationParameters(const std::string & instanceUuid, + const std::string& host, uint16_t port, const std::string& urlPath = "") : + etpServerHost(host), etpServerPort(port), etpServerUrlPath(urlPath) + { std::stringstream ss(instanceUuid); - ss >> identifier_; + ss >> identifier; } + virtual ~InitializationParameters() = default; - /** - * The default value is 16000000 according to Boost.Beast. This method allows you to modify it. - */ - FETPAPI_DLL_IMPORT_OR_EXPORT void setMaxWebSocketMessagePayloadSize(int64_t value) { maxWebSocketMessagePayloadSize_ = value; } - int64_t getMaxWebSocketMessagePayloadSize() const { return maxWebSocketMessagePayloadSize_; } + FETPAPI_DLL_IMPORT_OR_EXPORT void setMaxWebSocketMessagePayloadSize(uint64_t value) { maxWebSocketMessagePayloadSize = value; } + FETPAPI_DLL_IMPORT_OR_EXPORT uint64_t getMaxWebSocketMessagePayloadSize() const { return maxWebSocketMessagePayloadSize; } + + FETPAPI_DLL_IMPORT_OR_EXPORT void setPreferredMaxFrameSize(uint64_t value) { preferredMaxFrameSize = value; } + FETPAPI_DLL_IMPORT_OR_EXPORT uint64_t getPreferredMaxFrameSize() const { return preferredMaxFrameSize; } + + FETPAPI_DLL_IMPORT_OR_EXPORT void setAdditionalHandshakeHeaderFields(const std::map& extraHandshakeHeaderFields) + { this->additionalHandshakeHeaderFields = extraHandshakeHeaderFields; } + FETPAPI_DLL_IMPORT_OR_EXPORT const std::map& getAdditionalHandshakeHeaderFields() const { return additionalHandshakeHeaderFields; } + + FETPAPI_DLL_IMPORT_OR_EXPORT void setAdditionalCertificates(const std::string& extraCertificates) { this->additionalCertificates = extraCertificates; } + FETPAPI_DLL_IMPORT_OR_EXPORT const std::string& getAdditionalCertificates() const { return additionalCertificates; } + + FETPAPI_DLL_IMPORT_OR_EXPORT const boost::uuids::uuid& getInstanceId() const { return identifier; } + + FETPAPI_DLL_IMPORT_OR_EXPORT const std::string& getEtpServerHost() const { return etpServerHost; } + FETPAPI_DLL_IMPORT_OR_EXPORT uint16_t getEtpServerPort() const { return etpServerPort; } + FETPAPI_DLL_IMPORT_OR_EXPORT const std::string& getEtpServerUrlPath() const { return etpServerUrlPath; } + + FETPAPI_DLL_IMPORT_OR_EXPORT const std::string& getProxyHost() const { return proxyHost; } + FETPAPI_DLL_IMPORT_OR_EXPORT uint16_t getProxyPort() const { return proxyPort; } + + FETPAPI_DLL_IMPORT_OR_EXPORT void setForceTls(bool force) { forceTls = force; } + FETPAPI_DLL_IMPORT_OR_EXPORT bool isTlsForced() { return forceTls; } - const boost::uuids::uuid& getInstanceId() const { return identifier_; } - const std::string& getHost() const { return host_; } - unsigned short getPort() const { return port_; } - const std::string& getUrlPath() const { return urlPath_; } FETPAPI_DLL_IMPORT_OR_EXPORT virtual std::string getApplicationName() const { return "F2I-CONSULTING ETP CLIENT"; } FETPAPI_DLL_IMPORT_OR_EXPORT virtual std::string getApplicationVersion() const { return "0.0"; } diff --git a/src/etp/PlainClientSession.cpp b/src/etp/PlainClientSession.cpp index 7cc4987..cf2b517 100644 --- a/src/etp/PlainClientSession.cpp +++ b/src/etp/PlainClientSession.cpp @@ -22,9 +22,9 @@ under the License. using namespace ETP_NS; PlainClientSession::PlainClientSession( - InitializationParameters* initializationParams, const std::string & target, const std::string & authorization, + InitializationParameters* initializationParams, const std::string & target, const std::string & authorization, const std::string& proxyAuthorization, const std::map& additionalHandshakeHeaderFields, std::size_t frameSize) : - AbstractClientSession(initializationParams, target, authorization), + AbstractClientSessionCRTP(initializationParams, target, authorization, proxyAuthorization), ws_(ioc) { ws_.binary(true); diff --git a/src/etp/PlainClientSession.h b/src/etp/PlainClientSession.h index 3fe2ccf..a98b6a8 100644 --- a/src/etp/PlainClientSession.h +++ b/src/etp/PlainClientSession.h @@ -18,20 +18,18 @@ under the License. -----------------------------------------------------------------------*/ #pragma once -#include "AbstractClientSession.h" +#include "AbstractClientSessionCRTP.h" namespace ETP_NS { - class PlainClientSession : public AbstractClientSession + class PlainClientSession : public AbstractClientSessionCRTP { public: - FETPAPI_DLL_IMPORT_OR_EXPORT PlainClientSession(): ws_(ioc) {} - /* * @param frameSize Sets the size of the write buffer used by the implementation to send frames : https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/write_buffer_bytes/overload1.html. */ FETPAPI_DLL_IMPORT_OR_EXPORT PlainClientSession( - InitializationParameters* initializationParams, const std::string & target, const std::string & authorization, + InitializationParameters* initializationParams, const std::string & target, const std::string & authorization, const std::string& proxyAuthorization = "", const std::map& additionalHandshakeHeaderFields = {}, std::size_t frameSize = 4096); virtual ~PlainClientSession() = default; @@ -39,6 +37,8 @@ namespace ETP_NS // Called by the base class FETPAPI_DLL_IMPORT_OR_EXPORT websocket::stream& ws() { return ws_; } + bool isTls() const final{ return false; } + void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) { if (ec) { @@ -51,8 +51,8 @@ namespace ETP_NS results.begin(), results.end(), std::bind( - &AbstractClientSession::on_connect, - std::static_pointer_cast(shared_from_this()), + &AbstractClientSessionCRTP::on_connect, + std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); } diff --git a/src/etp/ProtocolHandlers/CoreHandlers.cpp b/src/etp/ProtocolHandlers/CoreHandlers.cpp index 86f3475..2cfdf44 100644 --- a/src/etp/ProtocolHandlers/CoreHandlers.cpp +++ b/src/etp/ProtocolHandlers/CoreHandlers.cpp @@ -115,18 +115,18 @@ void CoreHandlers::on_CloseSession(const Energistics::Etp::v12::Protocol::Core:: void CoreHandlers::on_ProtocolException(const Energistics::Etp::v12::Protocol::Core::ProtocolException & pe, int64_t correlationId) { - session->fesapi_log("EXCEPTION received for message_id", correlationId); + std::cerr << "EXCEPTION received for message_id " << correlationId << std::endl; if (pe.error) { - session->fesapi_log("Single error code", pe.error.get().code, ":", pe.error.get().message); + std::cerr << "Single error code " << pe.error.get().code << " : " << pe.error.get().message << std::endl; } else { - session->fesapi_log("One or more error code :"); + std::cerr << "One or more error code : " << std::endl; for (const auto& error : pe.errors) { - session->fesapi_log("*************************************************"); - session->fesapi_log("Resource non received :"); - session->fesapi_log("key :", error.first); - session->fesapi_log("message :", error.second.message); - session->fesapi_log("code :", error.second.code); + std::cerr << "*************************************************" << std::endl; + std::cerr << "Resource non received : " << std::endl; + std::cerr << "key : " << error.first << std::endl; + std::cerr << "message : " << error.second.message << std::endl; + std::cerr << "code : " << error.second.code << std::endl; } } } diff --git a/src/etp/fesapi/FesapiHdfProxy.cpp b/src/etp/fesapi/FesapiHdfProxy.cpp index 5a908b0..8552963 100644 --- a/src/etp/fesapi/FesapiHdfProxy.cpp +++ b/src/etp/fesapi/FesapiHdfProxy.cpp @@ -64,7 +64,7 @@ Energistics::Etp::v12::Datatypes::DataArrayTypes::DataArrayMetadata FesapiHdfPro auto timeOut = session_->getTimeOut(); while (session_->isMessageStillProcessing(msgId)) { if (std::chrono::duration(std::chrono::high_resolution_clock::now() - t_start).count() > timeOut) { - throw std::runtime_error("Time out waiting for a response of message id " + std::to_string(msgId)); + throw std::runtime_error("Time out waiting for a response of GetDataArrayMetadata message id " + std::to_string(msgId)); } } diff --git a/src/etp/fesapi/FesapiHelpers.cpp b/src/etp/fesapi/FesapiHelpers.cpp index deb44e1..ddd761e 100644 --- a/src/etp/fesapi/FesapiHelpers.cpp +++ b/src/etp/fesapi/FesapiHelpers.cpp @@ -52,8 +52,14 @@ Energistics::Etp::v12::Datatypes::Object::DataObject ETP_NS::FesapiHelpers::buil { Energistics::Etp::v12::Datatypes::Object::DataObject result; if (includeSerialization) { + if (obj == nullptr) { + throw std::invalid_argument("Cannot build an ETP dataobject from a null object."); + } if (obj->isPartial()) { obj = obj->getRepository()->resolvePartial(obj); + if (obj == nullptr) { + throw std::invalid_argument("Cannot build an ETP dataobject from a partial object which is not resolvable"); + } } result.format = "xml"; result.data = obj->serializeIntoString(); diff --git a/src/etp/ssl/HttpsClientSession.h b/src/etp/ssl/HttpsClientSession.h index a1150d0..067ad1b 100644 --- a/src/etp/ssl/HttpsClientSession.h +++ b/src/etp/ssl/HttpsClientSession.h @@ -18,6 +18,8 @@ under the License. -----------------------------------------------------------------------*/ #pragma once +#include + #include #include #include @@ -25,7 +27,6 @@ under the License. #include #include #include -#include using tcp = boost::asio::ip::tcp; // from namespace ssl = boost::asio::ssl; // from @@ -40,7 +41,12 @@ namespace ETP_NS ssl::stream stream_; boost::beast::flat_buffer buffer_; // (Must persist between reads) http::request req_; + http::request proxyHandshake; http::response res_; + http::response proxyHandshakeResponse; + // use own response parser + // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body + http::response_parser http_proxy_handshake_parser; public: // Resolver and stream require an io_context @@ -48,21 +54,25 @@ namespace ETP_NS HttpsClientSession(boost::asio::io_context& ioc, ssl::context& ctx) : resolver_(ioc) , stream_(ioc, ctx) + , http_proxy_handshake_parser(proxyHandshakeResponse) { } // Start the asynchronous operation void run( - char const* host, - unsigned short port, - char const* target, + const std::string& etpServerHost, + uint16_t etpServerPort, + const std::string& etpServerTarget, int version, - std::string authorization = "") + std::string authorization = "", + const std::string& proxyHost = "", + uint16_t proxyPort = 80, + const std::string& proxyAuthorization = "") { - size_t hostSizeWithNullTermChar = strlen(host) + 1; + size_t hostSizeWithNullTermChar = etpServerHost.size() + 1; char* copyHost = new char[hostSizeWithNullTermChar]; - std::memcpy(copyHost, host, hostSizeWithNullTermChar); // Copy host because it must be non const in SSL_set_tlsext_host_name + std::memcpy(copyHost, etpServerHost.c_str(), hostSizeWithNullTermChar); // Copy host because it must be non const in SSL_set_tlsext_host_name // Set SNI Hostname (many hosts need this to handshake successfully) if (!SSL_set_tlsext_host_name(stream_.native_handle(), copyHost)) { @@ -76,17 +86,30 @@ namespace ETP_NS // Set up an HTTP GET request message req_.version(version); req_.method(http::verb::get); - req_.target(target); - req_.set(http::field::host, host); + req_.target(etpServerTarget); + req_.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort)); req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); if (!authorization.empty()) { req_.set(http::field::authorization, authorization); } + if (!proxyHost.empty()) { + proxyHandshake.version(version); + proxyHandshake.method(http::verb::connect); + proxyHandshake.target(etpServerHost + ':' + std::to_string(etpServerPort)); + proxyHandshake.set(http::field::host, etpServerHost + ':' + std::to_string(etpServerPort)); + if (!authorization.empty()) { + proxyHandshake.set(http::field::authorization, authorization); + } + if (!proxyAuthorization.empty()) { + proxyHandshake.set(http::field::proxy_authorization, proxyAuthorization); + } + } + // Look up the domain name resolver_.async_resolve( - host, - std::to_string(port).c_str(), + proxyHost.empty() ? etpServerHost : proxyHost, + std::to_string(proxyHost.empty() ? etpServerPort : proxyPort), std::bind( &HttpsClientSession::on_resolve, shared_from_this(), @@ -123,6 +146,72 @@ namespace ETP_NS return; } + if (proxyHandshake.count(http::field::host) > 0) { + // Send the handshake to the proxy + http::async_write(stream_.next_layer(), proxyHandshake, + std::bind( + &HttpsClientSession::on_proxy_handshake_write, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + else { + // Perform the SSL handshake + stream_.async_handshake( + ssl::stream_base::client, + std::bind( + &HttpsClientSession::on_handshake, + shared_from_this(), + std::placeholders::_1)); + } + } + + void + on_proxy_handshake_write( + boost::system::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if (ec) { + std::cerr << "Proxy handshake write : " << ec.message() << std::endl; + return; + } + + /** Set the skip parse option. + This option controls whether or not the parser expects to see an HTTP + body, regardless of the presence or absence of certain fields such as + Content-Length or a chunked Transfer-Encoding. Depending on the request, + some responses do not carry a body. For example, a 200 response to a + CONNECT request from a tunneling proxy, or a response to a HEAD request. + In these cases, callers may use this function inform the parser that + no body is expected. The parser will consider the message complete + after the header has been received. + @param v `true` to set the skip body option or `false` to disable it. + @note This function must called before any bytes are processed. + */ + http_proxy_handshake_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212 + + // Receive the HTTP response + http::async_read(stream_.next_layer(), buffer_, http_proxy_handshake_parser, + std::bind( + &HttpsClientSession::on_proxy_handshake_read, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + + void + on_proxy_handshake_read( + boost::system::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + if (ec) { + std::cerr << "read : " << ec.message() << std::endl; + return; + } + // Perform the SSL handshake stream_.async_handshake( ssl::stream_base::client, @@ -182,12 +271,17 @@ namespace ETP_NS return; } + // Force close + boost::system::error_code closeEc; + stream_.next_layer().close(closeEc); + /* // Gracefully close the stream_ stream_.async_shutdown( std::bind( &HttpsClientSession::on_shutdown, shared_from_this(), std::placeholders::_1)); + */ } void diff --git a/src/etp/ssl/SslClientSession.cpp b/src/etp/ssl/SslClientSession.cpp index 10ca091..f401dc3 100644 --- a/src/etp/ssl/SslClientSession.cpp +++ b/src/etp/ssl/SslClientSession.cpp @@ -16,15 +16,14 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -----------------------------------------------------------------------*/ - #include "SslClientSession.h" using namespace ETP_NS; SslClientSession::SslClientSession(boost::asio::ssl::context& ctx, - InitializationParameters* initializationParams, const std::string& target, const std::string& authorization, + InitializationParameters* initializationParams, const std::string& target, const std::string& authorization, const std::string& proxyAuthorization, const std::map& additionalHandshakeHeaderFields, std::size_t frameSize) - : AbstractClientSession(initializationParams, target, authorization), + : AbstractClientSessionCRTP(initializationParams, target, authorization, proxyAuthorization), ws_(ioc, ctx) { ws_.binary(true); diff --git a/src/etp/ssl/SslClientSession.h b/src/etp/ssl/SslClientSession.h index c125b1a..5387b8a 100644 --- a/src/etp/ssl/SslClientSession.h +++ b/src/etp/ssl/SslClientSession.h @@ -18,7 +18,7 @@ under the License. -----------------------------------------------------------------------*/ #pragma once -#include "../AbstractClientSession.h" +#include "../AbstractClientSessionCRTP.h" #include #if BOOST_VERSION < 106800 @@ -26,23 +26,31 @@ under the License. #elif BOOST_VERSION < 107000 #include #else +#include #include #include #endif +namespace http = boost::beast::http; // from + namespace ETP_NS { - class SslClientSession : public AbstractClientSession + class SslClientSession : public AbstractClientSessionCRTP { private: websocket::stream> ws_; + http::request proxyHandshake; + http::response proxyHandshakeResponse; + // use own response parser + // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body + http::response_parser http_proxy_handshake_parser; public: /* * @param frameSize Sets the size of the write buffer used by the implementation to send frames : https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/write_buffer_bytes/overload1.html. */ FETPAPI_DLL_IMPORT_OR_EXPORT SslClientSession(boost::asio::ssl::context& ctx, - InitializationParameters* initializationParams, const std::string& target, const std::string& authorization, + InitializationParameters* initializationParams, const std::string& target, const std::string& authorization, const std::string& proxyAuthorization = "", const std::map& additionalHandshakeHeaderFields = {}, std::size_t frameSize = 4096); virtual ~SslClientSession() {} @@ -50,6 +58,8 @@ namespace ETP_NS // Called by the base class FETPAPI_DLL_IMPORT_OR_EXPORT websocket::stream>& ws() { return ws_; } + bool isTls() const final { return true; } + void on_resolve(boost::system::error_code ec, tcp::resolver::results_type results) { if (ec) { @@ -72,12 +82,89 @@ namespace ETP_NS std::cerr << "on_ssl_connect : " << ec.message() << std::endl; } + if (!proxyHost.empty()) { + proxyHandshake.version(11); + proxyHandshake.method(http::verb::connect); + proxyHandshake.target(etpServerHost + ':' + etpServerPort); + proxyHandshake.set(http::field::host, etpServerHost + ':' + etpServerPort); + if (!etpServerAuthorization.empty()) { + proxyHandshake.set(http::field::authorization, etpServerAuthorization); + } + if (!proxyAuthorization.empty()) { + proxyHandshake.set(http::field::proxy_authorization, proxyAuthorization); + } + + // Send the handshake to the proxy + http::async_write(ws_.next_layer().next_layer(), proxyHandshake, + std::bind( + &SslClientSession::on_proxy_handshake_write, + std::static_pointer_cast(shared_from_this()), + std::placeholders::_1, + std::placeholders::_2)); + } + else { + // Perform the SSL handshake + ws_.next_layer().async_handshake( + boost::asio::ssl::stream_base::client, + std::bind( + &AbstractClientSessionCRTP::on_connect, + std::static_pointer_cast(shared_from_this()), + std::placeholders::_1)); + } + } + + void + on_proxy_handshake_write( + boost::system::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if (ec) { + std::cerr << "Proxy handshake write : " << ec.message() << std::endl; + return; + } + + /** Set the skip parse option. + This option controls whether or not the parser expects to see an HTTP + body, regardless of the presence or absence of certain fields such as + Content-Length or a chunked Transfer-Encoding. Depending on the request, + some responses do not carry a body. For example, a 200 response to a + CONNECT request from a tunneling proxy, or a response to a HEAD request. + In these cases, callers may use this function inform the parser that + no body is expected. The parser will consider the message complete + after the header has been received. + @param v `true` to set the skip body option or `false` to disable it. + @note This function must called before any bytes are processed. + */ + http_proxy_handshake_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212 + + // Receive the HTTP response + http::async_read(ws_.next_layer().next_layer(), receivedBuffer, http_proxy_handshake_parser, + std::bind( + &SslClientSession::on_proxy_handshake_read, + std::static_pointer_cast(shared_from_this()), + std::placeholders::_1, + std::placeholders::_2)); + } + + void + on_proxy_handshake_read( + boost::system::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + if (ec) { + std::cerr << "read : " << ec.message() << std::endl; + return; + } + // Perform the SSL handshake ws_.next_layer().async_handshake( boost::asio::ssl::stream_base::client, std::bind( - &AbstractClientSession::on_connect, - std::static_pointer_cast(shared_from_this()), + &AbstractClientSessionCRTP::on_connect, + std::static_pointer_cast(shared_from_this()), std::placeholders::_1)); } };