mirror of
https://github.com/CrowCpp/Crow.git
synced 2024-06-07 21:10:44 +00:00
Merge branch 'master' into static_dir
This commit is contained in:
commit
50670c290b
4
Doxyfile
4
Doxyfile
@ -38,13 +38,13 @@ PROJECT_NAME = Crow
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# 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
|
||||
# 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.
|
||||
|
||||
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
|
||||
# in the documentation. The maximum height of the logo should not exceed 55
|
||||
|
@ -121,12 +121,12 @@ namespace crow
|
||||
///Set the server's log level
|
||||
|
||||
///
|
||||
/// Possible values are:
|
||||
/// crow::LogLevel::Debug (0)
|
||||
/// crow::LogLevel::Info (1)
|
||||
/// crow::LogLevel::Warning (2)
|
||||
/// crow::LogLevel::Error (3)
|
||||
/// crow::LogLevel::Critical (4)
|
||||
/// Possible values are:<br>
|
||||
/// crow::LogLevel::Debug (0)<br>
|
||||
/// crow::LogLevel::Info (1)<br>
|
||||
/// crow::LogLevel::Warning (2)<br>
|
||||
/// crow::LogLevel::Error (3)<br>
|
||||
/// crow::LogLevel::Critical (4)<br>
|
||||
self_t& loglevel(crow::LogLevel level)
|
||||
{
|
||||
crow::logger::setLogLevel(level);
|
||||
|
@ -108,31 +108,43 @@ namespace crow
|
||||
|
||||
bool is_open()
|
||||
{
|
||||
return raw_socket().is_open();
|
||||
return ssl_socket_ ? raw_socket().is_open() : false;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().close(ec);
|
||||
if (is_open())
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().close(ec);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown_readwrite()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec);
|
||||
if (is_open())
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown_write()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_send, ec);
|
||||
if (is_open())
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_send, ec);
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown_read()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_receive, ec);
|
||||
if (is_open())
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
raw_socket().shutdown(boost::asio::socket_base::shutdown_type::shutdown_receive, ec);
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::io_service& get_io_service()
|
||||
|
@ -18,10 +18,13 @@ namespace crow
|
||||
Payload,
|
||||
};
|
||||
|
||||
///A base class for websocket connection.
|
||||
struct connection
|
||||
{
|
||||
virtual void send_binary(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 ~connection(){}
|
||||
|
||||
@ -32,10 +35,35 @@ namespace crow
|
||||
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>
|
||||
class Connection : public connection
|
||||
{
|
||||
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,
|
||||
std::function<void(crow::websocket::connection&)> open_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));
|
||||
}
|
||||
|
||||
/// Send data through the socket.
|
||||
template<typename CompletionHandler>
|
||||
void dispatch(CompletionHandler handler)
|
||||
{
|
||||
adaptor_.get_io_service().dispatch(handler);
|
||||
}
|
||||
|
||||
/// Send data through the socket and return immediately.
|
||||
template<typename CompletionHandler>
|
||||
void post(CompletionHandler 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]{
|
||||
char buf[3] = "\x8A\x00";
|
||||
buf[1] += msg.size();
|
||||
write_buffers_.emplace_back(buf, buf+2);
|
||||
auto header = build_header(0x9, msg.size());
|
||||
write_buffers_.emplace_back(std::move(header));
|
||||
write_buffers_.emplace_back(msg);
|
||||
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
|
||||
{
|
||||
dispatch([this, msg]{
|
||||
@ -105,6 +153,7 @@ namespace crow
|
||||
});
|
||||
}
|
||||
|
||||
/// Send a plaintext message.
|
||||
void send_text(const std::string& msg) override
|
||||
{
|
||||
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
|
||||
{
|
||||
dispatch([this, msg]{
|
||||
@ -134,6 +187,7 @@ namespace crow
|
||||
|
||||
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";
|
||||
@ -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)
|
||||
{
|
||||
static std::string header = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
@ -174,6 +232,13 @@ namespace crow
|
||||
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()
|
||||
{
|
||||
is_reading = true;
|
||||
@ -181,8 +246,9 @@ namespace crow
|
||||
{
|
||||
case WebSocketReadState::MiniHeader:
|
||||
{
|
||||
mini_header_ = 0;
|
||||
//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
|
||||
#ifdef CROW_ENABLE_DEBUG
|
||||
bytes_transferred
|
||||
@ -200,8 +266,11 @@ namespace crow
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!ec && ((mini_header_ & 0x80) == 0x80))
|
||||
if (!ec)
|
||||
{
|
||||
if ((mini_header_ & 0x80) == 0x80)
|
||||
has_mask_ = true;
|
||||
|
||||
if ((mini_header_ & 0x7f) == 127)
|
||||
{
|
||||
state_ = WebSocketReadState::Len64;
|
||||
@ -300,34 +369,42 @@ namespace crow
|
||||
}
|
||||
break;
|
||||
case WebSocketReadState::Mask:
|
||||
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
|
||||
bytes_transferred
|
||||
#endif
|
||||
)
|
||||
{
|
||||
is_reading = false;
|
||||
if (has_mask_)
|
||||
{
|
||||
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
|
||||
if (!ec && bytes_transferred != 4)
|
||||
bytes_transferred
|
||||
#endif
|
||||
)
|
||||
{
|
||||
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
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
state_ = WebSocketReadState::Payload;
|
||||
do_read();
|
||||
}
|
||||
else
|
||||
{
|
||||
close_connection_ = true;
|
||||
if (error_handler_)
|
||||
error_handler_(*this);
|
||||
adaptor_.close();
|
||||
}
|
||||
});
|
||||
if (!ec)
|
||||
{
|
||||
state_ = WebSocketReadState::Payload;
|
||||
do_read();
|
||||
}
|
||||
else
|
||||
{
|
||||
close_connection_ = true;
|
||||
if (error_handler_)
|
||||
error_handler_(*this);
|
||||
adaptor_.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = WebSocketReadState::Payload;
|
||||
do_read();
|
||||
}
|
||||
break;
|
||||
case WebSocketReadState::Payload:
|
||||
{
|
||||
@ -365,21 +442,30 @@ namespace crow
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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())
|
||||
{
|
||||
@ -454,6 +540,10 @@ namespace crow
|
||||
fragment_.clear();
|
||||
}
|
||||
|
||||
/// Send the buffers' data through the socket.
|
||||
|
||||
///
|
||||
/// Also destroyes the object if the Close flag is set.
|
||||
void do_write()
|
||||
{
|
||||
if (sending_buffers_.empty())
|
||||
@ -485,6 +575,7 @@ namespace crow
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy the Connection.
|
||||
void check_destroy()
|
||||
{
|
||||
//if (has_sent_close_ && has_recv_close_)
|
||||
@ -509,6 +600,7 @@ namespace crow
|
||||
uint64_t remaining_length_{0};
|
||||
bool close_connection_{false};
|
||||
bool is_reading{false};
|
||||
bool has_mask_{false};
|
||||
uint32_t mask_;
|
||||
uint16_t mini_header_;
|
||||
bool has_sent_close_{false};
|
||||
|
@ -1371,3 +1371,137 @@ TEST_CASE("stream_response")
|
||||
});
|
||||
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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user