• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / libossia / 26790817643

02 Jun 2026 12:33AM UTC coverage: 47.484%. First build
26790817643

Pull #876

github

web-flow
Merge bed3c732f into 44922d63b
Pull Request #876: Improve network stack

425 of 1229 new or added lines in 24 files covered. (34.58%)

26129 of 55027 relevant lines covered (47.48%)

179826.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.71
/src/ossia/network/sockets/websocket_client_beast.cpp
1
#include <ossia/network/sockets/websocket_client_beast.hpp>
2

3
#include <ossia/detail/json.hpp>
4

5
namespace ossia::net
6
{
7

NEW
8
websocket_client_beast::websocket_client_beast(boost::asio::io_context& ctx)
×
NEW
9
    : m_context{ctx}
×
NEW
10
    , m_resolver{boost::asio::make_strand(ctx)}
×
11
{
NEW
12
}
×
13

14
websocket_client_beast::websocket_client_beast(
4✔
15
    boost::asio::io_context& ctx, ws_client_message_handler handler)
4✔
16
    : m_context{ctx}
4✔
17
    , m_resolver{boost::asio::make_strand(ctx)}
4✔
18
    , m_on_message{std::move(handler)}
8✔
19
{
20
}
4✔
21

22
websocket_client_beast::websocket_client_beast(ws_client_message_handler handler)
16✔
23
    : m_owned_context{std::make_unique<boost::asio::io_context>()}
16✔
24
    , m_context{*m_owned_context}
16✔
25
    , m_resolver{boost::asio::make_strand(*m_owned_context)}
16✔
26
    , m_on_message{std::move(handler)}
32✔
27
{
28
}
16✔
29

30
websocket_client_beast::~websocket_client_beast()
36✔
31
{
32
  if(m_open)
19✔
33
    stop();
4✔
34
}
36✔
35

36
void websocket_client_beast::parse_uri(
19✔
37
    const std::string& uri, std::string& host, std::string& port,
38
    std::string& path)
39
{
40
  // Parse ws://host:port/path or ws://host/path
41
  std::string_view sv = uri;
19✔
42

43
  // Remove scheme
44
  if(sv.starts_with("ws://"))
19✔
45
    sv.remove_prefix(5);
17✔
46
  else if(sv.starts_with("wss://"))
2✔
NEW
47
    sv.remove_prefix(6);
×
48
  else if(sv.starts_with("http://"))
2✔
49
    sv.remove_prefix(7);
2✔
NEW
50
  else if(sv.starts_with("https://"))
×
NEW
51
    sv.remove_prefix(8);
×
52

53
  // Find path
54
  auto path_pos = sv.find('/');
19✔
55
  if(path_pos != std::string_view::npos)
19✔
56
  {
NEW
57
    path = std::string(sv.substr(path_pos));
×
NEW
58
    sv = sv.substr(0, path_pos);
×
59
  }
60
  else
61
  {
62
    path = "/";
19✔
63
  }
64

65
  // Find port
66
  auto port_pos = sv.find(':');
19✔
67
  if(port_pos != std::string_view::npos)
19✔
68
  {
69
    host = std::string(sv.substr(0, port_pos));
19✔
70
    port = std::string(sv.substr(port_pos + 1));
19✔
71
  }
72
  else
73
  {
NEW
74
    host = std::string(sv);
×
NEW
75
    port = "80";
×
76
  }
77
}
19✔
78

79
void websocket_client_beast::connect(const std::string& uri)
19✔
80
{
81
  std::string host, port, path;
19✔
82
  parse_uri(uri, host, port, path);
19✔
83

84
  m_host = host;
19✔
85
  m_ws = std::make_unique<
86
      boost::beast::websocket::stream<boost::beast::tcp_stream>>(
38✔
87
      boost::asio::make_strand(m_context));
57✔
88

89
  m_ws->set_option(
38✔
90
      boost::beast::websocket::stream_base::timeout::suggested(
19✔
91
          boost::beast::role_type::client));
92

93
  m_ws->set_option(
38✔
94
      boost::beast::websocket::stream_base::decorator(
19✔
NEW
95
          [](boost::beast::websocket::request_type& req) {
×
96
    req.set(boost::beast::http::field::user_agent, "ossia");
19✔
97
  }));
38✔
98

99
  m_connected = true;
19✔
100
  do_resolve(host, port, path);
19✔
101
}
19✔
102

103
void websocket_client_beast::connect_and_run(const std::string& uri)
15✔
104
{
105
  connect(uri);
15✔
106
  m_context.run();
15✔
107
  m_context.restart();
15✔
108
  m_connected = false;
15✔
109
}
15✔
110

111
void websocket_client_beast::stop()
19✔
112
{
113
  m_connected = false;
19✔
114
  m_open = false;
19✔
115

116
  // Forcefully close the underlying TCP socket.
117
  // A synchronous WebSocket close handshake would deadlock if the
118
  // peer's io_context is not running.
119
  {
120
    std::lock_guard lock{m_mutex};
19✔
121
    if(m_ws)
19✔
122
    {
123
      boost::beast::error_code ec;
18✔
124
      boost::beast::get_lowest_layer(*m_ws).socket().shutdown(
18✔
125
          boost::asio::ip::tcp::socket::shutdown_both, ec);
126
      boost::beast::get_lowest_layer(*m_ws).socket().close(ec);
18✔
127
    }
128
  }
19✔
129

130
  // If we own the io_context, stop it so that run() returns in connect_and_run().
131
  if(m_owned_context)
19✔
132
    m_owned_context->stop();
15✔
133
}
19✔
134

135
bool websocket_client_beast::connected() const
28✔
136
{
137
  return m_open;
28✔
138
}
139

NEW
140
void websocket_client_beast::send_message(const std::string& request)
×
141
{
NEW
142
  if(!m_open || !m_ws)
×
NEW
143
    return;
×
144

NEW
145
  std::lock_guard lock{m_mutex};
×
NEW
146
  boost::beast::error_code ec;
×
NEW
147
  m_ws->text(true);
×
NEW
148
  m_ws->write(boost::asio::buffer(request), ec);
×
NEW
149
  if(ec)
×
NEW
150
    ossia::logger().error("WS send error: {}", ec.message());
×
NEW
151
}
×
152

153
void websocket_client_beast::send_message(const rapidjson::StringBuffer& request)
256✔
154
{
155
  if(!m_open || !m_ws)
256✔
NEW
156
    return;
×
157

158
  std::lock_guard lock{m_mutex};
256✔
159
  boost::beast::error_code ec;
256✔
160
  m_ws->text(true);
256✔
161
  m_ws->write(boost::asio::buffer(request.GetString(), request.GetSize()), ec);
256✔
162
  if(ec)
256✔
NEW
163
    ossia::logger().error("WS send error: {}", ec.message());
×
164
}
256✔
165

166
void websocket_client_beast::send_binary_message(std::string_view request)
440,517✔
167
{
168
  if(!m_open || !m_ws)
440,517✔
NEW
169
    return;
×
170

171
  std::lock_guard lock{m_mutex};
440,517✔
172
  boost::beast::error_code ec;
440,517✔
173
  m_ws->binary(true);
440,517✔
174
  m_ws->write(boost::asio::buffer(request.data(), request.size()), ec);
440,517✔
175
  if(ec)
440,517✔
NEW
176
    ossia::logger().error("WS send error: {}", ec.message());
×
177
}
440,517✔
178

179
void websocket_client_beast::do_resolve(
19✔
180
    const std::string& host, const std::string& port, const std::string& path)
181
{
182
  m_resolver.async_resolve(
19✔
183
      host, port,
184
      [this, path](
38✔
185
          boost::beast::error_code ec,
186
          boost::asio::ip::tcp::resolver::results_type results) {
187
    on_resolve(ec, std::move(results), path);
19✔
188
  });
19✔
189
}
19✔
190

191
void websocket_client_beast::on_resolve(
19✔
192
    boost::beast::error_code ec,
193
    boost::asio::ip::tcp::resolver::results_type results, std::string path)
194
{
195
  if(ec)
19✔
196
  {
NEW
197
    m_connected = false;
×
NEW
198
    on_fail();
×
NEW
199
    return;
×
200
  }
201

202
  boost::beast::get_lowest_layer(*m_ws).async_connect(
38✔
203
      results,
204
      [this, path = std::move(path)](
38✔
205
          boost::beast::error_code ec,
206
          boost::asio::ip::tcp::resolver::results_type::endpoint_type ep) {
207
    on_connect(ec, ep, std::move(path));
19✔
208
  });
19✔
209
}
210

211
void websocket_client_beast::on_connect(
19✔
212
    boost::beast::error_code ec,
213
    boost::asio::ip::tcp::resolver::results_type::endpoint_type,
214
    std::string path)
215
{
216
  if(ec)
19✔
217
  {
NEW
218
    m_connected = false;
×
NEW
219
    on_fail();
×
NEW
220
    return;
×
221
  }
222

223
  boost::beast::get_lowest_layer(*m_ws).expires_never();
19✔
224
  boost::asio::ip::tcp::no_delay opt(true);
19✔
225
  try
226
  {
227
    boost::beast::get_lowest_layer(*m_ws).socket().set_option(opt);
19✔
228
  }
NEW
229
  catch(...)
×
230
  {
NEW
231
  }
×
232

233
  // Use host:port for the Host header
234
  auto host = m_host + ":" + std::to_string(
38✔
235
      boost::beast::get_lowest_layer(*m_ws).socket().remote_endpoint().port());
38✔
236

237
  m_ws->async_handshake(
38✔
238
      host, path,
239
      [this](boost::beast::error_code ec) { on_handshake(ec); });
38✔
240
}
19✔
241

242
void websocket_client_beast::on_handshake(boost::beast::error_code ec)
19✔
243
{
244
  if(ec)
19✔
245
  {
NEW
246
    m_connected = false;
×
NEW
247
    on_fail();
×
NEW
248
    return;
×
249
  }
250

251
  m_open = true;
19✔
252
  on_open();
19✔
253

254
  // Start reading
255
  do_read();
19✔
256
}
257

258
void websocket_client_beast::do_read()
139,782✔
259
{
260
  m_buffer.clear();
139,782✔
261
  m_ws->async_read(
139,782✔
262
      m_buffer,
139,782✔
263
      [this](boost::beast::error_code ec, std::size_t bytes) {
139,782✔
264
    on_read(ec, bytes);
139,766✔
265
  });
139,766✔
266
}
139,782✔
267

268
void websocket_client_beast::on_read(boost::beast::error_code ec, std::size_t)
139,766✔
269
{
270
  if(ec)
139,766✔
271
  {
272
    m_open = false;
3✔
273
    on_close();
3✔
274
    return;
3✔
275
  }
276

277
  if(m_on_message)
139,763✔
278
  {
279
    auto opcode = m_ws->got_text() ? ws_opcode::text : ws_opcode::binary;
139,763✔
280
    auto data = boost::beast::buffers_to_string(m_buffer.data());
139,763✔
281
    ws_connection_handle hdl; // not used for client
139,763✔
282
    m_on_message(hdl, opcode, data);
139,763✔
283
  }
139,763✔
284

285
  do_read();
139,763✔
286
}
287

288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc