mirror of
https://github.com/CrowCpp/Crow.git
synced 2026-01-18 16:31:17 +01:00
Make websocket close include the status code (#854)
added status code to on_close function, status code will be sent now
This commit is contained in:
@@ -9,7 +9,7 @@ A websocket route differs from a normal route quite a bit. It uses a slightly al
|
||||
- `#!cpp onopen([&](crow::websocket::connection& conn){handler code goes here})`
|
||||
- `#!cpp onmessage([&](crow::websocket::connection& conn, const std::string& message, bool is_binary){handler code goes here})`
|
||||
- `#!cpp onerror([&](crow::websocket::connection& conn, const std::string& error_message){handler code goes here})`
|
||||
- `#!cpp onclose([&](crow::websocket::connection& conn, const std::string& reason){handler code goes here})`
|
||||
- `#!cpp onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t with_status_code ){handler code goes here})`
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -25,7 +25,7 @@ CROW_WEBSOCKET_ROUTE(app, "/ws")
|
||||
.onopen([&](crow::websocket::connection& conn){
|
||||
do_something();
|
||||
})
|
||||
.onclose([&](crow::websocket::connection& conn, const std::string& reason){
|
||||
.onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t){
|
||||
do_something();
|
||||
})
|
||||
.onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary){
|
||||
|
||||
@@ -16,7 +16,7 @@ int main()
|
||||
std::lock_guard<std::mutex> _(mtx);
|
||||
users.insert(&conn);
|
||||
})
|
||||
.onclose([&](crow::websocket::connection& conn, const std::string& reason) {
|
||||
.onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t) {
|
||||
CROW_LOG_INFO << "websocket connection closed: " << reason;
|
||||
std::lock_guard<std::mutex> _(mtx);
|
||||
users.erase(&conn);
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
* .onopen([&](crow::websocket::connection& conn){
|
||||
* do_something();
|
||||
* })
|
||||
* .onclose([&](crow::websocket::connection& conn, const std::string& reason){
|
||||
* .onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t){
|
||||
* do_something();
|
||||
* })
|
||||
* .onmessage([&](crow::websocket::connection&, const std::string& data, bool is_binary){
|
||||
|
||||
@@ -517,7 +517,7 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
App* app_;
|
||||
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&)> close_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<bool(const crow::request&, void**)> accept_handler_;
|
||||
uint64_t max_payload_;
|
||||
|
||||
@@ -33,6 +33,30 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
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
|
||||
{
|
||||
@@ -40,7 +64,7 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
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") = 0;
|
||||
virtual void close(std::string const& msg = "quit", uint16_t status_code = CloseStatusCode::NormalClosure) = 0;
|
||||
virtual std::string get_remote_ip() = 0;
|
||||
virtual ~connection() = default;
|
||||
|
||||
@@ -88,7 +112,7 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
Connection(const crow::request& req, 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&)> close_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<bool(const crow::request&, void**)> accept_handler):
|
||||
adaptor_(std::move(adaptor)),
|
||||
@@ -213,18 +237,22 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
|
||||
///
|
||||
/// Sets a flag to destroy the object once the message is sent.
|
||||
void close(std::string const& msg) override
|
||||
void close(std::string const& msg, uint16_t status_code) override
|
||||
{
|
||||
dispatch([this, msg]() mutable {
|
||||
dispatch([this, msg, status_code]() mutable {
|
||||
has_sent_close_ = true;
|
||||
if (has_recv_close_ && !is_close_handler_called_)
|
||||
{
|
||||
is_close_handler_called_ = true;
|
||||
if (close_handler_)
|
||||
close_handler_(*this, msg);
|
||||
close_handler_(*this, msg, status_code);
|
||||
}
|
||||
auto header = build_header(0x8, msg.size());
|
||||
auto header = build_header(0x8, msg.size() + 2);
|
||||
char status_buf[2];
|
||||
*(uint16_t*)(status_buf) = htons(status_code);
|
||||
|
||||
write_buffers_.emplace_back(std::move(header));
|
||||
write_buffers_.emplace_back(std::string(status_buf, 2));
|
||||
write_buffers_.emplace_back(msg);
|
||||
do_write();
|
||||
});
|
||||
@@ -344,7 +372,7 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
adaptor_.close();
|
||||
if (error_handler_)
|
||||
error_handler_(*this, "Client connection not masked.");
|
||||
check_destroy();
|
||||
check_destroy(CloseStatusCode::UnacceptableData);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -455,7 +483,7 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
adaptor_.close();
|
||||
if (error_handler_)
|
||||
error_handler_(*this, "Message length exceeds maximum payload.");
|
||||
check_destroy();
|
||||
check_destroy(MessageTooBig);
|
||||
}
|
||||
else if (has_mask_)
|
||||
{
|
||||
@@ -601,21 +629,36 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
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_);
|
||||
close(fragment_.substr(message_start), status_code);
|
||||
}
|
||||
else
|
||||
{
|
||||
adaptor_.shutdown_readwrite();
|
||||
adaptor_.close();
|
||||
|
||||
close_connection_ = true;
|
||||
if (!is_close_handler_called_)
|
||||
{
|
||||
if (close_handler_)
|
||||
close_handler_(*this, fragment_);
|
||||
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;
|
||||
}
|
||||
@@ -678,12 +721,13 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
}
|
||||
|
||||
/// Destroy the Connection.
|
||||
void check_destroy()
|
||||
void check_destroy(websocket::CloseStatusCode code = CloseStatusCode::ClosedAbnormally)
|
||||
{
|
||||
//if (has_sent_close_ && has_recv_close_)
|
||||
// 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");
|
||||
close_handler_(*this, "uncleanly", code);
|
||||
handler_->remove_websocket(this);
|
||||
if (sending_buffers_.empty() && !is_reading)
|
||||
delete this;
|
||||
@@ -750,7 +794,7 @@ namespace crow // NOTE: Already documented in "crow/app.h"
|
||||
|
||||
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&)> close_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<bool(const crow::request&, void**)> accept_handler_;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ void define_endpoints(crow::SimpleApp& app)
|
||||
return true;
|
||||
})
|
||||
.onopen([](crow::websocket::connection&) {})
|
||||
.onclose([](crow::websocket::connection&, const std::string&) {});
|
||||
.onclose([](crow::websocket::connection&, const std::string&, uint16_t) {});
|
||||
}
|
||||
|
||||
int main()
|
||||
|
||||
@@ -2669,10 +2669,12 @@ TEST_CASE("websocket")
|
||||
conn.send_ping("");
|
||||
else if (!isbin && message == "Hello")
|
||||
conn.send_text("Hello back");
|
||||
else if (!isbin && message.empty())
|
||||
conn.send_text("");
|
||||
else if (isbin && message == "Hello bin")
|
||||
conn.send_binary("Hello back bin");
|
||||
})
|
||||
.onclose([&](websocket::connection&, const std::string&) {
|
||||
.onclose([&](websocket::connection&, const std::string&, uint16_t) {
|
||||
CROW_LOG_INFO << "Closing websocket";
|
||||
});
|
||||
|
||||
@@ -2731,6 +2733,17 @@ TEST_CASE("websocket")
|
||||
std::string checkstring(std::string(buf).substr(0, 12));
|
||||
CHECK(checkstring == "\x81\x0AHello back");
|
||||
}
|
||||
//----------Empty Text----------
|
||||
{
|
||||
std::fill_n(buf, 2048, 0);
|
||||
char text_message[2 + 0 + 1]("\x81\x00");
|
||||
|
||||
c.send(asio::buffer(text_message, 2));
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
std::string checkstring(std::string(buf).substr(0, 2));
|
||||
CHECK(checkstring == text_message);
|
||||
}
|
||||
//----------Binary----------
|
||||
{
|
||||
std::fill_n(buf, 2048, 0);
|
||||
@@ -2809,6 +2822,192 @@ TEST_CASE("websocket")
|
||||
app.stop();
|
||||
} // websocket
|
||||
|
||||
TEST_CASE("websocket_close")
|
||||
{
|
||||
static std::string http_message = "GET /ws HTTP/1.1\r\nConnection: keep-alive, Upgrade\r\nupgrade: websocket\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\nHost: localhost\r\n\r\n";
|
||||
|
||||
static bool connected{false};
|
||||
websocket::connection* connection = nullptr;
|
||||
uint32_t close_calls = 0;
|
||||
uint16_t last_status_code = 0;
|
||||
CROW_LOG_INFO << "Setting up app!\n";
|
||||
SimpleApp app;
|
||||
|
||||
CROW_WEBSOCKET_ROUTE(app, "/ws")
|
||||
.onaccept([&](const crow::request& req, void**) {
|
||||
CROW_LOG_INFO << "Accepted websocket with URL " << req.url;
|
||||
return true;
|
||||
})
|
||||
.onopen([&](websocket::connection& conn) {
|
||||
connected = true;
|
||||
connection = &conn;
|
||||
CROW_LOG_INFO << "Connected websocket and value is " << connected;
|
||||
})
|
||||
.onmessage([&](websocket::connection& conn, const std::string& message, bool) {
|
||||
CROW_LOG_INFO << "Message is \"" << message << '\"';
|
||||
if (message == "quit-default")
|
||||
conn.close();
|
||||
else if (message == "quit-custom")
|
||||
conn.close("custom", crow::websocket::StartStatusCodesForPrivateUse + 10u);
|
||||
})
|
||||
.onclose([&](websocket::connection& conn, const std::string&, uint16_t status_code) {
|
||||
// There should just be one connection
|
||||
CHECK(&conn == connection);
|
||||
CHECK_FALSE(conn.get_remote_ip().empty());
|
||||
++close_calls;
|
||||
last_status_code = status_code;
|
||||
CROW_LOG_INFO << "Closing websocket";
|
||||
});
|
||||
|
||||
app.validate();
|
||||
|
||||
CROW_LOG_WARNING << "Starting app!\n";
|
||||
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(45453).run_async();
|
||||
app.wait_for_server_start();
|
||||
CROW_LOG_WARNING << "App started!\n";
|
||||
asio::io_service is;
|
||||
|
||||
asio::ip::tcp::socket c(is);
|
||||
c.connect(asio::ip::tcp::endpoint(
|
||||
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45453));
|
||||
|
||||
CROW_LOG_WARNING << "Connected!\n";
|
||||
|
||||
char buf[2048];
|
||||
|
||||
//----------Handshake----------
|
||||
{
|
||||
std::fill_n(buf, 2048, 0);
|
||||
c.send(asio::buffer(http_message));
|
||||
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK(connected);
|
||||
}
|
||||
|
||||
CHECK(close_calls == 0);
|
||||
|
||||
SECTION("normal close from client")
|
||||
{
|
||||
std::fill_n(buf, 2048, 0);
|
||||
// Close message with, len = 2, status code = 1000
|
||||
char close_message[5]("\x88\x02\x03\xE8");
|
||||
c.send(asio::buffer(close_message, 4));
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK((int)(unsigned char)buf[0] == 0x88);
|
||||
CHECK((int)(unsigned char)buf[1] == 0x02);
|
||||
CHECK((int)(unsigned char)buf[2] == 0x03);
|
||||
CHECK((int)(unsigned char)buf[3] == 0xE8);
|
||||
|
||||
CHECK(close_calls == 1);
|
||||
CHECK(last_status_code == 1000);
|
||||
}
|
||||
|
||||
SECTION("empty close from client")
|
||||
{
|
||||
std::fill_n(buf, 2048, 0);
|
||||
// Close message with, len = 0, status code = N/A -> To application give no status code present
|
||||
char close_message[3]("\x88\x00");
|
||||
|
||||
c.send(asio::buffer(close_message, 2));
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK((int)(unsigned char)buf[0] == 0x88);
|
||||
|
||||
CHECK(close_calls == 1);
|
||||
CHECK(last_status_code == crow::websocket::NoStatusCodePresent);
|
||||
}
|
||||
|
||||
SECTION("close with message from client")
|
||||
{
|
||||
std::fill_n(buf, 2048, 0);
|
||||
// Close message with, len = 2, status code = 1001
|
||||
char close_message[9]("\x88\x06\x03\xE9" "fail");
|
||||
|
||||
c.send(asio::buffer(close_message, 8));
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK((int)(unsigned char)buf[0] == 0x88);
|
||||
CHECK((int)(unsigned char)buf[1] == 0x06);
|
||||
CHECK((int)(unsigned char)buf[2] == 0x03);
|
||||
CHECK((int)(unsigned char)buf[3] == 0xE9);
|
||||
std::string checkstring(std::string(buf).substr(4, 4));
|
||||
CHECK(checkstring == "fail");
|
||||
|
||||
CHECK(close_calls == 1);
|
||||
CHECK(last_status_code == 1001);
|
||||
}
|
||||
|
||||
SECTION("normal close from server")
|
||||
{
|
||||
//----------Text----------
|
||||
std::fill_n(buf, 2048, 0);
|
||||
char text_message[2 + 12 + 1]("\x81\x0C"
|
||||
"quit-default");
|
||||
|
||||
c.send(asio::buffer(text_message, 14));
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK((int)(unsigned char)buf[0] == 0x88);
|
||||
// length is message + 2 for status code
|
||||
CHECK((int)(unsigned char)buf[1] == 0x6);
|
||||
|
||||
uint16_t expected_code = websocket::NormalClosure;
|
||||
CHECK((int)(unsigned char)buf[2] == expected_code >> 8);
|
||||
CHECK((int)(unsigned char)buf[3] == (expected_code & 0xff));
|
||||
std::string checkstring(std::string(buf).substr(4, 4));
|
||||
CHECK(checkstring == "quit");
|
||||
|
||||
CHECK(close_calls == 0);
|
||||
|
||||
// Reply with client close
|
||||
char client_close_response[9]("\x88\x06\x0\x0quit");
|
||||
client_close_response[2] = buf[2];
|
||||
client_close_response[3] = buf[3];
|
||||
|
||||
c.send(asio::buffer(client_close_response, 8));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK(close_calls == 1);
|
||||
CHECK(last_status_code == expected_code);
|
||||
}
|
||||
|
||||
SECTION("custom close from server")
|
||||
{
|
||||
//----------Text----------
|
||||
std::fill_n(buf, 2048, 0);
|
||||
char text_message[2 + 11 + 1]("\x81\x0B"
|
||||
"quit-custom");
|
||||
|
||||
c.send(asio::buffer(text_message, 13));
|
||||
c.receive(asio::buffer(buf, 2048));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK((int)(unsigned char)buf[0] == 0x88);
|
||||
// length is message + 2 for status code
|
||||
CHECK((int)(unsigned char)buf[1] == 0x8);
|
||||
uint16_t expected_code = 4010;
|
||||
CHECK((int)(unsigned char)buf[2] == expected_code >> 8);
|
||||
CHECK((int)(unsigned char)buf[3] == (expected_code & 0xff));
|
||||
std::string checkstring(std::string(buf).substr(4, 6));
|
||||
CHECK(checkstring == "custom");
|
||||
|
||||
CHECK(close_calls == 0);
|
||||
|
||||
// Reply with client close
|
||||
char client_close_response[11]("\x88\x08\x0\x0" "custom");
|
||||
client_close_response[2] = buf[2];
|
||||
client_close_response[3] = buf[3];
|
||||
|
||||
c.send(asio::buffer(client_close_response, 10));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
CHECK(close_calls == 1);
|
||||
CHECK(last_status_code == expected_code);
|
||||
}
|
||||
|
||||
CROW_LOG_WARNING << "Stopping app!\n";
|
||||
app.stop();
|
||||
}
|
||||
|
||||
TEST_CASE("websocket_missing_host")
|
||||
{
|
||||
static std::string http_message = "GET /ws HTTP/1.1\r\nConnection: keep-alive, Upgrade\r\nupgrade: websocket\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\nSec-WebSocket-Version: 13\r\n\r\n";
|
||||
@@ -2835,7 +3034,7 @@ TEST_CASE("websocket_missing_host")
|
||||
else if (isbin && message == "Hello bin")
|
||||
conn.send_binary("Hello back bin");
|
||||
})
|
||||
.onclose([&](websocket::connection&, const std::string&) {
|
||||
.onclose([&](websocket::connection&, const std::string&, uint16_t) {
|
||||
CROW_LOG_INFO << "Closing websocket";
|
||||
});
|
||||
|
||||
@@ -2887,7 +3086,7 @@ TEST_CASE("websocket_max_payload")
|
||||
else if (isbin && message == "Hello bin")
|
||||
conn.send_binary("Hello back bin");
|
||||
})
|
||||
.onclose([&](websocket::connection&, const std::string&) {
|
||||
.onclose([&](websocket::connection&, const std::string&, uint16_t) {
|
||||
CROW_LOG_INFO << "Closing websocket";
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user