├── .gitignore ├── Beast └── CMakeLists.txt ├── CMakeLists.txt ├── LICENSE_1_0.txt ├── README.md ├── cmake ├── DownloadProject.CMakeLists.cmake.in └── DownloadProject.cmake ├── cryptopp └── CMakeLists.txt ├── date └── CMakeLists.txt ├── pusher++ ├── CMakeLists.txt ├── examples │ ├── CMakeLists.txt │ ├── README.md │ ├── client.cpp │ └── server.cpp ├── include │ └── pusher++ │ │ ├── channel_proxy.hpp │ │ ├── client.hpp │ │ ├── detail │ │ ├── client │ │ │ ├── read.hpp │ │ │ ├── signal_filter.hpp │ │ │ └── write.hpp │ │ └── server │ │ │ ├── crypto.hpp │ │ │ └── write.hpp │ │ ├── event.hpp │ │ └── server.hpp └── test │ ├── CMakeLists.txt │ ├── test.cpp │ ├── test.hpp │ ├── test_coroutine.cpp │ └── test_signal_filter.cpp └── rapidjson └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /Beast/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | include(DownloadProject) 7 | download_project( 8 | PROJ Beast 9 | GIT_REPOSITORY https://github.com/boostorg/beast.git 10 | GIT_TAG ${BEAST_GIT_TAG} 11 | UPDATE_DISCONNECTED 1 12 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Beast 13 | ) 14 | 15 | add_library(Beast INTERFACE) 16 | target_include_directories(Beast INTERFACE 17 | ${Beast_SOURCE_DIR}/include 18 | ${Beast_SOURCE_DIR}/extras 19 | ) 20 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | cmake_minimum_required(VERSION 3.7.2) 7 | project(pusher++ CXX) 8 | 9 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 10 | set(CMAKE_CXX_EXTENSIONS OFF) 11 | 12 | set(BEAST_GIT_TAG 0bc2a41) # 121 13 | set(CRYPTOPP_GIT_TAG CRYPTOPP_5_6_5) 14 | set(DATE_GIT_TAG v2.2) 15 | set(RAPIDJSON_GIT_TAG v1.1.0) 16 | 17 | add_subdirectory(cryptopp) 18 | add_subdirectory(date) 19 | add_subdirectory(rapidjson) 20 | add_subdirectory(Beast) 21 | add_subdirectory(pusher++) 22 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pusher client and server built on Boost.Asio 2 | 3 | ## Contents 4 | 5 | - [Introduction](#introduction) 6 | - [Features](#features) 7 | - [Usage](#usage) 8 | - [Requirements](#requirements) 9 | - [Status](#status) 10 | - [Licence](#licence) 11 | - [Contact](#contact) 12 | 13 | ## Introduction 14 | 15 | Pusher++ is a header-only cross-platform C++ library for connecting to [Pusher](https://pusher.com). It's built on top of Boost.Asio via Boost.Beast for HTTP and Websocket support. 16 | 17 | ## Features 18 | 19 | * **Performance.** Supports both synchronous and asynchronous APIs. 20 | * **SSL support.** Connect securely to endpoints at Pusher and bypass problematic proxies. 21 | * **Symmetric API** Client and Server share the same API. 22 | 23 | ## Usage 24 | 25 | These examples are complete, but you will have to set your application specific credentials. The [examples](pusher++/examples) directory contains examples that parse the command line. 26 | 27 | Example Server: 28 | 29 | ```C++ 30 | #include 31 | #include 32 | 33 | int main() 34 | { 35 | std::string const app_id = "Your App ID"; 36 | std::string const secret = "Your Secret"; 37 | std::string const key = "Your Key"; 38 | 39 | boost::asio::io_service ios; 40 | pusher::server server{ios, app_id, key, secret}; 41 | server.connect(); 42 | 43 | std::string input; 44 | while(input != "bye" && std::cout << "\nMessage: " && std::getline(std::cin, input)) 45 | { 46 | std::cout << server.trigger("test_channel", "test_name", input); 47 | } 48 | ios.run(); 49 | } 50 | ``` 51 | 52 | Example Client: 53 | 54 | ```C++ 55 | #include 56 | #include 57 | 58 | int main(int argc, char* argv[]) 59 | { 60 | std::string const key = "Your Key"; 61 | 62 | boost::asio::io_service ios; 63 | pusher::client client{ios, key}; 64 | client.connect(); 65 | 66 | client.bind_all([](pusher::event const& event) 67 | { 68 | std::cout << event.data << '\n'; 69 | }); 70 | 71 | auto test_channel = client.channel("test_channel"); 72 | test_channel.bind("test_name", [&client](pusher::event const& event) 73 | { 74 | if(event.data == "bye") 75 | client.disconnect(); 76 | }); 77 | ios.run(); 78 | } 79 | 80 | ``` 81 | 82 | ## Requirements 83 | 84 | * C++14 85 | * [Beast](https://boostorg/beast) >= 121 86 | * [Boost](http://www.boost.org) >= 1.63 87 | * [RapidJSON](https://github.com/miloyip/rapidjson/) >= v1.1.0 88 | * [Crypto++](https://github.com/weidai11/cryptopp) >= 5.6.5 (server) 89 | * [OpenSSL](https://www.openssl.org/) = 1.0.2 (optional) 90 | 91 | ## Build 92 | 93 | This is a header only library, no building is required. 94 | 95 | However, a cmake build system is in place for tests and examples. Currently, it is assumed that the environment is the environment is linux-ish and that Boost and OpenSSL headers are installed; other dependencies are managed by cmake. Building on Windows should work, but is untested and will likely require some minor changes to find Boost and OpenSSL. 96 | 97 | ``` 98 | mkdir -p build && cd build && cmake .. && make -j 99 | ``` 100 | 101 | ## Status 102 | 103 | Caveat emptor. This is very early work; it's incomplete and has not been used in anger. Everything is subject to change, but it may not be completed or even maintained. 104 | 105 | However, you are free to use it under the terms set out below. 106 | 107 | ## Licence 108 | 109 | Distributed under the Boost Software License, Version 1.0. (See accompanying file [LICENSE_1_0.txt](LICENSE_1_0.txt) or copy at [http://www.boost.org/LICENSE_1_0.txt](http://www.boost.org/LICENSE_1_0.txt)) 110 | 111 | ## Contact 112 | 113 | Please report issues or questions here: 114 | https://github.com/BenPope/pusher-cpp/issues 115 | -------------------------------------------------------------------------------- /cmake/DownloadProject.CMakeLists.cmake.in: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.2) 2 | 3 | project(${DL_ARGS_PROJ}-download NONE) 4 | 5 | include(ExternalProject) 6 | ExternalProject_Add(${DL_ARGS_PROJ}-download 7 | ${DL_ARGS_UNPARSED_ARGUMENTS} 8 | SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" 9 | BINARY_DIR "${DL_ARGS_BINARY_DIR}" 10 | CONFIGURE_COMMAND "" 11 | BUILD_COMMAND "" 12 | INSTALL_COMMAND "" 13 | TEST_COMMAND "" 14 | ) 15 | -------------------------------------------------------------------------------- /cmake/DownloadProject.cmake: -------------------------------------------------------------------------------- 1 | # MODULE: DownloadProject 2 | # 3 | # PROVIDES: 4 | # download_project( PROJ projectName 5 | # [PREFIX prefixDir] 6 | # [DOWNLOAD_DIR downloadDir] 7 | # [SOURCE_DIR srcDir] 8 | # [BINARY_DIR binDir] 9 | # [QUIET] 10 | # ... 11 | # ) 12 | # 13 | # Provides the ability to download and unpack a tarball, zip file, git repository, 14 | # etc. at configure time (i.e. when the cmake command is run). How the downloaded 15 | # and unpacked contents are used is up to the caller, but the motivating case is 16 | # to download source code which can then be included directly in the build with 17 | # add_subdirectory() after the call to download_project(). Source and build 18 | # directories are set up with this in mind. 19 | # 20 | # The PROJ argument is required. The projectName value will be used to construct 21 | # the following variables upon exit (obviously replace projectName with its actual 22 | # value): 23 | # 24 | # projectName_SOURCE_DIR 25 | # projectName_BINARY_DIR 26 | # 27 | # The SOURCE_DIR and BINARY_DIR arguments are optional and would not typically 28 | # need to be provided. They can be specified if you want the downloaded source 29 | # and build directories to be located in a specific place. The contents of 30 | # projectName_SOURCE_DIR and projectName_BINARY_DIR will be populated with the 31 | # locations used whether you provide SOURCE_DIR/BINARY_DIR or not. 32 | # 33 | # The DOWNLOAD_DIR argument does not normally need to be set. It controls the 34 | # location of the temporary CMake build used to perform the download. 35 | # 36 | # The PREFIX argument can be provided to change the base location of the default 37 | # values of DOWNLOAD_DIR, SOURCE_DIR and BINARY_DIR. If all of those three arguments 38 | # are provided, then PREFIX will have no effect. The default value for PREFIX is 39 | # CMAKE_BINARY_DIR. 40 | # 41 | # The QUIET option can be given if you do not want to show the output associated 42 | # with downloading the specified project. 43 | # 44 | # In addition to the above, any other options are passed through unmodified to 45 | # ExternalProject_Add() to perform the actual download, patch and update steps. 46 | # The following ExternalProject_Add() options are explicitly prohibited (they 47 | # are reserved for use by the download_project() command): 48 | # 49 | # CONFIGURE_COMMAND 50 | # BUILD_COMMAND 51 | # INSTALL_COMMAND 52 | # TEST_COMMAND 53 | # 54 | # Only those ExternalProject_Add() arguments which relate to downloading, patching 55 | # and updating of the project sources are intended to be used. Also note that at 56 | # least one set of download-related arguments are required. 57 | # 58 | # If using CMake 3.2 or later, the UPDATE_DISCONNECTED option can be used to 59 | # prevent a check at the remote end for changes every time CMake is run 60 | # after the first successful download. See the documentation of the ExternalProject 61 | # module for more information. It is likely you will want to use this option if it 62 | # is available to you. Note, however, that the ExternalProject implementation contains 63 | # bugs which result in incorrect handling of the UPDATE_DISCONNECTED option when 64 | # using the URL download method or when specifying a SOURCE_DIR with no download 65 | # method. Fixes for these have been created, the last of which is scheduled for 66 | # inclusion in CMake 3.8.0. Details can be found here: 67 | # 68 | # https://gitlab.kitware.com/cmake/cmake/commit/bdca68388bd57f8302d3c1d83d691034b7ffa70c 69 | # https://gitlab.kitware.com/cmake/cmake/issues/16428 70 | # 71 | # If you experience build errors related to the update step, consider avoiding 72 | # the use of UPDATE_DISCONNECTED. 73 | # 74 | # EXAMPLE USAGE: 75 | # 76 | # include(download_project.cmake) 77 | # download_project(PROJ googletest 78 | # GIT_REPOSITORY https://github.com/google/googletest.git 79 | # GIT_TAG master 80 | # UPDATE_DISCONNECTED 1 81 | # QUIET 82 | # ) 83 | # 84 | # add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 85 | # 86 | #======================================================================================== 87 | 88 | 89 | set(_DownloadProjectDir "${CMAKE_CURRENT_LIST_DIR}") 90 | 91 | include(CMakeParseArguments) 92 | 93 | function(download_project) 94 | 95 | set(options QUIET) 96 | set(oneValueArgs 97 | PROJ 98 | PREFIX 99 | DOWNLOAD_DIR 100 | SOURCE_DIR 101 | BINARY_DIR 102 | # Prevent the following from being passed through 103 | CONFIGURE_COMMAND 104 | BUILD_COMMAND 105 | INSTALL_COMMAND 106 | TEST_COMMAND 107 | ) 108 | set(multiValueArgs "") 109 | 110 | cmake_parse_arguments(DL_ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 111 | 112 | # Hide output if requested 113 | if (DL_ARGS_QUIET) 114 | set(OUTPUT_QUIET "OUTPUT_QUIET") 115 | else() 116 | unset(OUTPUT_QUIET) 117 | message(STATUS "Downloading/updating ${DL_ARGS_PROJ}") 118 | endif() 119 | 120 | # Set up where we will put our temporary CMakeLists.txt file and also 121 | # the base point below which the default source and binary dirs will be 122 | if (NOT DL_ARGS_PREFIX) 123 | set(DL_ARGS_PREFIX "${CMAKE_BINARY_DIR}") 124 | endif() 125 | if (NOT DL_ARGS_DOWNLOAD_DIR) 126 | set(DL_ARGS_DOWNLOAD_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-download") 127 | endif() 128 | 129 | # Ensure the caller can know where to find the source and build directories 130 | if (NOT DL_ARGS_SOURCE_DIR) 131 | set(DL_ARGS_SOURCE_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-src") 132 | endif() 133 | if (NOT DL_ARGS_BINARY_DIR) 134 | set(DL_ARGS_BINARY_DIR "${DL_ARGS_PREFIX}/${DL_ARGS_PROJ}-build") 135 | endif() 136 | set(${DL_ARGS_PROJ}_SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" PARENT_SCOPE) 137 | set(${DL_ARGS_PROJ}_BINARY_DIR "${DL_ARGS_BINARY_DIR}" PARENT_SCOPE) 138 | 139 | # Create and build a separate CMake project to carry out the download. 140 | # If we've already previously done these steps, they will not cause 141 | # anything to be updated, so extra rebuilds of the project won't occur. 142 | configure_file("${_DownloadProjectDir}/DownloadProject.CMakeLists.cmake.in" 143 | "${DL_ARGS_DOWNLOAD_DIR}/CMakeLists.txt") 144 | execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . 145 | RESULT_VARIABLE result 146 | ${OUTPUT_QUIET} 147 | WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" 148 | ) 149 | if(result) 150 | message(FATAL_ERROR "CMake step for ${DL_ARGS_PROJ} failed: ${result}") 151 | endif() 152 | execute_process(COMMAND ${CMAKE_COMMAND} --build . 153 | RESULT_VARIABLE result 154 | ${OUTPUT_QUIET} 155 | WORKING_DIRECTORY "${DL_ARGS_DOWNLOAD_DIR}" 156 | ) 157 | if(result) 158 | message(FATAL_ERROR "Build step for ${DL_ARGS_PROJ} failed: ${result}") 159 | endif() 160 | 161 | endfunction() 162 | -------------------------------------------------------------------------------- /cryptopp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | include(DownloadProject) 7 | download_project( 8 | PROJ cryptopp 9 | GIT_REPOSITORY https://github.com/weidai11/cryptopp.git 10 | GIT_TAG ${CRYPTOPP_GIT_TAG} 11 | UPDATE_DISCONNECTED 1 12 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/crypto++ 13 | ) 14 | 15 | add_library(cryptopp INTERFACE) 16 | target_include_directories(cryptopp INTERFACE 17 | ${cryptopp_SOURCE_DIR}/.. 18 | ) 19 | target_compile_definitions(cryptopp INTERFACE 20 | -DCRYPTOPP_ENABLE_NAMESPACE_WEAK=1 # for MD5 21 | ) 22 | 23 | set(BUILD_SHARED OFF) 24 | set(BUILD_TESTING OFF) 25 | 26 | add_subdirectory(crypto++) 27 | 28 | target_link_libraries(cryptopp INTERFACE cryptopp-static) 29 | -------------------------------------------------------------------------------- /date/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | include(DownloadProject) 7 | download_project( 8 | PROJ date 9 | GIT_REPOSITORY https://github.com/HowardHinnant/date.git 10 | GIT_TAG ${DATE_GIT_TAG} 11 | UPDATE_DISCONNECTED 1 12 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/date 13 | ) 14 | 15 | add_library(date INTERFACE) 16 | target_include_directories(date INTERFACE 17 | ${date_SOURCE_DIR}/.. 18 | ) 19 | 20 | add_library(tz 21 | ${date_SOURCE_DIR}/tz.cpp 22 | ) 23 | target_compile_features(tz PUBLIC 24 | cxx_lambda_init_captures # forces C++14 25 | ) 26 | target_link_libraries(tz INTERFACE 27 | date 28 | ) 29 | -------------------------------------------------------------------------------- /pusher++/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | cmake_minimum_required(VERSION 3.3) 7 | 8 | find_package(Boost REQUIRED COMPONENTS system) 9 | 10 | add_library(pusher++ INTERFACE) 11 | target_sources(pusher++ INTERFACE 12 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/client.hpp 13 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/channel_proxy.hpp 14 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/event.hpp 15 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/server.hpp 16 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/detail/client/read.hpp 17 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/detail/client/signal_filter.hpp 18 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/detail/client/write.hpp 19 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/detail/server/crypto.hpp 20 | ${CMAKE_CURRENT_SOURCE_DIR}/include/pusher++/detail/server/write.hpp 21 | ) 22 | target_include_directories(pusher++ INTERFACE 23 | ${Boost_INCLUDE_DIRS} 24 | ${PROJECT_SOURCE_DIR}/pusher++/include 25 | ) 26 | target_compile_features(pusher++ INTERFACE 27 | cxx_lambda_init_captures # forces C++14 28 | ) 29 | target_link_libraries(pusher++ INTERFACE 30 | Beast 31 | ${Boost_LIBRARIES} 32 | rapidjson 33 | cryptopp 34 | ) 35 | 36 | add_subdirectory(examples) 37 | add_subdirectory(test) 38 | -------------------------------------------------------------------------------- /pusher++/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | find_package(Boost REQUIRED COMPONENTS program_options coroutine system) 7 | find_package(CURL REQUIRED) 8 | 9 | set(common_link_libraries 10 | pusher++ 11 | ${Boost_LIBRARIES} 12 | tz 13 | ${CURL_LIBRARIES} 14 | cryptopp 15 | pthread 16 | ) 17 | 18 | add_executable(${PROJECT_NAME}-server server.cpp) 19 | target_link_libraries(${PROJECT_NAME}-server ${common_link_libraries}) 20 | 21 | add_executable(${PROJECT_NAME}-client client.cpp) 22 | target_link_libraries(${PROJECT_NAME}-client ${common_link_libraries}) 23 | -------------------------------------------------------------------------------- /pusher++/examples/README.md: -------------------------------------------------------------------------------- 1 | # Pusher client and server built on Boost.Asio 2 | 3 | ## Examples 4 | 5 | The examples are a simple client and server designed to talk to each other. They should work by just specifying your application credentials from the [Pusher Dashboard](https://dashboard.pusher.com/) 6 | 7 | The server will send messages you type to the given `channel` and `event`; the client will receive them. Sending `bye` will terminate both. 8 | 9 | Running the examples without arguments will list the options. Be sure to set the cluster if it is not `mt1`. 10 | 11 | It may be possible to use the client to access existing public endpoints, but customisation is recommended. 12 | -------------------------------------------------------------------------------- /pusher++/examples/client.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace 12 | { 13 | boost::program_options::variables_map parse_options(int argc, char* argv[]) 14 | { 15 | boost::program_options::options_description desc{"Options"}; 16 | desc.add_options() 17 | ("key", boost::program_options::value(), "Application Key") 18 | ("cluster", boost::program_options::value()->default_value("mt1"), "Cluster: [ap1|ap2|eu|us2|mt1]") 19 | ("channel", boost::program_options::value()->default_value("my-channel"), "Channel to subscribe to") 20 | ("event", boost::program_options::value()->default_value("my-event"), "Event to subscribe to") 21 | ; 22 | boost::program_options::variables_map vm; 23 | boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); 24 | boost::program_options::notify(vm); 25 | 26 | if(!vm.count("key")) 27 | { 28 | std::cout << desc << '\n'; 29 | std::exit(1); 30 | } 31 | 32 | return vm; 33 | } 34 | 35 | std::ostream& operator<<(std::ostream& os, pusher::event ev) 36 | { 37 | return os 38 | << date::make_zoned(date::current_zone(), std::chrono::system_clock::now()) << ": " 39 | << "{\"channel\":\"" << ev.channel 40 | << "\",\"event\":\"" << ev.name 41 | << "\",\"data\":\"" << ev.data << "\"}\n"; 42 | } 43 | 44 | auto logger = [](pusher::event const& ev) 45 | { 46 | std::cout << ev; 47 | }; 48 | } 49 | 50 | int main(int argc, char* argv[]) 51 | { 52 | auto options = parse_options(argc, argv); 53 | 54 | auto const key = options["key"].as(); 55 | auto const cluster = options["cluster"].as(); 56 | auto const channel_name = options["channel"].as(); 57 | auto const event_name = options["event"].as(); 58 | 59 | boost::asio::io_service ios; 60 | pusher::client client{ios, key, cluster}; 61 | client.connect(); 62 | client.bind_all(logger); 63 | 64 | auto channel = client.channel(channel_name); 65 | channel.bind(event_name, [&client](pusher::event const& event) 66 | { 67 | if(event.data == "bye") 68 | client.disconnect(); 69 | }); 70 | ios.run(); 71 | } 72 | -------------------------------------------------------------------------------- /pusher++/examples/server.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace 11 | { 12 | boost::program_options::variables_map parse_options(int argc, char* argv[]) 13 | { 14 | boost::program_options::options_description desc{"Options"}; 15 | desc.add_options() 16 | ("app_id", boost::program_options::value(), "Application ID") 17 | ("key", boost::program_options::value(), "Application Key") 18 | ("secret", boost::program_options::value(), "Secret") 19 | ("cluster", boost::program_options::value()->default_value("mt1"), "Cluster: [ap1|ap2|eu|us2]mt1]") 20 | ("channel", boost::program_options::value()->default_value("my-channel"), "Channel to publish to") 21 | ("event", boost::program_options::value()->default_value("my-event"), "Event to publish to") 22 | ; 23 | boost::program_options::variables_map vm; 24 | boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); 25 | boost::program_options::notify(vm); 26 | 27 | if(!vm.count("app_id") || !vm.count("key") || !vm.count("secret")) 28 | { 29 | std::cout << desc << '\n'; 30 | std::exit(1); 31 | } 32 | 33 | return vm; 34 | } 35 | } 36 | 37 | int main(int argc, char* argv[]) 38 | { 39 | auto options = parse_options(argc, argv); 40 | 41 | auto const app_id = options["app_id"].as();; 42 | auto const secret = options["secret"].as(); 43 | auto const key = options["key"].as(); 44 | auto const cluster = options["cluster"].as(); 45 | auto const channel_name = options["channel"].as(); 46 | auto const event_name = options["event"].as(); 47 | 48 | boost::asio::io_service ios; 49 | pusher::server server{ios, app_id, key, secret, cluster}; 50 | server.connect(); 51 | 52 | std::string input; 53 | while(input != "bye" && std::cout << "\nMessage: " && std::getline(std::cin, input)) 54 | { 55 | std::cout << server.trigger(channel_name, event_name, input); 56 | } 57 | std::cout << '\n'; 58 | ios.run(); 59 | } 60 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/channel_proxy.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_CHANNEL_PROXY_HPP 7 | #define PUSHERPP_CHANNEL_PROXY_HPP 8 | 9 | #include "detail/client/signal_filter.hpp" 10 | 11 | namespace pusher 12 | { 13 | class channel_proxy 14 | { 15 | using signal_filter = detail::client::signal_filter; 16 | signal_filter* signal_filter_; 17 | public: 18 | explicit channel_proxy(signal_filter* signal_filter) : signal_filter_{signal_filter} 19 | {} 20 | 21 | template 22 | auto bind(std::string const& event_name, FuncT&& func) 23 | { 24 | return signal_filter_->connect(event_name, std::forward(func)); 25 | } 26 | 27 | template 28 | auto bind_all(FuncT&& func) 29 | { 30 | return signal_filter_->connect(std::forward(func)); 31 | } 32 | }; 33 | } 34 | 35 | #endif // PUSHERPP_CHANNEL_PROXY_HPP 36 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/client.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_CLIENT_HPP 7 | #define PUSHERPP_CLIENT_HPP 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "event.hpp" 22 | #include "channel_proxy.hpp" 23 | #include "detail/client/read.hpp" 24 | #include "detail/client/signal_filter.hpp" 25 | #include "detail/client/write.hpp" 26 | 27 | namespace pusher 28 | { 29 | template 30 | class client 31 | { 32 | using signal_filter = detail::client::signal_filter; 33 | boost::beast::websocket::stream socket_; 34 | boost::asio::ip::tcp::resolver resolver_; 35 | std::string host_; 36 | std::string handshake_resource_; 37 | boost::beast::flat_buffer read_buf_; 38 | detail::client::signal events_; 39 | signal_filter filtered_channels_; 40 | std::map channels_; 41 | signal_filter filtered_events_; 42 | 43 | public: 44 | client(boost::asio::io_service& ios, std::string key, std::string cluster = "mt1") 45 | : socket_{ios} 46 | , resolver_{ios} 47 | , host_{"ws-" + std::move(cluster) + ".pusher.com"} 48 | , handshake_resource_{"/app/" + std::move(key) + "?client=pusher++&version=0.01&protocol=7"} 49 | , events_{} 50 | , filtered_channels_{detail::client::filtered_signal(&detail::client::by_channel)} 51 | , filtered_events_{detail::client::filtered_signal(&detail::client::by_name)} 52 | {} 53 | 54 | void initialise() 55 | { 56 | filtered_channels_.connect_source(events_); 57 | filtered_events_.connect_source(events_); 58 | } 59 | 60 | template 61 | auto async_connect(TokenT&& token) 62 | { 63 | initialise(); 64 | 65 | typename boost::asio::handler_type::type handler(std::forward(token)); 66 | boost::asio::async_result result(handler); 67 | using query = boost::asio::ip::tcp::resolver::query; 68 | resolver_.async_resolve(query{host_, "http"}, [this, handler](auto ec, auto endpoint) mutable 69 | { 70 | if(ec) 71 | return handler(ec); 72 | 73 | boost::asio::async_connect(socket_.next_layer(), endpoint, [this, handler](auto ec, auto) mutable 74 | { 75 | if(ec) 76 | return handler(ec); 77 | 78 | socket_.async_handshake(host_, handshake_resource_, [this, handler](auto ec) mutable 79 | { 80 | if(ec) 81 | return handler(ec); 82 | 83 | this->read_impl(); 84 | return handler(ec); 85 | }); 86 | }); 87 | }); 88 | return result.get(); 89 | } 90 | 91 | auto connect() 92 | { 93 | initialise(); 94 | 95 | boost::asio::connect(socket_.next_layer(), resolver_.resolve(boost::asio::ip::tcp::resolver::query{host_, "80"})); 96 | socket_.handshake(host_, handshake_resource_); 97 | 98 | read_impl(); 99 | } 100 | 101 | void disconnect() 102 | { 103 | resolver_.cancel(); 104 | socket_.close(boost::beast::websocket::close_code::normal); 105 | } 106 | 107 | auto channel(std::string const& name) 108 | { 109 | auto channel_result = filtered_channels_.filtered_.emplace(name, detail::client::signal{}); 110 | auto& channel = channel_result.first->second; 111 | bool inserted = channel_result.second; 112 | 113 | auto result = channels_.emplace(name, detail::client::filtered_signal(&detail::client::by_name)); 114 | result.first->second.connect_source(channel); 115 | 116 | if(inserted) 117 | subscribe(name); 118 | 119 | return channel_proxy(&(result.first->second)); 120 | } 121 | 122 | template 123 | auto bind_all(FuncT&& func) 124 | { 125 | return filtered_events_.connect(std::forward(func)); 126 | } 127 | 128 | template 129 | auto bind(std::string const& event_name, FuncT&& func) 130 | { 131 | return filtered_events_.connect(event_name, std::forward(func)); 132 | } 133 | 134 | private: 135 | auto subscribe(std::string const& channel) 136 | { 137 | socket_.write(boost::asio::buffer(detail::client::make_subscription(channel))); 138 | } 139 | 140 | void read_impl() 141 | { 142 | return socket_.async_read(read_buf_, [this](auto ec, std::size_t bytes_written) 143 | { 144 | if(ec) 145 | return; 146 | 147 | events_(detail::client::make_event(read_buf_)); 148 | read_buf_.consume(read_buf_.size()); 149 | 150 | this->read_impl(); 151 | }); 152 | } 153 | }; 154 | } 155 | 156 | #endif // PUSHERPP_CLIENT_HPP 157 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/detail/client/read.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_DETAIL_CLIENT_READ_HPP 7 | #define PUSHERPP_DETAIL_CLIENT_READ_HPP 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace pusher { namespace detail { namespace client 21 | { 22 | template 23 | std::string stringify(ValueT const& value) 24 | { 25 | if (value.IsString()) 26 | return value.GetString(); 27 | 28 | rapidjson::StringBuffer buffer; 29 | rapidjson::Writer writer(buffer); 30 | if(!value.Accept(writer)) 31 | return std::string(); 32 | 33 | auto begin = buffer.GetString(); 34 | auto size = buffer.GetSize(); 35 | return std::string(begin, size); 36 | } 37 | 38 | inline event make_event(boost::beast::flat_buffer const& buf) 39 | { 40 | rapidjson::Document d; 41 | 42 | d.Parse(&*boost::asio::buffers_begin(buf.data()), buf.size()); 43 | event ev{}; 44 | if(d.HasMember("channel")) 45 | ev.channel = d["channel"].GetString(); 46 | if(d.HasMember("event")) 47 | ev.name = d["event"].GetString(); 48 | if(d.HasMember("data")) 49 | ev.data = stringify(d["data"]); 50 | ev.timestamp = detail::clock::now(); 51 | return ev; 52 | } 53 | }}} // pusher::detail::client 54 | 55 | #endif // PUSHERPP_DETAIL_CLIENT_READ_HPP 56 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/detail/client/signal_filter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_DETAIL_CLIENT_SIGNAL_FILTER_HPP 7 | #define PUSHERPP_DETAIL_CLIENT_SIGNAL_FILTER_HPP 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace pusher { namespace detail { namespace client 18 | { 19 | // Thread safe signals are not required 20 | using signal_mutex = boost::signals2::keywords::mutex_type; 21 | using signal = boost::signals2::signal_type::type; 22 | 23 | using signal_map = std::map; 24 | using scoped_connection = boost::signals2::scoped_connection; 25 | 26 | template 27 | class signal_filter 28 | { 29 | public: 30 | signal* source_; 31 | std::decay_t filter_; 32 | signal_map filtered_; 33 | 34 | explicit signal_filter(FilterT&& filter) 35 | : source_{nullptr} 36 | , filter_{std::forward(filter)} 37 | , filtered_{} 38 | {} 39 | 40 | auto connect_source(signal& source) 41 | { 42 | source_ = &source; 43 | source_->connect([this](event const& ev) 44 | { 45 | auto name = filter_(ev); 46 | if(!name.empty()) 47 | { 48 | auto it = filtered_.find(name); 49 | if(it != std::end(filtered_)) 50 | it->second(ev); 51 | } 52 | }); 53 | } 54 | 55 | template 56 | auto connect(FuncT&& func) 57 | { 58 | return source_->connect(std::forward(func)); 59 | } 60 | 61 | template 62 | auto connect(std::string const& name, FuncT&& func) 63 | { 64 | return filtered_[name].connect(std::forward(func)); 65 | } 66 | }; 67 | 68 | template 69 | signal_filter filtered_signal(FilterT&& filter) 70 | { 71 | return signal_filter{std::forward(filter)}; 72 | } 73 | 74 | inline std::string by_channel(event const& ev) 75 | { 76 | return ev.channel; 77 | } 78 | 79 | inline std::string by_name(event const& ev) 80 | { 81 | return ev.name; 82 | } 83 | 84 | }}} // pusher::detail::client 85 | 86 | #endif // PUSHERPP_DETAIL_CLIENT_SIGNAL_FILTER_HPP 87 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/detail/client/write.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_DETAIL_CLIENT_WRITE_HPP 7 | #define PUSHERPP_DETAIL_CLIENT_WRITE_HPP 8 | 9 | #include 10 | #include 11 | 12 | namespace pusher { namespace detail { namespace client 13 | { 14 | inline std::string make_subscription(std::string const& channel) 15 | { 16 | rapidjson::StringBuffer sub_buf; 17 | rapidjson::Writer sub_writer(sub_buf); 18 | sub_writer.StartObject(); 19 | sub_writer.String("event"); 20 | sub_writer.String("pusher:subscribe"); 21 | sub_writer.String("data"); 22 | sub_writer.StartObject(); 23 | sub_writer.String("channel"); 24 | sub_writer.String(channel); 25 | sub_writer.EndObject(); 26 | sub_writer.EndObject(); 27 | return {sub_buf.GetString(), sub_buf.GetSize()}; 28 | } 29 | }}} // pusher::detail::client 30 | 31 | #endif // PUSHERPP_DETAIL_CLIENT_WRITE_HPP 32 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/detail/server/crypto.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_DETAIL_SERVER_CRYPTO_HPP 7 | #define PUSHERPP_DETAIL_SERVER_CRYPTO_HPP 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace pusher { namespace detail { namespace server 18 | { 19 | template 20 | std::string hex_encode(ArgsT&&... input) 21 | { 22 | std::string encoded; 23 | CryptoPP::StringSource ss(std::forward(input)..., true, 24 | new CryptoPP::HexEncoder( 25 | new CryptoPP::StringSink(encoded), false 26 | ) 27 | ); 28 | return encoded; 29 | } 30 | 31 | inline std::string md5_digest(std::string const& input) 32 | { 33 | byte digest[CryptoPP::Weak::MD5::DIGESTSIZE]; 34 | CryptoPP::Weak::MD5 hash; 35 | hash.CalculateDigest(digest, reinterpret_cast(input.data()), input.length() ); 36 | return hex_encode(digest, sizeof(digest)); 37 | } 38 | 39 | inline std::string auth_signature(std::string const& input, std::string const& secret_key) 40 | { 41 | CryptoPP::HMAC hmac(reinterpret_cast(secret_key.data()), secret_key.size()); 42 | std::string mac; 43 | CryptoPP::StringSource ss(input, true, 44 | new CryptoPP::HashFilter(hmac, 45 | new CryptoPP::StringSink(mac) 46 | ) // HashFilter 47 | ); // StringSource 48 | return hex_encode(mac); 49 | } 50 | 51 | 52 | }}} // pusher::detail::server 53 | 54 | #endif // PUSHERPP_DETAIL_SERVER_CRYPTO_HPP 55 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/detail/server/write.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_DETAIL_SERVER_WRITE_HPP 7 | #define PUSHERPP_DETAIL_SERVER_WRITE_HPP 8 | 9 | #include "crypto.hpp" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace pusher { namespace detail { namespace server 16 | { 17 | inline std::string make_body(std::string const& channel, std::string const& event, std::string const& data) 18 | { 19 | rapidjson::StringBuffer body_buf; 20 | rapidjson::Writer body_writer{body_buf}; 21 | body_writer.StartObject(); 22 | body_writer.String("name"); 23 | body_writer.String(event); 24 | body_writer.String("channel"); 25 | body_writer.String(channel); 26 | body_writer.String("data"); 27 | body_writer.String(data); 28 | body_writer.EndObject(); 29 | return {body_buf.GetString(), body_buf.GetSize()}; 30 | } 31 | 32 | inline std::string make_url(std::string const& app_id, std::string const& key, std::string const& secret, std::string const& body) 33 | { 34 | std::stringstream url_builder; 35 | url_builder << "/apps/" << app_id << "/events"; 36 | std::stringstream request_builder; 37 | request_builder 38 | << "auth_key=" << key 39 | << "&auth_timestamp=" << pusher::detail::clock::to_time_t(pusher::detail::clock::now()) 40 | << "&auth_version=1.0" 41 | << "&body_md5=" << md5_digest(body); 42 | std::string to_sign = "POST\n" + url_builder.str() + '\n' + request_builder.str(); 43 | request_builder << "&auth_signature=" << auth_signature(to_sign, secret); 44 | url_builder << '?' << request_builder.str(); 45 | return url_builder.str(); 46 | } 47 | 48 | inline auto make_request(std::string const& host_and_port, std::string url, std::string body) 49 | { 50 | namespace http = boost::beast::http; 51 | http::request req{http::verb::post, url, 11}; 52 | req.set(http::field::host, host_and_port); 53 | req.set(http::field::content_type, "application/json"); 54 | req.body() = body; 55 | req.prepare_payload(); 56 | return req; 57 | } 58 | 59 | }}} // pusher::detail::server 60 | 61 | #endif // PUSHERPP_DETAIL_SERVER_WRITE_HPP 62 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/event.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_EVENT_HPP 7 | #define PUSHERPP_EVENT_HPP 8 | 9 | #include 10 | #include 11 | 12 | namespace pusher 13 | { 14 | namespace detail 15 | { 16 | using clock = std::chrono::system_clock; 17 | } 18 | 19 | struct event 20 | { 21 | std::string channel; 22 | std::string name; 23 | std::string data; 24 | detail::clock::time_point timestamp; 25 | }; 26 | } 27 | 28 | #endif // PUSHERPP_EVENT_HPP 29 | -------------------------------------------------------------------------------- /pusher++/include/pusher++/server.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_SERVER_HPP 7 | #define PUSHERPP_SERVER_HPP 8 | 9 | #include "event.hpp" 10 | #include "detail/server/write.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace pusher 23 | { 24 | template 25 | class server 26 | { 27 | using request = boost::beast::http::request; 28 | using response = boost::beast::http::response; 29 | 30 | template 31 | using handler_type_t = typename boost::asio::handler_type::type; 32 | 33 | SocketT socket_; 34 | boost::asio::ip::tcp::resolver resolver_; 35 | std::string app_id_; 36 | std::string key_; 37 | std::string secret_; 38 | std::string host_; 39 | request request_; 40 | response response_; 41 | boost::beast::flat_buffer response_buf_; 42 | 43 | public: 44 | server(boost::asio::io_service& ios, std::string app_id, std::string key, std::string secret, std::string cluster = "mt1") 45 | : socket_{ios} 46 | , resolver_{ios} 47 | , app_id_{std::move(app_id)} 48 | , key_{std::move(key)} 49 | , secret_{std::move(secret)} 50 | , host_{"api-" + std::move(cluster) + ".pusher.com"} 51 | {} 52 | 53 | template 54 | auto async_connect(TokenT&& token) 55 | { 56 | handler_type_t handler(std::forward(token)); 57 | boost::asio::async_result result(handler); 58 | using query = boost::asio::ip::tcp::resolver::query; 59 | resolver_.async_resolve(query{host_, "http"}, [this, handler](auto ec, auto endpoint) mutable 60 | { 61 | if(ec) 62 | return handler(ec); 63 | 64 | boost::asio::async_connect(socket_, endpoint, [handler](auto ec, auto) mutable 65 | { 66 | return handler(ec); 67 | }); 68 | }); 69 | return result.get(); 70 | } 71 | 72 | auto connect() 73 | { 74 | return boost::asio::connect(socket_, resolver_.resolve(boost::asio::ip::tcp::resolver::query{host_, "http"})); 75 | } 76 | 77 | auto disconnect() 78 | { 79 | resolver_.cancel(); 80 | socket_.close(); 81 | } 82 | 83 | template 84 | auto trigger(std::string const& channel, std::string const& event, std::string const& data, TokenT&& token) 85 | { 86 | handler_type_t handler(std::forward(token)); 87 | boost::asio::async_result result(handler); 88 | auto body = detail::server::make_body(channel, event, data); 89 | auto url = detail::server::make_url(app_id_, key_, secret_, body); 90 | auto host_and_port = host_ + ":" + std::to_string(socket_.remote_endpoint().port()); 91 | request_ = detail::server::make_request(host_and_port, url, body); 92 | 93 | boost::beast::http::async_write(socket_, request_, [this, handler](auto ec, size_t) mutable 94 | { 95 | if(ec) 96 | return handler(ec, response{}); 97 | 98 | boost::beast::http::async_read(socket_, response_buf_, response_, [this, handler](auto ec, size_t) mutable 99 | { 100 | response res; 101 | boost::swap(res, response_); 102 | return handler(ec, res); 103 | }); 104 | }); 105 | return result.get(); 106 | } 107 | 108 | auto trigger(std::string const& channel, std::string const& event, std::string const& data) 109 | { 110 | auto body = detail::server::make_body(channel, event, data); 111 | auto url = detail::server::make_url(app_id_, key_, secret_, body); 112 | auto host_and_port = host_ + ":" + std::to_string(socket_.remote_endpoint().port()); 113 | request_ = detail::server::make_request(host_and_port, url, body); 114 | 115 | boost::beast::http::write(socket_, request_); 116 | boost::beast::http::read(socket_, response_buf_, response_); 117 | response res; 118 | boost::swap(res, response_); 119 | return res; 120 | } 121 | }; 122 | } 123 | 124 | #endif // PUSHERPP_SERVER_HPP 125 | -------------------------------------------------------------------------------- /pusher++/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | find_package(Boost REQUIRED COMPONENTS unit_test_framework program_options coroutine system) 7 | find_package(CURL REQUIRED) 8 | 9 | set(common_link_libraries 10 | pusher++ 11 | ${Boost_LIBRARIES} 12 | tz 13 | ${CURL_LIBRARIES} 14 | cryptopp 15 | pthread 16 | ) 17 | 18 | add_executable(${PROJECT_NAME}-test 19 | test.hpp 20 | test.cpp 21 | test_coroutine.cpp 22 | test_signal_filter.cpp 23 | ) 24 | target_link_libraries(${PROJECT_NAME}-test ${common_link_libraries}) 25 | #target_include_directories(${PROJECT_NAME}-test PUBLIC 26 | # ${CMAKE_CURRENT_SOURCE_DIR} 27 | #) 28 | -------------------------------------------------------------------------------- /pusher++/test/test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #define BOOST_TEST_MODULE pusher-test 7 | #define BOOST_TEST_NO_MAIN 8 | #define BOOST_TEST_ALTERNATIVE_INIT_API 9 | 10 | #include "test.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace 18 | { 19 | boost::program_options::variables_map options; 20 | } 21 | 22 | std::string app_id() { return options["app_id"].as(); } 23 | std::string key() { return options["key"].as(); } 24 | std::string secret() { return options["secret"].as(); } 25 | std::string cluster() { return options["cluster"].as(); } 26 | 27 | std::ostream& operator<<(std::ostream& os, pusher::event const& ev) 28 | { 29 | return os 30 | << date::make_zoned(date::current_zone(), std::chrono::system_clock::now()) << ": " 31 | << "{\"channel\":\"" << ev.channel 32 | << "\",\"event\":\"" << ev.name 33 | << "\",\"data\":\"" << ev.data << "\"}\n"; 34 | } 35 | 36 | namespace 37 | { 38 | boost::program_options::variables_map parse_options(int argc, char* argv[]) 39 | { 40 | namespace po = boost::program_options; 41 | po::options_description desc{"Options"}; 42 | desc.add_options() 43 | ("app_id", po::value(), "Application ID") 44 | ("key", po::value(), "Key") 45 | ("secret", po::value(), "Secret") 46 | ("cluster", po::value()->default_value("eu"), "Cluster: [ap1|ap2|eu|us2]mt1]") 47 | ; 48 | po::variables_map vm; 49 | po::store(po::command_line_parser(argc, argv).options(desc).allow_unregistered().run(), vm); 50 | po::notify(vm); 51 | 52 | if(!vm.count("app_id") || !vm.count("key") || !vm.count("secret")) 53 | { 54 | std::cout << desc << '\n'; 55 | std::exit(1); 56 | } 57 | 58 | return vm; 59 | } 60 | } 61 | 62 | int main(int argc, char* argv[]) 63 | { 64 | options = parse_options(argc - 1, argv + 1); 65 | return boost::unit_test::unit_test_main(init_unit_test, argc, argv); 66 | } 67 | -------------------------------------------------------------------------------- /pusher++/test/test.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #ifndef PUSHERPP_TEST_TEST_HPP 7 | #define PUSHERPP_TEST_TEST_HPP 8 | 9 | #define BOOST_TEST_NO_MAIN 10 | #define BOOST_TEST_ALTERNATIVE_INIT_API 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace pusher 17 | { 18 | class event; 19 | } 20 | 21 | std::string app_id(); 22 | std::string key(); 23 | std::string secret(); 24 | std::string cluster(); 25 | 26 | std::ostream& operator<<(std::ostream& os, pusher::event const& ev); 27 | 28 | #endif // PUSHERPP_TEST_TEST_HPP 29 | -------------------------------------------------------------------------------- /pusher++/test/test_coroutine.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include "test.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | BOOST_AUTO_TEST_CASE(test_coroutine_tx_and_rx) 15 | { 16 | std::string const tx_message = "Message"; 17 | std::string rx_message; 18 | boost::asio::io_service ios; 19 | 20 | pusher::client client{ios, key(), cluster()}; 21 | pusher::server server{ios, app_id(), key(), secret(), cluster()}; 22 | 23 | boost::asio::spawn(ios, [&](boost::asio::yield_context yield) 24 | { 25 | boost::system::error_code ec; 26 | 27 | client.async_connect(yield[ec]); 28 | BOOST_REQUIRE_EQUAL(ec, boost::system::errc::success); 29 | 30 | server.async_connect(yield[ec]); 31 | BOOST_REQUIRE_EQUAL(ec, boost::system::errc::success); 32 | 33 | auto channel_sub = client.channel("test_channel"); 34 | channel_sub.bind("test_name", [&client, &rx_message](pusher::event const& ev) 35 | { 36 | rx_message = ev.data; 37 | client.disconnect(); 38 | }); 39 | 40 | auto response = server.trigger("test_channel", "test_name", tx_message, yield[ec]); 41 | BOOST_REQUIRE_EQUAL(ec, boost::system::errc::success); 42 | BOOST_REQUIRE_EQUAL(response.result(), boost::beast::http::status::ok); 43 | 44 | server.disconnect(); 45 | }); 46 | ios.run(); 47 | 48 | BOOST_REQUIRE_EQUAL(tx_message, rx_message); 49 | } 50 | -------------------------------------------------------------------------------- /pusher++/test/test_signal_filter.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Ben Pope 2017. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE_1_0.txt or copy at 4 | // http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #include "test.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | using namespace pusher::detail; 18 | 19 | BOOST_AUTO_TEST_CASE(test_signal_filter_all) 20 | { 21 | auto const time = pusher::detail::clock::now(); 22 | 23 | client::signal source; 24 | auto filtered_signal = client::filtered_signal(client::by_channel); 25 | filtered_signal.connect_source(source); 26 | 27 | int count = 0; 28 | auto conn_all = filtered_signal.connect([&count](pusher::event const& ev) 29 | { 30 | ++count; 31 | }); 32 | BOOST_REQUIRE_EQUAL(count, 0); 33 | source(pusher::event{"one", "one", "one", time}); 34 | BOOST_REQUIRE_EQUAL(count, 1); 35 | source(pusher::event{"two", "two", "two", time}); 36 | BOOST_REQUIRE_EQUAL(count, 2); 37 | source(pusher::event{"", "", "", time}); 38 | BOOST_REQUIRE_EQUAL(count, 3); 39 | } 40 | 41 | BOOST_AUTO_TEST_CASE(test_signal_filter_filtered) 42 | { 43 | auto const time = pusher::detail::clock::now(); 44 | 45 | client::signal source; 46 | auto filtered_signal = client::filtered_signal(client::by_channel); 47 | filtered_signal.connect_source(source); 48 | 49 | int count_one = 0; 50 | auto conn_one = filtered_signal.connect("one", [&count_one](pusher::event const& ev) 51 | { 52 | ++count_one; 53 | }); 54 | int count_two = 0; 55 | auto conn_two = filtered_signal.connect("two", [&count_two](pusher::event const& ev) 56 | { 57 | ++count_two; 58 | }); 59 | 60 | BOOST_REQUIRE_EQUAL(count_one, 0); 61 | BOOST_REQUIRE_EQUAL(count_two, 0); 62 | 63 | source(pusher::event{"one", "one", "one", time}); 64 | BOOST_REQUIRE_EQUAL(count_one, 1); 65 | BOOST_REQUIRE_EQUAL(count_two, 0); 66 | 67 | source(pusher::event{"two", "two", "two", time}); 68 | BOOST_REQUIRE_EQUAL(count_one, 1); 69 | BOOST_REQUIRE_EQUAL(count_two, 1); 70 | 71 | source(pusher::event{"", "", "", time}); 72 | BOOST_REQUIRE_EQUAL(count_one, 1); 73 | BOOST_REQUIRE_EQUAL(count_two, 1); 74 | } 75 | 76 | BOOST_AUTO_TEST_CASE(test_signal_filter_scoped) 77 | { 78 | auto const time = pusher::detail::clock::now(); 79 | 80 | client::signal source; 81 | auto filtered_signal = client::filtered_signal(client::by_channel); 82 | filtered_signal.connect_source(source); 83 | 84 | int count_all = 0; 85 | 86 | { 87 | auto conn_all = client::scoped_connection(filtered_signal.connect([&count_all](pusher::event const&) 88 | { 89 | ++count_all; 90 | })); 91 | 92 | BOOST_REQUIRE_EQUAL(count_all, 0); 93 | 94 | source(pusher::event{"one", "event", "data", time}); 95 | BOOST_REQUIRE_EQUAL(count_all, 1); 96 | } 97 | 98 | source(pusher::event{"two", "event", "data", time}); 99 | BOOST_REQUIRE_EQUAL(count_all, 1); 100 | } 101 | -------------------------------------------------------------------------------- /rapidjson/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Ben Pope 2017. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE_1_0.txt or copy at 4 | # http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | include(DownloadProject) 7 | download_project( 8 | PROJ rapidjson 9 | GIT_REPOSITORY https://github.com/miloyip/rapidjson.git 10 | GIT_TAG ${RAPIDJSON_GIT_TAG} 11 | UPDATE_DISCONNECTED 1 12 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/rapidjson 13 | ) 14 | 15 | add_library(rapidjson INTERFACE) 16 | target_include_directories(rapidjson INTERFACE 17 | ${rapidjson_SOURCE_DIR}/include 18 | ) 19 | target_compile_definitions(rapidjson INTERFACE 20 | -DRAPIDJSON_HAS_STDSTRING=1 21 | ) 22 | --------------------------------------------------------------------------------