855 lines
37 KiB
C++
855 lines
37 KiB
C++
#pragma once
|
|
#include <array>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <thread>
|
|
#include "crow/http_response.h"
|
|
#include "crow/logging.h"
|
|
#include "crow/socket_adaptors.h"
|
|
#include "crow/http_request.h"
|
|
#include "crow/TinySHA1.hpp"
|
|
#include "crow/utility.h"
|
|
|
|
namespace crow // NOTE: Already documented in "crow/app.h"
|
|
{
|
|
#ifdef CROW_USE_BOOST
|
|
namespace asio = boost::asio;
|
|
using error_code = boost::system::error_code;
|
|
#else
|
|
using error_code = asio::error_code;
|
|
#endif
|
|
|
|
/**
|
|
* \namespace crow::websocket
|
|
* \brief Namespace that includes the \ref Connection class
|
|
* and \ref connection struct. Useful for WebSockets connection.
|
|
*
|
|
* Used specially in crow/websocket.h, crow/app.h and crow/routing.h
|
|
*/
|
|
namespace websocket
|
|
{
|
|
enum class WebSocketReadState
|
|
{
|
|
MiniHeader,
|
|
Len16,
|
|
Len64,
|
|
Mask,
|
|
Payload,
|
|
};
|
|
|
|
// Codes taken from https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1
|
|
enum CloseStatusCode : uint16_t {
|
|
NormalClosure = 1000,
|
|
EndpointGoingAway = 1001,
|
|
ProtocolError = 1002,
|
|
UnacceptableData = 1003,
|
|
InconsistentData = 1007,
|
|
PolicyViolated = 1008,
|
|
MessageTooBig = 1009,
|
|
ExtensionsNotNegotiated = 1010,
|
|
UnexpectedCondition = 1011,
|
|
|
|
// Reserved for applications only, should not send/receive these to/from clients
|
|
NoStatusCodePresent = 1005,
|
|
ClosedAbnormally = 1006,
|
|
TLSHandshakeFailure = 1015,
|
|
|
|
StartStatusCodesForLibraries = 3000,
|
|
StartStatusCodesForPrivateUse = 4000,
|
|
// Status code should be between 1000 and 4999 inclusive
|
|
StartStatusCodes = NormalClosure,
|
|
EndStatusCodes = 4999,
|
|
};
|
|
|
|
/// A base class for websocket connection.
|
|
struct connection
|
|
{
|
|
virtual void send_binary(std::string msg) = 0;
|
|
virtual void send_text(std::string msg) = 0;
|
|
virtual void send_ping(std::string msg) = 0;
|
|
virtual void send_pong(std::string msg) = 0;
|
|
virtual void close(std::string const& msg = "quit", uint16_t status_code = CloseStatusCode::NormalClosure) = 0;
|
|
virtual std::string get_remote_ip() = 0;
|
|
virtual std::string get_subprotocol() const = 0;
|
|
virtual ~connection() = default;
|
|
|
|
void userdata(void* u) { userdata_ = u; }
|
|
void* userdata() { return userdata_; }
|
|
|
|
private:
|
|
void* userdata_;
|
|
};
|
|
|
|
// Modified version of the illustration in RFC6455 Section-5.2
|
|
//
|
|
//
|
|
// 0 1 2 3 -byte
|
|
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 -bit
|
|
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
|
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
|
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
|
// |N|V|V|V| |S| | (if payload len==126/127) |
|
|
// | |1|2|3| |K| | |
|
|
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
|
// | Extended payload length continued, if payload len == 127 |
|
|
// + - - - - - - - - - - - - - - - +-------------------------------+
|
|
// | |Masking-key, if MASK set to 1 |
|
|
// +-------------------------------+-------------------------------+
|
|
// | Masking-key (continued) | Payload Data |
|
|
// +-------------------------------- - - - - - - - - - - - - - - - +
|
|
// : Payload Data continued ... :
|
|
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
|
// | Payload Data continued ... |
|
|
// +---------------------------------------------------------------+
|
|
//
|
|
|
|
/// A websocket connection.
|
|
|
|
template<typename Adaptor, typename Handler>
|
|
class Connection : public connection, public std::enable_shared_from_this<Connection<Adaptor, Handler>>
|
|
{
|
|
public:
|
|
/// Factory for a connection.
|
|
///
|
|
/// Requires a request with an "Upgrade: websocket" header.<br>
|
|
/// Automatically handles the handshake.
|
|
static void create(const crow::request& req, Adaptor adaptor, Handler* handler,
|
|
uint64_t max_payload, const std::vector<std::string>& subprotocols,
|
|
std::function<void(crow::websocket::connection&)> open_handler,
|
|
std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler,
|
|
std::function<void(crow::websocket::connection&, const std::string&, uint16_t)> close_handler,
|
|
std::function<void(crow::websocket::connection&, const std::string&)> error_handler,
|
|
std::function<void(const crow::request&, std::optional<crow::response>&, void**)> accept_handler,
|
|
bool mirror_protocols)
|
|
{
|
|
auto conn = std::shared_ptr<Connection>(new Connection(std::move(adaptor),
|
|
handler, max_payload,
|
|
std::move(open_handler),
|
|
std::move(message_handler),
|
|
std::move(close_handler),
|
|
std::move(error_handler),
|
|
std::move(accept_handler)));
|
|
|
|
// Perform handshake validation
|
|
if (!utility::string_equals(req.get_header_value("upgrade"), "websocket"))
|
|
{
|
|
conn->adaptor_.close();
|
|
return;
|
|
}
|
|
|
|
std::string requested_subprotocols_header = req.get_header_value("Sec-WebSocket-Protocol");
|
|
if (!subprotocols.empty() || !requested_subprotocols_header.empty())
|
|
{
|
|
auto requested_subprotocols = utility::split(requested_subprotocols_header, ", ");
|
|
auto subprotocol = utility::find_first_of(subprotocols.begin(), subprotocols.end(), requested_subprotocols.begin(), requested_subprotocols.end());
|
|
if (subprotocol != subprotocols.end())
|
|
{
|
|
conn->subprotocol_ = *subprotocol;
|
|
}
|
|
}
|
|
|
|
if (mirror_protocols & !requested_subprotocols_header.empty())
|
|
{
|
|
conn->subprotocol_ = requested_subprotocols_header;
|
|
}
|
|
|
|
if (conn->accept_handler_)
|
|
{
|
|
void* ud = nullptr;
|
|
std::optional<crow::response> res;
|
|
conn->accept_handler_(req, res, &ud);
|
|
if (res)
|
|
{
|
|
std::vector<asio::const_buffer> buffers;
|
|
auto server_name = "";
|
|
std::string content_length_buffer;
|
|
res->write_header_into_buffer(buffers, content_length_buffer, req.keep_alive, server_name);
|
|
buffers.emplace_back(res->body.data(), res->body.size());
|
|
error_code ec;
|
|
asio::write(conn->adaptor_.socket(), buffers, ec);
|
|
conn->adaptor_.close();
|
|
return;
|
|
}
|
|
conn->userdata(ud);
|
|
}
|
|
|
|
// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
|
// Sec-WebSocket-Version: 13
|
|
std::string magic = req.get_header_value("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
sha1::SHA1 s;
|
|
s.processBytes(magic.data(), magic.size());
|
|
uint8_t digest[20];
|
|
s.getDigestBytes(digest);
|
|
|
|
conn->handler_->add_websocket(conn);
|
|
conn->start(crow::utility::base64encode((unsigned char*)digest, 20));
|
|
}
|
|
|
|
~Connection() noexcept override = default;
|
|
|
|
template<typename Callable>
|
|
struct WeakWrappedMessage
|
|
{
|
|
Callable callable;
|
|
std::weak_ptr<void> watch;
|
|
|
|
void operator()()
|
|
{
|
|
if (auto anchor = watch.lock())
|
|
{
|
|
std::move(callable)();
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Send data through the socket.
|
|
template<typename CompletionHandler>
|
|
void dispatch(CompletionHandler&& handler)
|
|
{
|
|
asio::dispatch(adaptor_.get_io_context(),
|
|
WeakWrappedMessage<typename std::decay<CompletionHandler>::type>{
|
|
std::forward<CompletionHandler>(handler), anchor_});
|
|
}
|
|
|
|
/// Send data through the socket and return immediately.
|
|
template<typename CompletionHandler>
|
|
void post(CompletionHandler&& handler)
|
|
{
|
|
asio::post(adaptor_.get_io_context(),
|
|
WeakWrappedMessage<typename std::decay<CompletionHandler>::type>{
|
|
std::forward<CompletionHandler>(handler), anchor_});
|
|
}
|
|
|
|
/// Send a "Ping" message.
|
|
|
|
///
|
|
/// Usually invoked to check if the other point is still online.
|
|
void send_ping(std::string msg) override
|
|
{
|
|
send_data(0x9, std::move(msg));
|
|
}
|
|
|
|
/// Send a "Pong" message.
|
|
|
|
///
|
|
/// Usually automatically invoked as a response to a "Ping" message.
|
|
void send_pong(std::string msg) override
|
|
{
|
|
send_data(0xA, std::move(msg));
|
|
}
|
|
|
|
/// Send a binary encoded message.
|
|
void send_binary(std::string msg) override
|
|
{
|
|
send_data(0x2, std::move(msg));
|
|
}
|
|
|
|
/// Send a plaintext message.
|
|
void send_text(std::string msg) override
|
|
{
|
|
send_data(0x1, std::move(msg));
|
|
}
|
|
|
|
/// Send a close signal.
|
|
|
|
///
|
|
/// Sets a flag to destroy the object once the message is sent.
|
|
void close(std::string const& msg, uint16_t status_code) override
|
|
{
|
|
dispatch([shared_this = this->shared_from_this(), msg, status_code]() mutable {
|
|
shared_this->has_sent_close_ = true;
|
|
if (shared_this->has_recv_close_ && !shared_this->is_close_handler_called_)
|
|
{
|
|
shared_this->is_close_handler_called_ = true;
|
|
if (shared_this->close_handler_)
|
|
shared_this->close_handler_(*shared_this, msg, status_code);
|
|
}
|
|
auto header = shared_this->build_header(0x8, msg.size() + 2);
|
|
char status_buf[2];
|
|
*(uint16_t*)(status_buf) = htons(status_code);
|
|
|
|
shared_this->write_buffers_.emplace_back(std::move(header));
|
|
shared_this->write_buffers_.emplace_back(std::string(status_buf, 2));
|
|
shared_this->write_buffers_.emplace_back(msg);
|
|
shared_this->do_write();
|
|
});
|
|
}
|
|
|
|
std::string get_remote_ip() override
|
|
{
|
|
return adaptor_.address();
|
|
}
|
|
|
|
void set_max_payload_size(uint64_t payload)
|
|
{
|
|
max_payload_bytes_ = payload;
|
|
}
|
|
|
|
/// Returns the matching client/server subprotocol, empty string if none matched.
|
|
std::string get_subprotocol() const override
|
|
{
|
|
return subprotocol_;
|
|
}
|
|
|
|
protected:
|
|
/// Generate the websocket headers using an opcode and the message size (in bytes).
|
|
std::string build_header(int opcode, size_t size)
|
|
{
|
|
char buf[2 + 8] = "\x80\x00";
|
|
buf[0] += opcode;
|
|
if (size < 126)
|
|
{
|
|
buf[1] += static_cast<char>(size);
|
|
return {buf, buf + 2};
|
|
}
|
|
else if (size < 0x10000)
|
|
{
|
|
buf[1] += 126;
|
|
*(uint16_t*)(buf + 2) = htons(static_cast<uint16_t>(size));
|
|
return {buf, buf + 4};
|
|
}
|
|
else
|
|
{
|
|
buf[1] += 127;
|
|
*reinterpret_cast<uint64_t*>(buf + 2) = ((1 == htonl(1)) ? static_cast<uint64_t>(size) : (static_cast<uint64_t>(htonl((size)&0xFFFFFFFF)) << 32) | htonl(static_cast<uint64_t>(size) >> 32));
|
|
return {buf, buf + 10};
|
|
}
|
|
}
|
|
|
|
/// Send the HTTP upgrade response.
|
|
|
|
///
|
|
/// Finishes the handshake process, then starts reading messages from the socket.
|
|
void start(std::string&& hello)
|
|
{
|
|
static const std::string header =
|
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
|
"Upgrade: websocket\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"Sec-WebSocket-Accept: ";
|
|
write_buffers_.emplace_back(header);
|
|
write_buffers_.emplace_back(std::move(hello));
|
|
write_buffers_.emplace_back(crlf);
|
|
if (!subprotocol_.empty())
|
|
{
|
|
write_buffers_.emplace_back("Sec-WebSocket-Protocol: ");
|
|
write_buffers_.emplace_back(subprotocol_);
|
|
write_buffers_.emplace_back(crlf);
|
|
}
|
|
write_buffers_.emplace_back(crlf);
|
|
do_write();
|
|
if (open_handler_)
|
|
open_handler_(*this);
|
|
do_read();
|
|
}
|
|
|
|
/// Read a websocket message.
|
|
|
|
///
|
|
/// Involves:<br>
|
|
/// Handling headers (opcodes, size).<br>
|
|
/// Unmasking the payload.<br>
|
|
/// Reading the actual payload.<br>
|
|
void do_read()
|
|
{
|
|
if (has_sent_close_ && has_recv_close_)
|
|
{
|
|
close_connection_ = true;
|
|
adaptor_.shutdown_readwrite();
|
|
adaptor_.close();
|
|
check_destroy();
|
|
return;
|
|
}
|
|
|
|
is_reading = true;
|
|
switch (state_)
|
|
{
|
|
case WebSocketReadState::MiniHeader:
|
|
{
|
|
mini_header_ = 0;
|
|
//asio::async_read(adaptor_.socket(), asio::buffer(&mini_header_, 1),
|
|
adaptor_.socket().async_read_some(
|
|
asio::buffer(&mini_header_, 2),
|
|
[shared_this = this->shared_from_this()](const error_code& ec, std::size_t
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
bytes_transferred
|
|
#endif
|
|
)
|
|
|
|
{
|
|
shared_this->is_reading = false;
|
|
shared_this->mini_header_ = ntohs(shared_this->mini_header_);
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
|
|
if (!ec && bytes_transferred != 2)
|
|
{
|
|
throw std::runtime_error("WebSocket:MiniHeader:async_read fail:asio bug?");
|
|
}
|
|
#endif
|
|
|
|
if (!ec)
|
|
{
|
|
if ((shared_this->mini_header_ & 0x80) == 0x80)
|
|
shared_this->has_mask_ = true;
|
|
else //if the websocket specification is enforced and the message isn't masked, terminate the connection
|
|
{
|
|
#ifndef CROW_ENFORCE_WS_SPEC
|
|
shared_this->has_mask_ = false;
|
|
#else
|
|
shared_this->close_connection_ = true;
|
|
shared_this->adaptor_.shutdown_readwrite();
|
|
shared_this->adaptor_.close();
|
|
if (shared_this->error_handler_)
|
|
shared_this->error_handler_(*shared_this, "Client connection not masked.");
|
|
shared_this->check_destroy(CloseStatusCode::UnacceptableData);
|
|
#endif
|
|
}
|
|
|
|
if ((shared_this->mini_header_ & 0x7f) == 127)
|
|
{
|
|
shared_this->state_ = WebSocketReadState::Len64;
|
|
}
|
|
else if ((shared_this->mini_header_ & 0x7f) == 126)
|
|
{
|
|
shared_this->state_ = WebSocketReadState::Len16;
|
|
}
|
|
else
|
|
{
|
|
shared_this->remaining_length_ = shared_this->mini_header_ & 0x7f;
|
|
shared_this->state_ = WebSocketReadState::Mask;
|
|
}
|
|
shared_this->do_read();
|
|
}
|
|
else
|
|
{
|
|
shared_this->close_connection_ = true;
|
|
shared_this->adaptor_.shutdown_readwrite();
|
|
shared_this->adaptor_.close();
|
|
if (shared_this->error_handler_)
|
|
shared_this->error_handler_(*shared_this, ec.message());
|
|
shared_this->check_destroy();
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case WebSocketReadState::Len16:
|
|
{
|
|
remaining_length_ = 0;
|
|
remaining_length16_ = 0;
|
|
asio::async_read(
|
|
adaptor_.socket(), asio::buffer(&remaining_length16_, 2),
|
|
[shared_this = this->shared_from_this()](const error_code& ec, std::size_t
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
bytes_transferred
|
|
#endif
|
|
) {
|
|
shared_this->is_reading = false;
|
|
shared_this->remaining_length16_ = ntohs(shared_this->remaining_length16_);
|
|
shared_this->remaining_length_ = shared_this->remaining_length16_;
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
if (!ec && bytes_transferred != 2)
|
|
{
|
|
throw std::runtime_error("WebSocket:Len16:async_read fail:asio bug?");
|
|
}
|
|
#endif
|
|
|
|
if (!ec)
|
|
{
|
|
shared_this->state_ = WebSocketReadState::Mask;
|
|
shared_this->do_read();
|
|
}
|
|
else
|
|
{
|
|
shared_this->close_connection_ = true;
|
|
shared_this->adaptor_.shutdown_readwrite();
|
|
shared_this->adaptor_.close();
|
|
if (shared_this->error_handler_)
|
|
shared_this->error_handler_(*shared_this, ec.message());
|
|
shared_this->check_destroy();
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case WebSocketReadState::Len64:
|
|
{
|
|
asio::async_read(
|
|
adaptor_.socket(), asio::buffer(&remaining_length_, 8),
|
|
[shared_this = this->shared_from_this()](const error_code& ec, std::size_t
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
bytes_transferred
|
|
#endif
|
|
) {
|
|
shared_this->is_reading = false;
|
|
shared_this->remaining_length_ = ((1 == ntohl(1)) ? (shared_this->remaining_length_) : (static_cast<uint64_t>(ntohl((shared_this->remaining_length_)&0xFFFFFFFF)) << 32) | ntohl((shared_this->remaining_length_) >> 32));
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
if (!ec && bytes_transferred != 8)
|
|
{
|
|
throw std::runtime_error("WebSocket:Len16:async_read fail:asio bug?");
|
|
}
|
|
#endif
|
|
|
|
if (!ec)
|
|
{
|
|
shared_this->state_ = WebSocketReadState::Mask;
|
|
shared_this->do_read();
|
|
}
|
|
else
|
|
{
|
|
shared_this->close_connection_ = true;
|
|
shared_this->adaptor_.shutdown_readwrite();
|
|
shared_this->adaptor_.close();
|
|
if (shared_this->error_handler_)
|
|
shared_this->error_handler_(*shared_this, ec.message());
|
|
shared_this->check_destroy();
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case WebSocketReadState::Mask:
|
|
if (remaining_length_ > max_payload_bytes_)
|
|
{
|
|
close_connection_ = true;
|
|
adaptor_.close();
|
|
if (error_handler_)
|
|
error_handler_(*this, "Message length exceeds maximum payload.");
|
|
check_destroy(MessageTooBig);
|
|
}
|
|
else if (has_mask_)
|
|
{
|
|
asio::async_read(
|
|
adaptor_.socket(), asio::buffer((char*)&mask_, 4),
|
|
[shared_this = this->shared_from_this()](const error_code& ec, std::size_t
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
bytes_transferred
|
|
#endif
|
|
) {
|
|
shared_this->is_reading = false;
|
|
#ifdef CROW_ENABLE_DEBUG
|
|
if (!ec && bytes_transferred != 4)
|
|
{
|
|
throw std::runtime_error("WebSocket:Mask:async_read fail:asio bug?");
|
|
}
|
|
#endif
|
|
|
|
if (!ec)
|
|
{
|
|
shared_this->state_ = WebSocketReadState::Payload;
|
|
shared_this->do_read();
|
|
}
|
|
else
|
|
{
|
|
shared_this->close_connection_ = true;
|
|
if (shared_this->error_handler_)
|
|
shared_this->error_handler_(*shared_this, ec.message());
|
|
shared_this->adaptor_.shutdown_readwrite();
|
|
shared_this->adaptor_.close();
|
|
shared_this->check_destroy();
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
state_ = WebSocketReadState::Payload;
|
|
do_read();
|
|
}
|
|
break;
|
|
case WebSocketReadState::Payload:
|
|
{
|
|
auto to_read = static_cast<std::uint64_t>(buffer_.size());
|
|
if (remaining_length_ < to_read)
|
|
to_read = remaining_length_;
|
|
adaptor_.socket().async_read_some(
|
|
asio::buffer(buffer_, static_cast<std::size_t>(to_read)),
|
|
[shared_this = this->shared_from_this()](const error_code& ec, std::size_t bytes_transferred) {
|
|
shared_this->is_reading = false;
|
|
|
|
if (!ec)
|
|
{
|
|
shared_this->fragment_.insert(shared_this->fragment_.end(), shared_this->buffer_.begin(), shared_this->buffer_.begin() + bytes_transferred);
|
|
shared_this->remaining_length_ -= bytes_transferred;
|
|
if (shared_this->remaining_length_ == 0)
|
|
{
|
|
if (shared_this->handle_fragment())
|
|
{
|
|
shared_this->state_ = WebSocketReadState::MiniHeader;
|
|
shared_this->do_read();
|
|
}
|
|
}
|
|
else
|
|
shared_this->do_read();
|
|
}
|
|
else
|
|
{
|
|
shared_this->close_connection_ = true;
|
|
if (shared_this->error_handler_)
|
|
shared_this->error_handler_(*shared_this, ec.message());
|
|
shared_this->adaptor_.shutdown_readwrite();
|
|
shared_this->adaptor_.close();
|
|
shared_this->check_destroy();
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// Check if the FIN bit is set.
|
|
bool is_FIN()
|
|
{
|
|
return mini_header_ & 0x8000;
|
|
}
|
|
|
|
/// Extract the opcode from the header.
|
|
int opcode()
|
|
{
|
|
return (mini_header_ & 0x0f00) >> 8;
|
|
}
|
|
|
|
/// Process the payload fragment.
|
|
|
|
///
|
|
/// Unmasks the fragment, checks the opcode, merges fragments into 1 message body, and calls the appropriate handler.
|
|
bool handle_fragment()
|
|
{
|
|
if (has_mask_)
|
|
{
|
|
for (decltype(fragment_.length()) i = 0; i < fragment_.length(); i++)
|
|
{
|
|
fragment_[i] ^= ((char*)&mask_)[i % 4];
|
|
}
|
|
}
|
|
switch (opcode())
|
|
{
|
|
case 0: // Continuation
|
|
{
|
|
message_ += fragment_;
|
|
if (is_FIN())
|
|
{
|
|
if (message_handler_)
|
|
message_handler_(*this, message_, is_binary_);
|
|
message_.clear();
|
|
}
|
|
}
|
|
break;
|
|
case 1: // Text
|
|
{
|
|
is_binary_ = false;
|
|
message_ += fragment_;
|
|
if (is_FIN())
|
|
{
|
|
if (message_handler_)
|
|
message_handler_(*this, message_, is_binary_);
|
|
message_.clear();
|
|
}
|
|
}
|
|
break;
|
|
case 2: // Binary
|
|
{
|
|
is_binary_ = true;
|
|
message_ += fragment_;
|
|
if (is_FIN())
|
|
{
|
|
if (message_handler_)
|
|
message_handler_(*this, message_, is_binary_);
|
|
message_.clear();
|
|
}
|
|
}
|
|
break;
|
|
case 0x8: // Close
|
|
{
|
|
has_recv_close_ = true;
|
|
|
|
|
|
uint16_t status_code = NoStatusCodePresent;
|
|
std::string::size_type message_start = 2;
|
|
if (fragment_.size() >= 2)
|
|
{
|
|
status_code = ntohs(((uint16_t*)fragment_.data())[0]);
|
|
} else {
|
|
// no message will crash substr
|
|
message_start = 0;
|
|
}
|
|
|
|
if (!has_sent_close_)
|
|
{
|
|
close(fragment_.substr(message_start), status_code);
|
|
}
|
|
else
|
|
{
|
|
|
|
close_connection_ = true;
|
|
if (!is_close_handler_called_)
|
|
{
|
|
if (close_handler_)
|
|
close_handler_(*this, fragment_.substr(message_start), status_code);
|
|
is_close_handler_called_ = true;
|
|
}
|
|
adaptor_.shutdown_readwrite();
|
|
adaptor_.close();
|
|
|
|
// Close handler must have been called at this point so code does not matter
|
|
check_destroy();
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
case 0x9: // Ping
|
|
{
|
|
send_pong(fragment_);
|
|
}
|
|
break;
|
|
case 0xA: // Pong
|
|
{
|
|
pong_received_ = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
fragment_.clear();
|
|
return true;
|
|
}
|
|
|
|
/// Send the buffers' data through the socket.
|
|
|
|
///
|
|
/// Also destroys the object if the Close flag is set.
|
|
void do_write()
|
|
{
|
|
if (sending_buffers_.empty()) {
|
|
if (write_buffers_.empty()) return;
|
|
|
|
sending_buffers_.swap(write_buffers_);
|
|
std::vector<asio::const_buffer> buffers;
|
|
buffers.reserve(sending_buffers_.size());
|
|
for (auto &s: sending_buffers_)
|
|
{
|
|
buffers.emplace_back(asio::buffer(s));
|
|
}
|
|
auto watch = std::weak_ptr<void>{anchor_};
|
|
asio::async_write(
|
|
adaptor_.socket(), buffers,
|
|
[shared_this = this->shared_from_this(), watch](const error_code &ec, std::size_t /*bytes_transferred*/) {
|
|
auto anchor = watch.lock();
|
|
if (anchor == nullptr)
|
|
return;
|
|
|
|
if (!ec && !shared_this->close_connection_)
|
|
{
|
|
shared_this->sending_buffers_.clear();
|
|
if (!shared_this->write_buffers_.empty())
|
|
shared_this->do_write();
|
|
if (shared_this->has_sent_close_)
|
|
shared_this->close_connection_ = true;
|
|
}
|
|
else
|
|
{
|
|
shared_this->sending_buffers_.clear();
|
|
shared_this->close_connection_ = true;
|
|
shared_this->check_destroy();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Destroy the Connection.
|
|
void check_destroy(websocket::CloseStatusCode code = CloseStatusCode::ClosedAbnormally)
|
|
{
|
|
// Note that if the close handler was not yet called at this point we did not receive a close packet (or send one)
|
|
// and thus we use ClosedAbnormally unless instructed otherwise
|
|
if (!is_close_handler_called_)
|
|
{
|
|
if (close_handler_)
|
|
{
|
|
close_handler_(*this, "uncleanly", code);
|
|
}
|
|
}
|
|
|
|
handler_->remove_websocket(this->shared_from_this());
|
|
}
|
|
|
|
|
|
struct SendMessageType
|
|
{
|
|
std::string payload;
|
|
Connection* self;
|
|
int opcode;
|
|
|
|
void operator()()
|
|
{
|
|
self->send_data_impl(this);
|
|
}
|
|
};
|
|
|
|
void send_data_impl(SendMessageType* s)
|
|
{
|
|
auto header = build_header(s->opcode, s->payload.size());
|
|
write_buffers_.emplace_back(std::move(header));
|
|
write_buffers_.emplace_back(std::move(s->payload));
|
|
do_write();
|
|
}
|
|
|
|
void send_data(int opcode, std::string&& msg)
|
|
{
|
|
SendMessageType event_arg{
|
|
std::move(msg),
|
|
this,
|
|
opcode};
|
|
|
|
post(std::move(event_arg));
|
|
}
|
|
|
|
private:
|
|
Connection(Adaptor&& adaptor, Handler* handler, uint64_t max_payload,
|
|
std::function<void(crow::websocket::connection&)> open_handler,
|
|
std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler,
|
|
std::function<void(crow::websocket::connection&, const std::string&, uint16_t)> close_handler,
|
|
std::function<void(crow::websocket::connection&, const std::string&)> error_handler,
|
|
std::function<void(const crow::request&, std::optional<crow::response>&, void**)> accept_handler):
|
|
adaptor_(std::move(adaptor)),
|
|
handler_(handler),
|
|
max_payload_bytes_(max_payload),
|
|
open_handler_(std::move(open_handler)),
|
|
message_handler_(std::move(message_handler)),
|
|
close_handler_(std::move(close_handler)),
|
|
error_handler_(std::move(error_handler)),
|
|
accept_handler_(std::move(accept_handler))
|
|
{}
|
|
|
|
Adaptor adaptor_;
|
|
Handler* handler_;
|
|
|
|
std::vector<std::string> sending_buffers_;
|
|
std::vector<std::string> write_buffers_;
|
|
|
|
std::array<char, 4096> buffer_;
|
|
bool is_binary_;
|
|
std::string message_;
|
|
std::string fragment_;
|
|
WebSocketReadState state_{WebSocketReadState::MiniHeader};
|
|
uint16_t remaining_length16_{0};
|
|
uint64_t remaining_length_{0};
|
|
uint64_t max_payload_bytes_{UINT64_MAX};
|
|
std::string subprotocol_;
|
|
bool close_connection_{false};
|
|
bool is_reading{false};
|
|
bool has_mask_{false};
|
|
uint32_t mask_;
|
|
uint16_t mini_header_;
|
|
bool has_sent_close_{false};
|
|
bool has_recv_close_{false};
|
|
bool error_occurred_{false};
|
|
bool pong_received_{false};
|
|
bool is_close_handler_called_{false};
|
|
|
|
std::shared_ptr<void> anchor_ = std::make_shared<int>(); // Value is just for placeholding
|
|
|
|
std::function<void(crow::websocket::connection&)> open_handler_;
|
|
std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_;
|
|
std::function<void(crow::websocket::connection&, const std::string&, uint16_t status_code)> close_handler_;
|
|
std::function<void(crow::websocket::connection&, const std::string&)> error_handler_;
|
|
std::function<void(const crow::request&, std::optional<crow::response>&, void**)> accept_handler_;
|
|
};
|
|
} // namespace websocket
|
|
} // namespace crow
|