Skip to content

Commit

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

Signed-off-by: Melg Eight <public.melg8@gmail.com>
  • Loading branch information
melg8 committed May 13, 2024
1 parent 8832ca6 commit 2d795ee
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 7 deletions.
2 changes: 1 addition & 1 deletion ci/docker/build_docker_files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ TARGETS=('cit' 'just_builder')
DOCKER_PATH="./ci/docker/docker_files"
DOCKER_FILE=${DOCKER_PATH}/"Dockerfile"

export DOCKER_BUILDKIT=0
export DOCKER_BUILDKIT=1

# Change to 1 if you want to set up signing of your docker images.
export DOCKER_CONTENT_TRUST=0
Expand Down
2 changes: 2 additions & 0 deletions ci/docker/run_nix_shell.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ echo "command with args: " "${COMMAND}"

# --privileged flag used for docker incompatibility with glibc-2.34 workaround.

export GITHUB_EVENT_PATH="/home/user/event.json"

docker container run --rm --ulimit nofile=2048 \
-e REVIEWDOG_GITHUB_API_TOKEN \
-e GITHUB_REPOSITORY \
Expand Down
2 changes: 2 additions & 0 deletions ci/docker/run_shell.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ set -e

# --privileged flag used for docker incompatibility with glibc-2.34 workaround.

export GITHUB_EVENT_PATH="/home/user/event.json"

docker container run --rm -it --ulimit nofile=2048 \
-e REVIEWDOG_GITHUB_API_TOKEN \
-e GITHUB_REPOSITORY \
Expand Down
1 change: 1 addition & 0 deletions cmake/compiler_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ if(${ENABLE_COVERAGE})
endif()

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_compiler_flags(-fdiagnostics-color=always)
add_compiler_flags(-fno-rtti)
add_compiler_flags(-fno-exceptions)

Expand Down
1 change: 1 addition & 0 deletions sources/coal/application/sources/auth_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ ServersAndCharactersFrom(std::string_view json_body) {
spdlog::error("Got empty json body for servers and characters parsing");
return std::make_error_code(std::errc::invalid_argument);
}
spdlog::info("Got json_body anwer: {}", json_body);
const auto s =
glz::read_json<ServersAndCharactersResponse>(ReducedFrom(json_body));
if (!s) {
Expand Down
13 changes: 8 additions & 5 deletions sources/coal/application/sources/http_requests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <http_requests.h>

#include <universal_declarations.h>

#include <fmt_custom_types.h>

#include <boost/asio.hpp>
Expand Down Expand Up @@ -49,7 +51,8 @@ using websocket_type = beast::websocket::stream<ssl_socket_type>;

using HttpRequest = http::request<http::string_body>;

constexpr auto nothrow_use_op = net::as_tuple(cobalt::use_op);
static constexpr auto nothrow_use_op =
boost::asio::as_tuple(boost::cobalt::use_op);

static void ReportError(boost::system::error_code err,
std::string_view action,
Expand Down Expand Up @@ -89,7 +92,7 @@ static HttpRequest FormRequestFor(boost::urls::url url,

static cobalt::promise<Result<ssl_socket_type>> Connect(boost::urls::url url,
ssl::context& ctx) {
ip::tcp::resolver resolve{cobalt::this_thread::get_executor()};
ip::tcp::resolver resolve{co_await cobalt::this_coro::executor};
const auto port = url.port().empty() ? url.scheme() : url.port();
const auto [err, endpoints] =
co_await resolve.async_resolve(url.host(), port, nothrow_use_op);
Expand All @@ -98,7 +101,7 @@ static cobalt::promise<Result<ssl_socket_type>> Connect(boost::urls::url url,
co_return err;
}

ssl_socket_type sock{cobalt::this_thread::get_executor(), ctx};
ssl_socket_type sock{co_await cobalt::this_coro::executor, ctx};
if (!SSL_set_tlsext_host_name(sock.native_handle(), url.host().data())) {
const auto error_code = ::ERR_get_error();
spdlog::error("SSL_set_tlsext_host_name failed, error code: {}",
Expand Down Expand Up @@ -167,8 +170,8 @@ static cobalt::promise<Result<HttpResponse>> SendHttpsRequestTo(

static cobalt::promise<Result<beast::tcp_stream>> ConnectTcpStream(
boost::urls::url url) {
beast::tcp_stream stream(cobalt::this_thread::get_executor());
ip::tcp::resolver resolve{cobalt::this_thread::get_executor()};
beast::tcp_stream stream(co_await cobalt::this_coro::executor);
ip::tcp::resolver resolve{co_await cobalt::this_coro::executor};
const auto port = url.port().empty() ? url.scheme() : url.port();
const auto [err, endpoints] =
co_await resolve.async_resolve(url.host(), port, nothrow_use_op);
Expand Down
7 changes: 7 additions & 0 deletions sources/coal/application/sources/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <auth_client.h>
#include <fmt_custom_types.h>
#include <http_requests.h>
#include <websocket_client.h>

#include <spdlog/spdlog.h>
#include <boost/asio/co_spawn.hpp>
Expand Down Expand Up @@ -199,12 +200,18 @@ static cobalt::task<void> TestAuthConnectivity() {
co_await TestAuthTo(config);
}

static cobalt::task<void> TestWebsocketConnectivity() {
spdlog::info("Testing websockets");
co_await DoSession("echo.websocket.org", "80", "Hello, world!\n");
}

} // namespace coal

boost::cobalt::main co_main(int, char**) {
using namespace coal;
SetupSpdLog();
SpeakWithDelay();
co_await TestWebsocketConnectivity();
co_await TestAuthConnectivity();
co_await TestHttpRequests();
co_await Test();
Expand Down
103 changes: 103 additions & 0 deletions sources/coal/application/sources/websocket_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: © 2024 Melg Eight <public.melg8@gmail.com>
//
// SPDX-License-Identifier: MIT

#include <websocket_client.h>

#include <spdlog/spdlog.h>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>

#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
#include <string_view>

namespace coal {

namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>

static constexpr auto nothrow_use_op =
boost::asio::as_tuple(boost::cobalt::use_op);

//------------------------------------------------------------------------------

// Sends a WebSocket message and prints the response
cobalt::task<void> DoSession(std::string host,
std::string port,
std::string text) {
// These objects perform our I/O
auto resolver = tcp::resolver(co_await net::this_coro::executor);
auto ws =
websocket::stream<beast::tcp_stream>(co_await net::this_coro::executor);
// Look up the domain name

const auto [err_1, endpoints] =
co_await resolver.async_resolve(host, port, nothrow_use_op);
if (err_1) {
co_return;
}

// Set a timeout on the operation
beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(30));

// Make the connection on the IP address we get from a lookup
auto [err_2] = co_await beast::get_lowest_layer(ws).async_connect(
*endpoints.begin(), nothrow_use_op);

if (err_2) {
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);

// Send the message
co_await ws.async_write(net::buffer(std::string(text)), nothrow_use_op);

// 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
spdlog::info("Got response from websocket size: {}", buffer.data().size());
}

} // namespace coal
33 changes: 33 additions & 0 deletions sources/coal/application/sources/websocket_client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: © 2024 Melg Eight <public.melg8@gmail.com>
//
// SPDX-License-Identifier: MIT

#ifndef WEBSOCKET_CLIENT_H
#define WEBSOCKET_CLIENT_H

#include <universal_declarations.h>

#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/cobalt/task.hpp>
#include <string>

namespace coal {
//

struct WebsocketServer {
std::string addr = {};
std::string port = {};
};

using tcp = boost::asio::ip::tcp;
namespace cobalt = boost::cobalt;

cobalt::task<void> DoSession(std::string host,
std::string port,
std::string text);

} // namespace coal

#endif // WEBSOCKET_CLIENT_H
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ struct Server {
size_t players = 0u;
std::string key = {};
std::string addr = {};
int port = {};
size_t port = {};
};
using Servers = std::vector<Server>;

Expand Down

0 comments on commit 2d795ee

Please sign in to comment.