Merge branch 'master' into static_dir

This commit is contained in:
Farook Al-Sammarraie 2020-11-10 12:34:04 +03:00 committed by GitHub
commit 50670c290b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 287 additions and 49 deletions

View File

@ -38,13 +38,13 @@ PROJECT_NAME = Crow
# could be handy for archiving the generated documentation or if some version # could be handy for archiving the generated documentation or if some version
# control system is used. # control system is used.
PROJECT_NUMBER = 0.1 PROJECT_NUMBER = 0.2
# Using the PROJECT_BRIEF tag one can provide an optional one line description # Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a # for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short. # quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "C++ microframework for the web" PROJECT_BRIEF = "A C++ microframework for the web"
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included # With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55 # in the documentation. The maximum height of the logo should not exceed 55

View File

@ -121,12 +121,12 @@ namespace crow
///Set the server's log level ///Set the server's log level
/// ///
/// Possible values are: /// Possible values are:<br>
/// crow::LogLevel::Debug (0) /// crow::LogLevel::Debug (0)<br>
/// crow::LogLevel::Info (1) /// crow::LogLevel::Info (1)<br>
/// crow::LogLevel::Warning (2) /// crow::LogLevel::Warning (2)<br>
/// crow::LogLevel::Error (3) /// crow::LogLevel::Error (3)<br>
/// crow::LogLevel::Critical (4) /// crow::LogLevel::Critical (4)<br>
self_t& loglevel(crow::LogLevel level) self_t& loglevel(crow::LogLevel level)
{ {
crow::logger::setLogLevel(level); crow::logger::setLogLevel(level);

View File

@ -108,31 +108,43 @@ namespace crow
bool is_open() bool is_open()
{ {
return raw_socket().is_open(); return ssl_socket_ ? raw_socket().is_open() : false;
} }
void close() void close()
{ {
boost::system::error_code ec; if (is_open())
raw_socket().close(ec); {
boost::system::error_code ec;
raw_socket().close(ec);
}
} }
void shutdown_readwrite() void shutdown_readwrite()
{ {
boost::system::error_code ec; if (is_open())
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec); {
boost::system::error_code ec;
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec);
}
} }
void shutdown_write() void shutdown_write()
{ {
boost::system::error_code ec; if (is_open())
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_send, ec); {
boost::system::error_code ec;
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_send, ec);
}
} }
void shutdown_read() void shutdown_read()
{ {
boost::system::error_code ec; if (is_open())
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_receive, ec); {
boost::system::error_code ec;
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_receive, ec);
}
} }
boost::asio::io_service& get_io_service() boost::asio::io_service& get_io_service()

View File

@ -18,10 +18,13 @@ namespace crow
Payload, Payload,
}; };
///A base class for websocket connection.
struct connection struct connection
{ {
virtual void send_binary(const std::string& msg) = 0; virtual void send_binary(const std::string& msg) = 0;
virtual void send_text(const std::string& msg) = 0; virtual void send_text(const std::string& msg) = 0;
virtual void send_ping(const std::string& msg) = 0;
virtual void send_pong(const std::string& msg) = 0;
virtual void close(const std::string& msg = "quit") = 0; virtual void close(const std::string& msg = "quit") = 0;
virtual ~connection(){} virtual ~connection(){}
@ -32,10 +35,35 @@ namespace crow
void* userdata_; void* userdata_;
}; };
// 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> template <typename Adaptor>
class Connection : public connection class Connection : public connection
{ {
public: public:
/// Constructor for a connection.
///
/// Requires a request with an "Upgrade: websocket" header.<br>
/// Automatically handles the handshake.
Connection(const crow::request& req, Adaptor&& adaptor, Connection(const crow::request& req, Adaptor&& adaptor,
std::function<void(crow::websocket::connection&)> open_handler, 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&, bool)> message_handler,
@ -72,29 +100,49 @@ namespace crow
start(crow::utility::base64encode((char*)digest, 20)); start(crow::utility::base64encode((char*)digest, 20));
} }
/// Send data through the socket.
template<typename CompletionHandler> template<typename CompletionHandler>
void dispatch(CompletionHandler handler) void dispatch(CompletionHandler handler)
{ {
adaptor_.get_io_service().dispatch(handler); adaptor_.get_io_service().dispatch(handler);
} }
/// Send data through the socket and return immediately.
template<typename CompletionHandler> template<typename CompletionHandler>
void post(CompletionHandler handler) void post(CompletionHandler handler)
{ {
adaptor_.get_io_service().post(handler); adaptor_.get_io_service().post(handler);
} }
void send_pong(const std::string& msg) /// Send a "Ping" message.
///
/// Usually invoked to check if the other point is still online.
void send_ping(const std::string& msg) override
{ {
dispatch([this, msg]{ dispatch([this, msg]{
char buf[3] = "\x8A\x00"; auto header = build_header(0x9, msg.size());
buf[1] += msg.size(); write_buffers_.emplace_back(std::move(header));
write_buffers_.emplace_back(buf, buf+2);
write_buffers_.emplace_back(msg); write_buffers_.emplace_back(msg);
do_write(); do_write();
}); });
} }
/// Send a "Pong" message.
///
/// Usually automatically invoked as a response to a "Ping" message.
void send_pong(const std::string& msg) override
{
dispatch([this, msg]{
auto header = build_header(0xA, msg.size());
write_buffers_.emplace_back(std::move(header));
write_buffers_.emplace_back(msg);
do_write();
});
}
/// Send a binary encoded message.
void send_binary(const std::string& msg) override void send_binary(const std::string& msg) override
{ {
dispatch([this, msg]{ dispatch([this, msg]{
@ -105,6 +153,7 @@ namespace crow
}); });
} }
/// Send a plaintext message.
void send_text(const std::string& msg) override void send_text(const std::string& msg) override
{ {
dispatch([this, msg]{ dispatch([this, msg]{
@ -115,6 +164,10 @@ namespace crow
}); });
} }
/// Send a close signal.
///
/// Sets a flag to destroy the object once the message is sent.
void close(const std::string& msg) override void close(const std::string& msg) override
{ {
dispatch([this, msg]{ dispatch([this, msg]{
@ -134,6 +187,7 @@ namespace crow
protected: protected:
/// Generate the websocket headers using an opcode and the message size (in bytes).
std::string build_header(int opcode, size_t size) std::string build_header(int opcode, size_t size)
{ {
char buf[2+8] = "\x80\x00"; char buf[2+8] = "\x80\x00";
@ -157,6 +211,10 @@ namespace crow
} }
} }
/// Send the HTTP upgrade response.
///
/// Finishes the handshake process, then starts reading messages from the socket.
void start(std::string&& hello) void start(std::string&& hello)
{ {
static std::string header = "HTTP/1.1 101 Switching Protocols\r\n" static std::string header = "HTTP/1.1 101 Switching Protocols\r\n"
@ -174,6 +232,13 @@ namespace crow
do_read(); 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() void do_read()
{ {
is_reading = true; is_reading = true;
@ -181,6 +246,7 @@ namespace crow
{ {
case WebSocketReadState::MiniHeader: case WebSocketReadState::MiniHeader:
{ {
mini_header_ = 0;
//boost::asio::async_read(adaptor_.socket(), boost::asio::buffer(&mini_header_, 1), //boost::asio::async_read(adaptor_.socket(), boost::asio::buffer(&mini_header_, 1),
adaptor_.socket().async_read_some(boost::asio::buffer(&mini_header_, 2), adaptor_.socket().async_read_some(boost::asio::buffer(&mini_header_, 2),
[this](const boost::system::error_code& ec, std::size_t [this](const boost::system::error_code& ec, std::size_t
@ -200,8 +266,11 @@ namespace crow
} }
#endif #endif
if (!ec && ((mini_header_ & 0x80) == 0x80)) if (!ec)
{ {
if ((mini_header_ & 0x80) == 0x80)
has_mask_ = true;
if ((mini_header_ & 0x7f) == 127) if ((mini_header_ & 0x7f) == 127)
{ {
state_ = WebSocketReadState::Len64; state_ = WebSocketReadState::Len64;
@ -300,34 +369,42 @@ namespace crow
} }
break; break;
case WebSocketReadState::Mask: case WebSocketReadState::Mask:
boost::asio::async_read(adaptor_.socket(), boost::asio::buffer((char*)&mask_, 4), if (has_mask_)
[this](const boost::system::error_code& ec, std::size_t {
boost::asio::async_read(adaptor_.socket(), boost::asio::buffer((char*)&mask_, 4),
[this](const boost::system::error_code& ec, std::size_t
#ifdef CROW_ENABLE_DEBUG #ifdef CROW_ENABLE_DEBUG
bytes_transferred bytes_transferred
#endif #endif
) )
{
is_reading = false;
#ifdef CROW_ENABLE_DEBUG
if (!ec && bytes_transferred != 4)
{ {
throw std::runtime_error("WebSocket:Mask:async_read fail:asio bug?"); is_reading = false;
} #ifdef CROW_ENABLE_DEBUG
if (!ec && bytes_transferred != 4)
{
throw std::runtime_error("WebSocket:Mask:async_read fail:asio bug?");
}
#endif #endif
if (!ec) if (!ec)
{ {
state_ = WebSocketReadState::Payload; state_ = WebSocketReadState::Payload;
do_read(); do_read();
} }
else else
{ {
close_connection_ = true; close_connection_ = true;
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
adaptor_.close(); adaptor_.close();
} }
}); });
}
else
{
state_ = WebSocketReadState::Payload;
do_read();
}
break; break;
case WebSocketReadState::Payload: case WebSocketReadState::Payload:
{ {
@ -365,21 +442,30 @@ namespace crow
} }
} }
/// Check if the FIN bit is set.
bool is_FIN() bool is_FIN()
{ {
return mini_header_ & 0x8000; return mini_header_ & 0x8000;
} }
/// Extract the opcode from the header.
int opcode() int opcode()
{ {
return (mini_header_ & 0x0f00) >> 8; 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.
void handle_fragment() void handle_fragment()
{ {
for(decltype(fragment_.length()) i = 0; i < fragment_.length(); i ++) if (has_mask_)
{ {
fragment_[i] ^= ((char*)&mask_)[i%4]; for(decltype(fragment_.length()) i = 0; i < fragment_.length(); i ++)
{
fragment_[i] ^= ((char*)&mask_)[i%4];
}
} }
switch(opcode()) switch(opcode())
{ {
@ -454,6 +540,10 @@ namespace crow
fragment_.clear(); fragment_.clear();
} }
/// Send the buffers' data through the socket.
///
/// Also destroyes the object if the Close flag is set.
void do_write() void do_write()
{ {
if (sending_buffers_.empty()) if (sending_buffers_.empty())
@ -485,6 +575,7 @@ namespace crow
} }
} }
/// Destroy the Connection.
void check_destroy() void check_destroy()
{ {
//if (has_sent_close_ && has_recv_close_) //if (has_sent_close_ && has_recv_close_)
@ -509,6 +600,7 @@ namespace crow
uint64_t remaining_length_{0}; uint64_t remaining_length_{0};
bool close_connection_{false}; bool close_connection_{false};
bool is_reading{false}; bool is_reading{false};
bool has_mask_{false};
uint32_t mask_; uint32_t mask_;
uint16_t mini_header_; uint16_t mini_header_;
bool has_sent_close_{false}; bool has_sent_close_{false};

View File

@ -1371,3 +1371,137 @@ TEST_CASE("stream_response")
}); });
runTest.join(); runTest.join();
} }
TEST_CASE("websocket")
{
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";
static bool connected{false};
SimpleApp app;
CROW_ROUTE(app, "/ws").websocket()
.onopen([&](websocket::connection&){
connected = true;
CROW_LOG_INFO << "Connected websocket and value is " << connected;
})
.onmessage([&](websocket::connection& conn, const std::string& message, bool isbin){
CROW_LOG_INFO << "Message is \"" << message << '\"';
if (!isbin && message == "PINGME")
conn.send_ping("");
else if (!isbin && message == "Hello")
conn.send_text("Hello back");
else if (isbin && message == "Hello bin")
conn.send_binary("Hello back bin");
})
.onclose([&](websocket::connection&, const std::string&){
CROW_LOG_INFO << "Closing websocket";
});
app.validate();
auto _ = async(launch::async,
[&] { app.bindaddr(LOCALHOST_ADDRESS).port(45451).run(); });
app.wait_for_server_start();
asio::io_service is;
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
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);
}
//----------Pong----------
{
std::fill_n (buf, 2048, 0);
char ping_message[2]("\x89");
c.send(asio::buffer(ping_message, 2));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
CHECK((int)(unsigned char)buf[0] == 0x8A);
}
//----------Ping----------
{
std::fill_n (buf, 2048, 0);
char not_ping_message[2+6+1]("\x81\x06"
"PINGME");
c.send(asio::buffer(not_ping_message, 8));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
CHECK((int)(unsigned char)buf[0] == 0x89);
}
//----------Text----------
{
std::fill_n (buf, 2048, 0);
char text_message[2+5+1]("\x81\x05"
"Hello");
c.send(asio::buffer(text_message, 7));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::string checkstring(std::string(buf).substr(0, 12));
CHECK(checkstring == "\x81\x0AHello back");
}
//----------Binary----------
{
std::fill_n (buf, 2048, 0);
char bin_message[2+9+1]("\x82\x09"
"Hello bin");
c.send(asio::buffer(bin_message, 11));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::string checkstring2(std::string(buf).substr(0, 16));
CHECK(checkstring2 == "\x82\x0EHello back bin");
}
//----------Masked Text----------
{
std::fill_n (buf, 2048, 0);
char text_masked_message[2+4+5+1]("\x81\x85"
"\x67\xc6\x69\x73"
"\x2f\xa3\x05\x1f\x08");
c.send(asio::buffer(text_masked_message, 11));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::string checkstring3(std::string(buf).substr(0, 12));
CHECK(checkstring3 == "\x81\x0AHello back");
}
//----------Masked Binary----------
{
std::fill_n (buf, 2048, 0);
char bin_masked_message[2+4+9+1]("\x82\x89"
"\x67\xc6\x69\x73"
"\x2f\xa3\x05\x1f\x08\xe6\x0b\x1a\x09");
c.send(asio::buffer(bin_masked_message, 15));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
std::string checkstring4(std::string(buf).substr(0, 16));
CHECK(checkstring4 == "\x82\x0EHello back bin");
}
//----------Close----------
{
std::fill_n (buf, 2048, 0);
char close_message[10]("\x88"); //I do not know why, but the websocket code does not read this unless it's longer than 4 or so bytes
c.send(asio::buffer(close_message, 10));
c.receive(asio::buffer(buf, 2048));
std::this_thread::sleep_for(std::chrono::milliseconds(5));
CHECK((int)(unsigned char)buf[0] == 0x88);
}
app.stop();
}