diff --git a/clients/cpp/CPPSketch/http_connection.h b/clients/cpp/CPPSketch/http_connection.h new file mode 100644 index 0000000000..27435d7cc1 --- /dev/null +++ b/clients/cpp/CPPSketch/http_connection.h @@ -0,0 +1,57 @@ +#pragma once + +#include "transfer_format.h" +#include "http_connection_options.h" +#include +#include + +namespace signalR +{ + namespace impl + { + class http_connection + { + public: + http_connection() noexcept + : mFormat(transfer_format::Text) + { + } + + http_connection(const http_connection_options& options) noexcept + : mOptions(options), mFormat(transfer_format::Text), mUrl(options.Url) + { + } + + http_connection(http_connection&& rhs) noexcept + : mOptions(rhs.mOptions), mFormat(rhs.mFormat), mUrl(std::forward(rhs.mUrl)) + { + } + + http_connection& operator=(http_connection&& rhs) noexcept + { + mOptions = rhs.mOptions; + mFormat = rhs.mFormat; + mUrl = std::forward(rhs.mUrl); + return *this; + } + + http_connection(const http_connection&) = delete; + http_connection& operator=(const http_connection&) = delete; + + std::future start(transfer_format format) + { + mFormat = format; + return std::async(std::launch::async, [] {}); + } + + std::future dispose() + { + return std::async(std::launch::deferred, [] {}); + } + private: + transfer_format mFormat; + http_connection_options mOptions; + std::string mUrl; + }; + } +} \ No newline at end of file diff --git a/clients/cpp/CPPSketch/http_connection_factory.h b/clients/cpp/CPPSketch/http_connection_factory.h new file mode 100644 index 0000000000..33fcd2591a --- /dev/null +++ b/clients/cpp/CPPSketch/http_connection_factory.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include "http_connection.h" +#include "transfer_format.h" + +namespace signalR +{ + namespace impl + { + class http_connection_factory + { + public: + http_connection_factory(const http_connection_options& options) noexcept + : mOptions(options) + { + } + + std::future connect(transfer_format format) + { + return std::async(std::launch::async, [this, format] + { + auto connection = http_connection(mOptions); + try + { + connection.start(format).get(); + } + catch (...) + { + connection.dispose().get(); + throw; + } + + return connection; + }); + } + private: + http_connection_options mOptions; + }; + } +} \ No newline at end of file diff --git a/clients/cpp/CPPSketch/http_connection_options.h b/clients/cpp/CPPSketch/http_connection_options.h new file mode 100644 index 0000000000..8514f8dd0e --- /dev/null +++ b/clients/cpp/CPPSketch/http_connection_options.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace signalR +{ + struct http_connection_options + { + int Transports; + std::string Url; + }; +} \ No newline at end of file diff --git a/clients/cpp/CPPSketch/hub_connection.h b/clients/cpp/CPPSketch/hub_connection.h new file mode 100644 index 0000000000..65662ead57 --- /dev/null +++ b/clients/cpp/CPPSketch/hub_connection.h @@ -0,0 +1,124 @@ +#pragma once +#ifndef _HUBCONNECTION_H +#define _HUBCONNECTION_H + +#include +#include +#include "http_connection_factory.h" + +namespace signalR +{ + template + class hub_connection + { + public: + explicit hub_connection(impl::http_connection_factory factory, Protocol hubProtocol) noexcept + : mProtocol(hubProtocol), mFactory(factory) + {} + + ~hub_connection() {} + + hub_connection(hub_connection &&rhs) noexcept + : mHandlers(std::move(rhs.mHandlers)), mProtocol(std::move(rhs.mProtocol)), mFactory(std::move(rhs.mFactory)), mConnection(std::move(rhs.mConnection)) + { + } + + hub_connection(const hub_connection&) = delete; + + hub_connection& operator=(const hub_connection&) = delete; + + std::future __cdecl start() + { + mConnection = mFactory.connect(mProtocol.transferFormat).get(); + + return some_func(); + } + + std::future __cdecl stop() + { + return some_func(); + } + + void __cdecl on_closed(const std::function& closed_callback); + + template + void __cdecl on(const std::string& name, const std::function& methodHandler) + { + mHandlers[name] = [methodHandler, this](const std::string& arguments) + { + auto tuple = mProtocol.parse_message(arguments); + invoke_with_args(methodHandler, tuple); + }; + } + + template + std::future invoke(const std::string& method_name, const T&... args) + { + auto msg = mProtocol.write_message(args...); + std::cout << "json created: " << msg << std::endl; + return std::async(std::launch::deferred, []() { return R(); }); + } + + template + std::future send(const std::string& method_name, const T&... args) + { + auto msg = mProtocol.write_message(args...); + return std::async(std::launch::deferred, []() { }); + } + + //private: + std::future some_func() + { + return std::async(std::launch::deferred, []() { std::cout << std::endl; }); + } + + std::map> mHandlers; + Protocol mProtocol; + impl::http_connection_factory mFactory; + impl::http_connection mConnection; + + private: + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args)); + } + + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args), std::get<1>(args)); + } + + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args), std::get<1>(args), std::get<2>(args)); + } + + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args), std::get<1>(args), std::get<2>(args), std::get<3>(args)); + } + + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args), std::get<1>(args), std::get<2>(args), std::get<3>(args), std::get<4>(args)); + } + + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args), std::get<1>(args), std::get<2>(args), std::get<3>(args), std::get<4>(args), std::get<5>(args)); + } + + template + void invoke_with_args(const std::function& func, const std::tuple& args) + { + func(std::get<0>(args), std::get<1>(args), std::get<2>(args), std::get<3>(args), std::get<4>(args), std::get<5>(args), std::get<6>(args)); + } + }; +} +#endif \ No newline at end of file diff --git a/clients/cpp/CPPSketch/hub_connection_builder.h b/clients/cpp/CPPSketch/hub_connection_builder.h new file mode 100644 index 0000000000..34d9f25809 --- /dev/null +++ b/clients/cpp/CPPSketch/hub_connection_builder.h @@ -0,0 +1,121 @@ +#pragma once +#ifndef _HUBCONNECTIONBUILDER_H +#define _HUBCONNECTIONBUILDER_H + +#include +#include +#include "json_hub_protocol.h" +#include "hub_connection.h" +#include "http_connection_factory.h" + +namespace signalR +{ + namespace impl + { + template + struct has_parse_message + { + template + static char Test(decltype(&U::parse_message<>)*); + template + static unsigned int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + }; + + template + struct has_write_message + { + template + static char Test(decltype(&U::write_message<>)*); + template + static unsigned int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + }; + + template + class hub_connection_builder_impl; + } + + class hub_connection_builder + { + public: + hub_connection_builder() noexcept + { + } + + hub_connection_builder& configure_logging() + { + return *this; + } + + hub_connection_builder& with_url(const std::string& url, std::function configure = nullptr) + { + mOptions.Transports = 1; + mOptions.Url = url; + if (configure) + { + configure(mOptions); + } + + return *this; + } + + template + impl::hub_connection_builder_impl

use_protocol(P p) + { + return impl::hub_connection_builder_impl

(*this, p); + } + + hub_connection build() + { + return std::move(hub_connection(mOptions, json_hub_protocol())); + } + + template + hub_connection build(Protocol protocol) + { + static_assert(impl::has_parse_message::value, "parse_message function expected from protocol"); + static_assert(impl::has_write_message::value, "write_message function expected from protocol"); + + return hub_connection(mOptions, protocol); + } + + private: + http_connection_options mOptions; + }; + + namespace impl + { + template + class hub_connection_builder_impl + { + public: + hub_connection_builder_impl(hub_connection_builder& internalBuilder, Protocol protocol) + : mBuilder(internalBuilder), mProtocol(protocol) + { + } + + hub_connection_builder_impl& configure_logging() + { + return *this; + } + + hub_connection_builder_impl& with_url(const std::string& url, std::function configure = nullptr) + { + mBuilder.with_url(url, configure); + return *this; + } + + hub_connection build() + { + static_assert(has_parse_message::value, "parse_message function expected from protocol"); + static_assert(has_write_message::value, "write_message function expected from protocol"); + return mBuilder.build(mProtocol); + } + private: + hub_connection_builder & mBuilder; + Protocol mProtocol; + }; + } +} +#endif \ No newline at end of file diff --git a/clients/cpp/CPPSketch/json_hub_protocol.cpp b/clients/cpp/CPPSketch/json_hub_protocol.cpp new file mode 100644 index 0000000000..7408d56cd3 --- /dev/null +++ b/clients/cpp/CPPSketch/json_hub_protocol.cpp @@ -0,0 +1,140 @@ +#include "json_hub_protocol.h" + +namespace signalR +{ + template <> + rapidjson::Value to_json<>(int32_t item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(int64_t item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(uint32_t item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(uint64_t item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(short item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(char item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(double item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(float item, rapidjson::MemoryPoolAllocator<>&) + { + return rapidjson::Value(item); + } + + template <> + rapidjson::Value to_json<>(std::string item, rapidjson::MemoryPoolAllocator<>& alloc) + { + return rapidjson::Value(item.c_str(), item.size(), alloc); + } + + template <> + rapidjson::Value to_json<>(const char* item, rapidjson::MemoryPoolAllocator<>& alloc) + { + return rapidjson::Value(item, alloc); + } + + /*template + T from_json(const rapidjson::Value& item) + { + static_assert(false, "No conversion to json could be found for the given type, see below output for more info."); + }*/ + + template <> + int from_json(const rapidjson::Value& item) + { + if (!item.IsInt()) + { + throw std::runtime_error("Could not convert json to type 'int'"); + } + return item.GetInt(); + } + + template <> + unsigned int from_json(const rapidjson::Value& item) + { + if (!item.IsUint()) + { + throw std::runtime_error("Could not convert json to type 'int'"); + } + return item.GetUint(); + } + + template <> + int64_t from_json(const rapidjson::Value& item) + { + if (!item.IsInt64()) + { + throw std::runtime_error("Could not convert json to type 'int'"); + } + return item.GetInt64(); + } + + template <> + uint64_t from_json(const rapidjson::Value& item) + { + if (!item.IsUint64()) + { + throw std::runtime_error("Could not convert json to type 'int'"); + } + return item.GetUint64(); + } + + template <> + float from_json(const rapidjson::Value& item) + { + if (!item.IsFloat()) + { + throw std::runtime_error("Could not convert json to type 'float'"); + } + return item.GetFloat(); + } + + template <> + std::string from_json(const rapidjson::Value& item) + { + if (!item.IsString()) + { + throw std::runtime_error("Could not convert json to type 'std::string'"); + } + return std::string(item.GetString(), item.GetStringLength()); + } + + template <> + bool from_json(const rapidjson::Value& item) + { + if (!item.IsBool()) + { + throw std::runtime_error("Could not convert json to type 'bool'"); + } + return item.GetBool(); + } +} \ No newline at end of file diff --git a/clients/cpp/CPPSketch/json_hub_protocol.h b/clients/cpp/CPPSketch/json_hub_protocol.h new file mode 100644 index 0000000000..6c637deab2 --- /dev/null +++ b/clients/cpp/CPPSketch/json_hub_protocol.h @@ -0,0 +1,99 @@ +#pragma once +#ifndef _JSONHUBPROTOCOL_H +#define _JSONHUBPROTOCOL_H + +#include +#include +#include + +#include "rapidjson\document.h" +#include "rapidjson\writer.h" +#include "rapidjson\stringbuffer.h" + +#include "transfer_format.h" + +namespace signalR +{ + template + rapidjson::Value to_json(T item, rapidjson::MemoryPoolAllocator<>& alloc); + + template + T from_json(const rapidjson::Value& item); + + class json_hub_protocol + { + public: + template + std::tuple parse_message(const std::string& data) const + { + /*auto parsed = web::json::value::parse(utility::conversions::to_string_t(data)); + if (!parsed.is_array()) + { + throw std::exception("expected json array"); + } + if (parsed.size() != sizeof...(T)) + { + throw std::exception("incorrect number of arguments"); + }*/ + auto d = rapidjson::Document(); + d.Parse(data.c_str()); + return parse_args(d["arguments"].GetArray()); + } + + template + std::string write_message(const T&... args) const + { + rapidjson::Document d; + d.SetObject(); + auto& alloc = d.GetAllocator(); + + rapidjson::Value arr(rapidjson::kArrayType); + write_message_impl(arr, alloc, args...); + d.AddMember("arguments", arr, alloc); + + rapidjson::StringBuffer buf; + buf.Clear(); + rapidjson::Writer writer(buf); + d.Accept(writer); + return buf.GetString(); + } + + static const transfer_format transferFormat = transfer_format::Text; + private: + template + void write_message_impl(rapidjson::Value& d, rapidjson::MemoryPoolAllocator<>& alloc, const First& first, const T&... args) const + { + auto k = to_json(first, alloc); + d.PushBack(k, alloc); + + write_message_impl(d, alloc, args...); + } + + template + void write_message_impl(rapidjson::Value& d, rapidjson::MemoryPoolAllocator<>& alloc, const T& arg) const + { + auto k = to_json(arg, alloc); + d.PushBack(k, alloc); + } + + template + std::tuple parse_args(const rapidjson::Value::Array& arr) const + { + return std::make_tuple(from_json(arr[0])); + } + + template + std::tuple parse_args(const rapidjson::Value::Array& arr) const + { + return std::make_tuple(from_json(arr[0]), from_json(arr[1])); + } + + template + std::tuple parse_args(const rapidjson::Value::Array& arr) const + { + return std::make_tuple(from_json(arr[0]), from_json(arr[1]), from_json(arr[2])); + } + }; +} + +#endif \ No newline at end of file diff --git a/clients/cpp/CPPSketch/main.cpp b/clients/cpp/CPPSketch/main.cpp new file mode 100644 index 0000000000..a5fb29e31e --- /dev/null +++ b/clients/cpp/CPPSketch/main.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include + +#include "hub_connection_builder.h" + +namespace signalR +{ + // add 'custom' type json parsing + template <> + std::vector from_json>(const rapidjson::Value& item) + { + if (!item.IsArray()) + { + throw std::runtime_error("Could not convert json to type 'std::vector'"); + } + std::vector vec; + for (auto& i : item.GetArray()) + { + if (i.IsInt()) + { + vec.push_back(i.GetInt()); + } + } + return vec; + } + + template <> + rapidjson::Value to_json<>(std::vector item, rapidjson::MemoryPoolAllocator<>& alloc) + { + rapidjson::Value arr(rapidjson::kArrayType); + + for (size_t i = 0; i < item.size(); ++i) + { + arr.PushBack(item[i], alloc); + } + + return arr; + } +} + +auto doCon() +{ + auto connection = signalR::hub_connection_builder() + .use_protocol(signalR::json_hub_protocol()) + .with_url("http://example.com", [](signalR::http_connection_options&) {}) + .configure_logging() + .build(); + + connection.on("test", [](float i) + { + std::cout << "from test " << i << std::endl; + }); + + connection.on>("test2", [](int i, std::string s, std::vector v) + { + std::cout << "from test2 " << i << " " << s.c_str(); + for (auto val : v) + { + std::cout << " " << val; + } + std::cout << std::endl; + }); + + connection.start().get(); + auto integer = connection.invoke("", 10Ui64, 1.f, std::string("hello"), std::vector{2, 5, 3}).get(); + connection.stop().get(); + + return connection; +} + +int main() +{ + auto c = doCon(); + c.mHandlers["test"]("{\"arguments\":[5.0]}"); + c.mHandlers["test2"]("{\"arguments\":[5, \"word\", [2,1,1]]}"); + + char i; + std::cin >> i; +} \ No newline at end of file diff --git a/clients/cpp/CPPSketch/transfer_format.h b/clients/cpp/CPPSketch/transfer_format.h new file mode 100644 index 0000000000..d5cc06cb94 --- /dev/null +++ b/clients/cpp/CPPSketch/transfer_format.h @@ -0,0 +1,10 @@ +#pragma once + +namespace signalR +{ + enum transfer_format + { + Text, + Binary + }; +} \ No newline at end of file