Skip to content

Commit

Permalink
chore: add websocket client connection
Browse files Browse the repository at this point in the history
Add websocket client connection.

Signed-off-by: Melg Eight <public.melg8@gmail.com>
  • Loading branch information
melg8 committed May 13, 2024
1 parent 2d795ee commit 8c32b4f
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 27 deletions.
3 changes: 2 additions & 1 deletion sources/coal/application/sources/auth_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ cobalt::promise<Result<UserAuthData>> AuthTo(std::string_view server_url,
const auto response = maybe_response.value();
const auto body = response.body();
if (!body.contains("Logged In!")) {
spdlog::error("Can't login, server response: {}", body);
spdlog::error("Can't login, with credentials: {} server response: {}",
credentials, body);
co_return Result<UserAuthData>{
std::make_error_code(std::errc::permission_denied)};
}
Expand Down
12 changes: 12 additions & 0 deletions sources/coal/application/sources/auth_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include <servers_and_characters_response_from_json.h>
#include <universal_declarations.h>
#include <pretty_json_from_any_struct.h>

#include <fmt/format.h>
#include <boost/cobalt.hpp>

#include <string>
Expand Down Expand Up @@ -39,4 +41,14 @@ cobalt::promise<Result<ServersAndCharactersResponse>> GetServersAndCharacters(

} // namespace coal

namespace fmt {
template <>
struct formatter<coal::Credentials> : formatter<std::string> {
auto format(const coal::Credentials& c, format_context& ctx) {
return formatter<std::string>::format(JsonFrom(c), ctx);
}
};

} // namespace fmt

#endif // AUTH_CLIENT_H
22 changes: 18 additions & 4 deletions sources/coal/application/sources/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,16 @@ struct SubscribableSocket {
HandlesRegistry registry;
};

struct WebsocketRequest {
std::string websocket_url = {};
std::string port = {};
std::string message = {};
};

struct Configuration {
std::string url = {};
Credentials credentials = {};
WebsocketRequest ws_request = {};
};

static auto OnValue(HandlesRegistry& registry, int value) {
Expand Down Expand Up @@ -181,13 +188,18 @@ static cobalt::task<void> Test() {

[[nodiscard]] static Configuration ConfigurationFromFile(
const std::string& path) {
Configuration config = {"http://127.0.0.1:8083",
{"test@test.com", "123456789"}};
Configuration config = {
.url = "http://127.0.0.1:8083",
.credentials = {.email = "test@test.com", .password = "123456789"},
.ws_request = {.websocket_url = "websocket-echo.com",
.port = "80",
.message = "Hello world!"}};

const auto read_error = glz::read_file_json(config, path, std::string{});
if (read_error) {
spdlog::warn("Can't get configuration file, will use default values");
const auto write_error = glz::write_file_json(config, path, std::string{});
const auto write_error = glz::write_file_json<glz::opts{.prettify = true}>(
config, path, std::string{});
if (write_error) {
spdlog::warn("Can't write default configuration file");
}
Expand All @@ -202,7 +214,9 @@ static cobalt::task<void> TestAuthConnectivity() {

static cobalt::task<void> TestWebsocketConnectivity() {
spdlog::info("Testing websockets");
co_await DoSession("echo.websocket.org", "80", "Hello, world!\n");
const auto config = ConfigurationFromFile("./.config.json");
const auto& ws = config.ws_request;
co_await DoSession(ws.websocket_url, ws.port, ws.message);
}

} // namespace coal
Expand Down
51 changes: 29 additions & 22 deletions sources/coal/application/sources/websocket_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ cobalt::task<void> DoSession(std::string host,
const auto [err_1, endpoints] =
co_await resolver.async_resolve(host, port, nothrow_use_op);
if (err_1) {
spdlog::error("Can't resolve host {}:{}", host, port);
co_return;
}

Expand All @@ -55,49 +56,55 @@ cobalt::task<void> DoSession(std::string host,
*endpoints.begin(), nothrow_use_op);

if (err_2) {
spdlog::error("Can't connect to {}:{}", host, port);
co_return;
}

// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
// See https://tools.ietf.org/html/rfc7230#section-5.4
host += ':' + port;

// Turn off the timeout on the tcp_stream, because
// the websocket stream has its own timeout system.
beast::get_lowest_layer(ws).expires_never();

// Set suggested timeout settings for the websocket
ws.set_option(
websocket::stream_base::timeout::suggested(beast::role_type::client));

// Set a decorator to change the User-Agent of the handshake
ws.set_option(
websocket::stream_base::decorator([](websocket::request_type& req) {
req.set(
http::field::user_agent,
std::string(BOOST_BEAST_VERSION_STRING) + " websocket-client-coro");
}));

// Perform the websocket handshake
co_await ws.async_handshake(host, "/", nothrow_use_op);
const auto [handshake_err] =
co_await ws.async_handshake(host, "/", nothrow_use_op);
if (handshake_err) {
spdlog::error("Handshake error with {} reason: {}", host,
handshake_err.message());
co_return;
}

// Send the message
co_await ws.async_write(net::buffer(std::string(text)), nothrow_use_op);
spdlog::info("Sending message to websocket: {}", text);
const auto [write_err, _1] =
co_await ws.async_write(net::buffer(std::string(text)), nothrow_use_op);
if (write_err) {
spdlog::error("Error writing into web socket {}", host);
co_return;
}

// This buffer will hold the incoming message
beast::flat_buffer buffer;

// Read a message into our buffer
co_await ws.async_read(buffer, nothrow_use_op);

// Close the WebSocket connection
co_await ws.async_close(websocket::close_code::normal, nothrow_use_op);

// If we get here then the connection is closed gracefully

// The make_printable() function helps print a ConstBufferSequence
const auto [read_err, _2] = co_await ws.async_read(buffer, nothrow_use_op);
if (read_err) {
spdlog::error("Error reading from web socket {}", host);
co_return;
}
spdlog::info("Got response from websocket size: {}", buffer.data().size());

const auto [close_err] =
co_await ws.async_close(websocket::close_code::normal, nothrow_use_op);
if (close_err) {
spdlog::error("Can't close gracefully connection to {}", host);
co_return;
}
spdlog::info("Websocket connection closed gracefuly with {}", host);
}

} // namespace coal
2 changes: 2 additions & 0 deletions sources/coal/library/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: MIT

find_package(glaze REQUIRED)

file(GLOB_RECURSE ALL_COAL_LIB_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
file(GLOB_RECURSE ALL_COAL_LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
Expand All @@ -13,3 +14,4 @@ header_directories(ALL_LIB_INCLUDE_DIRECTORIES)

target_include_directories(coal PUBLIC "${ALL_LIB_INCLUDE_DIRECTORIES}")

target_link_libraries(coal PRIVATE glaze::glaze)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: © 2024 Melg Eight <public.melg8@gmail.com>
//
// SPDX-License-Identifier: MIT

#include <pretty_json_from_any_struct.h>
24 changes: 24 additions & 0 deletions sources/coal/library/sources/json/pretty_json_from_any_struct.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: © 2024 Melg Eight <public.melg8@gmail.com>
//
// SPDX-License-Identifier: MIT

#ifndef PRETTY_JSON_FROM_ANY_STRUCT_H
#define PRETTY_JSON_FROM_ANY_STRUCT_H

#include <glaze/glaze.hpp>

#include <string>

namespace coal {

template <typename T>
[[nodiscard]] std::string JsonFrom(const T& any_struct) {
std::string result{};
glz::write<glz::opts{.prettify = true}>(any_struct, result);
return result;
}

//
} // namespace coal

#endif // PRETTY_JSON_FROM_ANY_STRUCT_H
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: © 2024 Melg Eight <public.melg8@gmail.com>
//
// SPDX-License-Identifier: MIT

#include <testing_framework.h>

#include <pretty_json_from_any_struct.h>

#include <string>

namespace coal::test {
struct TestStruct {
int x = 42;
std::string field = "test";
};

SCENARIO("pretty json creation from struct") {
SECTION("custom struct with fields") {
TestStruct test_struct{};
const auto json = JsonFrom(test_struct);
const auto expected =
"{\n"
" \"x\": 42,\n"
" \"field\": \"test\"\n"
"}";
CHECK(json == expected);
}
}
//
} // namespace coal::test

0 comments on commit 8c32b4f

Please sign in to comment.