From fd6de9bc0588655fc16baebab8178a7e9fb6e4fb Mon Sep 17 00:00:00 2001 From: The-EDev Date: Sat, 6 Nov 2021 06:06:18 +0300 Subject: [PATCH 01/29] Added functionality to close websocket connections before the app is terminated. This is incomplete and needs more work. --- examples/websocket/templates/ws.html | 4 +- include/crow/app.h | 6 ++- include/crow/http_server.h | 50 ++++++++++++++++--------- include/crow/routing.h | 10 ++--- include/crow/websocket.h | 56 +++++++++++++++++++--------- 5 files changed, 83 insertions(+), 43 deletions(-) diff --git a/examples/websocket/templates/ws.html b/examples/websocket/templates/ws.html index 2d38fdfce..6465d9f06 100644 --- a/examples/websocket/templates/ws.html +++ b/examples/websocket/templates/ws.html @@ -19,8 +19,8 @@ sock.onopen = ()=>{ sock.onerror = (e)=>{ console.log('error',e) } -sock.onclose = ()=>{ - console.log('close') +sock.onclose = (e)=>{ + console.log('close', e) } sock.onmessage = (e)=>{ $("#log").val( diff --git a/include/crow/app.h b/include/crow/app.h index 083cca618..4660274f2 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -62,6 +62,9 @@ namespace crow { } + + std::atomic websocket_count{0}; + ///Process an Upgrade request /// @@ -69,7 +72,7 @@ namespace crow template void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) { - router_.handle_upgrade(req, res, adaptor); + router_.handle_upgrade(req, res, adaptor, websocket_count); } ///Process the request and generate a response for it @@ -289,7 +292,6 @@ namespace crow { server_ = std::move(std::unique_ptr(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, nullptr))); server_->set_tick_function(tick_interval_, tick_function_); - server_->signal_clear(); for (auto snum : signals_) { server_->signal_add(snum); diff --git a/include/crow/http_server.h b/include/crow/http_server.h index e6efa6fb0..87b96ecfb 100644 --- a/include/crow/http_server.h +++ b/include/crow/http_server.h @@ -29,7 +29,7 @@ namespace crow public: Server(Handler* handler, std::string bindaddr, uint16_t port, std::string server_name = std::string("Crow/") + VERSION, std::tuple* middlewares = nullptr, uint16_t concurrency = 1, typename Adaptor::context* adaptor_ctx = nullptr) : acceptor_(io_service_, tcp::endpoint(boost::asio::ip::address::from_string(bindaddr), port)), - signals_(io_service_, SIGINT, SIGTERM), + signals_(io_service_), tick_timer_(io_service_), handler_(handler), concurrency_(concurrency == 0 ? 1 : concurrency), @@ -169,9 +169,21 @@ namespace crow void stop() { - io_service_.stop(); + should_close_ = false; //Prevent the acceptor from taking new connections + while (handler_->websocket_count.load(std::memory_order_release) != 0) //Wait for the websockets to close properly + { + } for(auto& io_service:io_service_pool_) - io_service->stop(); + { + if (io_service != nullptr) + { + CROW_LOG_INFO << "Closing IO service " << &io_service; + io_service->stop(); //Close all io_services (and HTTP connections) + } + } + + CROW_LOG_INFO << "Closing main IO service (" << &io_service_ << ')'; + io_service_.stop(); //Close main io_service } void signal_clear() @@ -201,22 +213,25 @@ namespace crow is, handler_, server_name_, middlewares_, get_cached_date_str_pool_[roundrobin_index_], *timer_queue_pool_[roundrobin_index_], adaptor_ctx_); - acceptor_.async_accept(p->socket(), - [this, p, &is](boost::system::error_code ec) - { - if (!ec) + if (!should_close_) + { + acceptor_.async_accept(p->socket(), + [this, p, &is](boost::system::error_code ec) { - is.post([p] + if (!ec) { - p->start(); - }); - } - else - { - delete p; - } - do_accept(); - }); + is.post([p] + { + p->start(); + }); + } + else + { + delete p; + } + do_accept(); + }); + } } private: @@ -225,6 +240,7 @@ namespace crow std::vector timer_queue_pool_; std::vector> get_cached_date_str_pool_; tcp::acceptor acceptor_; + bool should_close_ = false; boost::asio::signal_set signals_; boost::asio::deadline_timer tick_timer_; diff --git a/include/crow/routing.h b/include/crow/routing.h index eca2bf5e4..8800732f5 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -49,7 +49,7 @@ namespace crow } virtual void handle(const request&, response&, const routing_params&) = 0; - virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) + virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&, std::atomic&) { res = response(404); res.end(); @@ -400,9 +400,9 @@ namespace crow res.end(); } - void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override + void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor, std::atomic& websocket_count) override { - new crow::websocket::Connection(req, std::move(adaptor), open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + new crow::websocket::Connection(req, std::move(adaptor), websocket_count, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override @@ -1397,7 +1397,7 @@ namespace crow //TODO maybe add actual_method template - void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) + void handle_upgrade(const request& req, response& res, Adaptor&& adaptor, std::atomic& websocket_count) { if (req.method >= HTTPMethod::InternalMethodCount) return; @@ -1451,7 +1451,7 @@ namespace crow // any uncaught exceptions become 500s try { - rules[rule_index]->handle_upgrade(req, res, std::move(adaptor)); + rules[rule_index]->handle_upgrade(req, res, std::move(adaptor), websocket_count); } catch(std::exception& e) { diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 86120735f..0bc758e59 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "crow/logging.h" #include "crow/socket_adaptors.h" #include "crow/http_request.h" #include "crow/TinySHA1.hpp" @@ -56,7 +57,7 @@ namespace crow // +---------------------------------------------------------------+ /// A websocket connection. - template + template class Connection : public connection { public: @@ -65,19 +66,20 @@ namespace crow /// /// Requires a request with an "Upgrade: websocket" header.
/// Automatically handles the handshake. - Connection(const crow::request& req, Adaptor&& adaptor, + Connection(const crow::request& req, Adaptor&& adaptor, std::atomic& websocket_count, std::function open_handler, std::function message_handler, std::function close_handler, std::function error_handler, std::function accept_handler) - : adaptor_(std::move(adaptor)), 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_(std::move(adaptor)), websocket_count_(websocket_count), 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)), signals_(adaptor_.get_io_service(), SIGINT, SIGTERM) { + if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { - adaptor.close(); - delete this; + adaptor.close(); + delete this; return; } @@ -85,19 +87,28 @@ namespace crow { if (!accept_handler_(req)) { - adaptor.close(); - delete this; + adaptor.close(); + delete this; return; } } + websocket_count_++; // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // Sec-WebSocket-Version: 13 - std::string magic = req.get_header_value("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + 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); + s.getDigestBytes(digest); + signals_.async_wait( + [&](const boost::system::error_code& e, int /*signal_number*/){ + if (!e){ + CROW_LOG_INFO << "quitting " << this; + do_not_destroy_ = true; + close("Quitter"); + } + }); start(crow::utility::base64encode((unsigned char*)digest, 20)); } @@ -307,7 +318,7 @@ namespace crow { remaining_length_ = 0; remaining_length16_ = 0; - boost::asio::async_read(adaptor_.socket(), boost::asio::buffer(&remaining_length16_, 2), + boost::asio::async_read(adaptor_.socket(), boost::asio::buffer(&remaining_length16_, 2), [this](const boost::system::error_code& ec, std::size_t #ifdef CROW_ENABLE_DEBUG bytes_transferred @@ -342,7 +353,7 @@ namespace crow break; case WebSocketReadState::Len64: { - boost::asio::async_read(adaptor_.socket(), boost::asio::buffer(&remaining_length_, 8), + boost::asio::async_read(adaptor_.socket(), boost::asio::buffer(&remaining_length_, 8), [this](const boost::system::error_code& ec, std::size_t #ifdef CROW_ENABLE_DEBUG bytes_transferred @@ -417,7 +428,7 @@ namespace crow auto to_read = static_cast(buffer_.size()); if (remaining_length_ < to_read) to_read = remaining_length_; - adaptor_.socket().async_read_some(boost::asio::buffer(buffer_, static_cast(to_read)), + adaptor_.socket().async_read_some(boost::asio::buffer(buffer_, static_cast(to_read)), [this](const boost::system::error_code& ec, std::size_t bytes_transferred) { is_reading = false; @@ -561,7 +572,7 @@ namespace crow { buffers.emplace_back(boost::asio::buffer(s)); } - boost::asio::async_write(adaptor_.socket(), buffers, + boost::asio::async_write(adaptor_.socket(), buffers, [&](const boost::system::error_code& ec, std::size_t /*bytes_transferred*/) { sending_buffers_.clear(); @@ -588,11 +599,12 @@ namespace crow if (!is_close_handler_called_) if (close_handler_) close_handler_(*this, "uncleanly"); - if (sending_buffers_.empty() && !is_reading) + websocket_count_--; + if (sending_buffers_.empty() && !is_reading && !do_not_destroy_) delete this; } - private: - Adaptor adaptor_; + private: + Adaptor adaptor_; std::vector sending_buffers_; std::vector write_buffers_; @@ -615,11 +627,21 @@ namespace crow bool pong_received_{false}; bool is_close_handler_called_{false}; + //**WARNING** + //SETTING THIS PREVENTS THE OBJECT FROM BEING DELETED, + //AND WILL ABSOLUTELY CAUSE A MEMORY LEAK!! + //ONLY USE IF THE APPLICATION IS BEING TERMINATED!! + bool do_not_destroy_{false}; + //**WARNING** + + std::atomic& websocket_count_; + std::function open_handler_; std::function message_handler_; std::function close_handler_; std::function error_handler_; std::function accept_handler_; + boost::asio::signal_set signals_; }; } } From 1d758e939f3ab56826c126ac2dd04f5962c535b0 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Tue, 26 Apr 2022 20:10:44 -0700 Subject: [PATCH 02/29] #421 add rudimentary support for websocket max payload --- include/crow/websocket.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 4555b70da..1cbb1fccb 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -390,7 +390,15 @@ namespace crow } break; case WebSocketReadState::Mask: - if (has_mask_) + if (remaining_length_ > max_payload_bytes_) + { + close_connection_ = true; + adaptor_.close(); + if (error_handler_) + error_handler_(*this); + check_destroy(); + } + else if (has_mask_) { boost::asio::async_read( adaptor_.socket(), boost::asio::buffer((char*)&mask_, 4), @@ -620,6 +628,7 @@ namespace crow WebSocketReadState state_{WebSocketReadState::MiniHeader}; uint16_t remaining_length16_{0}; uint64_t remaining_length_{0}; + uint64_t max_payload_bytes_{UINT64_MAX}; bool close_connection_{false}; bool is_reading{false}; bool has_mask_{false}; From 60bf49be02d6cc6eee74196a387254c5d7a7e591 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Tue, 26 Apr 2022 20:33:10 -0700 Subject: [PATCH 03/29] fix clang format --- include/crow/websocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 1cbb1fccb..5bb5976bb 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -395,7 +395,7 @@ namespace crow close_connection_ = true; adaptor_.close(); if (error_handler_) - error_handler_(*this); + error_handler_(*this); check_destroy(); } else if (has_mask_) From 182b1ba915acef2bee2f91ac9a7eb758f6799be7 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Tue, 26 Apr 2022 20:10:44 -0700 Subject: [PATCH 04/29] #421 add rudimentary support for websocket max payload --- include/crow/websocket.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/crow/websocket.h b/include/crow/websocket.h index dd0461029..2255c589c 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -395,7 +395,15 @@ namespace crow } break; case WebSocketReadState::Mask: - if (has_mask_) + if (remaining_length_ > max_payload_bytes_) + { + close_connection_ = true; + adaptor_.close(); + if (error_handler_) + error_handler_(*this); + check_destroy(); + } + else if (has_mask_) { boost::asio::async_read( adaptor_.socket(), boost::asio::buffer((char*)&mask_, 4), @@ -626,6 +634,7 @@ namespace crow WebSocketReadState state_{WebSocketReadState::MiniHeader}; uint16_t remaining_length16_{0}; uint64_t remaining_length_{0}; + uint64_t max_payload_bytes_{UINT64_MAX}; bool close_connection_{false}; bool is_reading{false}; bool has_mask_{false}; From dc988ce27ba1646e7ec4b636d04efbbf2ce1d8d5 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Tue, 26 Apr 2022 20:33:10 -0700 Subject: [PATCH 05/29] fix clang format --- include/crow/websocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 2255c589c..ce626b3c3 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -400,7 +400,7 @@ namespace crow close_connection_ = true; adaptor_.close(); if (error_handler_) - error_handler_(*this); + error_handler_(*this); check_destroy(); } else if (has_mask_) From e5fe4e6e269a6a3314e1c6c2c22fbcb8a59edff3 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Sun, 15 May 2022 01:15:39 +0300 Subject: [PATCH 06/29] Used App reference to get websocket count and signals. Also fixed issue where enabling SSL prevented compilation. --- include/crow/app.h | 7 ++++++- include/crow/routing.h | 10 +++++----- include/crow/websocket.h | 32 ++++++++++++++++++++------------ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/include/crow/app.h b/include/crow/app.h index 1b3ce1be7..c652fbf38 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -69,7 +69,7 @@ namespace crow template void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) { - router_.handle_upgrade(req, res, adaptor, websocket_count); + router_.handle_upgrade(req, res, adaptor); } /// Process the request and generate a response for it @@ -118,6 +118,11 @@ namespace crow return *this; } + std::vector signals() + { + return signals_; + } + /// Set the port that Crow will handle requests on self_t& port(std::uint16_t port) { diff --git a/include/crow/routing.h b/include/crow/routing.h index 2a69bf03b..0122b4b9e 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -46,7 +46,7 @@ namespace crow } virtual void handle(request&, response&, const routing_params&) = 0; - virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&, std::atomic&) + virtual void handle_upgrade(const request&, response& res, SocketAdaptor&&) { res = response(404); res.end(); @@ -388,9 +388,9 @@ namespace crow res.end(); } - void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor, std::atomic& websocket_count) override + void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { - new crow::websocket::Connection(req, std::move(adaptor), websocket_count, app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + new crow::websocket::Connection(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override @@ -1303,7 +1303,7 @@ namespace crow // TODO maybe add actual_method template - void handle_upgrade(const request& req, response& res, Adaptor&& adaptor, std::atomic& websocket_count) + void handle_upgrade(const request& req, response& res, Adaptor&& adaptor) { if (req.method >= HTTPMethod::InternalMethodCount) return; @@ -1357,7 +1357,7 @@ namespace crow // any uncaught exceptions become 500s try { - rules[rule_index]->handle_upgrade(req, res, std::move(adaptor), websocket_count); + rules[rule_index]->handle_upgrade(req, res, std::move(adaptor)); } catch (std::exception& e) { diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 90f3bc851..54dd7d953 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -71,7 +71,7 @@ namespace crow /// /// Requires a request with an "Upgrade: websocket" header.
/// Automatically handles the handshake. - Connection(const crow::request& req, Adaptor&& adaptor, Handler* handler, std::atomic& websocket_count, + Connection(const crow::request& req, Adaptor&& adaptor, Handler* handler, std::function open_handler, std::function message_handler, std::function close_handler, @@ -79,12 +79,13 @@ namespace crow std::function accept_handler): adaptor_(std::move(adaptor)), handler_(handler), - websocket_count_(websocket_count), + websocket_count_(handler_->websocket_count), 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)) + accept_handler_(std::move(accept_handler)), + signals_(adaptor_.get_io_service()) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { @@ -103,6 +104,11 @@ namespace crow } } + + signals_.clear(); + for (auto snum: handler_->signals()) + signals_.add(snum); + // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // Sec-WebSocket-Version: 13 std::string magic = req.get_header_value("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -110,14 +116,15 @@ namespace crow s.processBytes(magic.data(), magic.size()); uint8_t digest[20]; s.getDigestBytes(digest); - signals_.async_wait( - [&](const boost::system::error_code& e, int /*signal_number*/){ - if (!e){ - CROW_LOG_INFO << "quitting " << this; - do_not_destroy_ = true; - close("Quitter"); - } - }); + + signals_.async_wait( + [&](const boost::system::error_code& e, int /*signal_number*/){ + if (!e){ + CROW_LOG_INFO << "quitting " << this; + do_not_destroy_ = true; + close("Quitter"); + } + }); start(crow::utility::base64encode((unsigned char*)digest, 20)); } @@ -658,12 +665,13 @@ namespace crow // **WARNING** std::atomic& websocket_count_; - + std::function open_handler_; std::function message_handler_; std::function close_handler_; std::function error_handler_; std::function accept_handler_; + boost::asio::signal_set signals_; }; } // namespace websocket } // namespace crow From 4daf126df09d713b0b9f52de658710b8426f8569 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 17:51:51 -0700 Subject: [PATCH 07/29] Add websocket payload size for both app and WebsocketRule --- include/crow/app.h | 13 +++++++++++++ include/crow/routing.h | 10 +++++++++- include/crow/websocket.h | 6 ++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/include/crow/app.h b/include/crow/app.h index 01d873fd8..86fc995dc 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -103,6 +103,18 @@ namespace crow return router_.catchall_rule(); } + /// Set the default max payload size for websockets + self_t& websocket_max_payload(uint64_t max_payload) + { + max_payload_ = max_payload; + } + + /// Get the port that Crow will handle requests on + uint64_t websocket_max_payload() + { + return max_payload_; + } + self_t& signal_clear() { signals_.clear(); @@ -462,6 +474,7 @@ namespace crow std::uint8_t timeout_{5}; uint16_t port_ = 80; uint16_t concurrency_ = 2; + uint64_t max_payload_{ UINT64_MAX }; bool validated_ = false; std::string server_name_ = std::string("Crow/") + VERSION; std::string bindaddr_ = "0.0.0.0"; diff --git a/include/crow/routing.h b/include/crow/routing.h index 0122b4b9e..6c39450bf 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -377,6 +377,7 @@ namespace crow WebSocketRule(std::string rule, App* app): BaseRule(std::move(rule)), app_(app) + max_payload_{app_.websocket_max_payload()} {} void validate() override @@ -390,7 +391,8 @@ namespace crow void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { - new crow::websocket::Connection(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + auto* conn = new crow::websocket::Connection(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + conn->set_max_payload_size(max_payload_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override @@ -399,6 +401,11 @@ namespace crow } #endif + self_t& maxpayload(uint64_t payload) + { + max_payload_ = payload; + } + template self_t& onopen(Func f) { @@ -441,6 +448,7 @@ namespace crow std::function close_handler_; std::function error_handler_; std::function accept_handler_; + uint64_t max_payload_; }; /// Allows the user to assign parameters using functions. diff --git a/include/crow/websocket.h b/include/crow/websocket.h index ce626b3c3..afa1f34d1 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -82,6 +82,7 @@ namespace crow close_handler_(std::move(close_handler)), error_handler_(std::move(error_handler)), accept_handler_(std::move(accept_handler)) + max_payload_bytes_{handler.websocket_max_payload()} { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { @@ -200,6 +201,11 @@ namespace crow return adaptor_.remote_endpoint().address().to_string(); } + void set_max_payload_size(uint64_t payload) + { + max_payload_bytes_ = payload; + } + protected: /// Generate the websocket headers using an opcode and the message size (in bytes). std::string build_header(int opcode, size_t size) From aaeaf9922e0dc070ae0198cd1c054950e943e428 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 17:54:21 -0700 Subject: [PATCH 08/29] fix clang format --- include/crow/app.h | 2 +- include/crow/routing.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/crow/app.h b/include/crow/app.h index 86fc995dc..2d2816aa8 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -474,7 +474,7 @@ namespace crow std::uint8_t timeout_{5}; uint16_t port_ = 80; uint16_t concurrency_ = 2; - uint64_t max_payload_{ UINT64_MAX }; + uint64_t max_payload_{ UINT64_MAX }; bool validated_ = false; std::string server_name_ = std::string("Crow/") + VERSION; std::string bindaddr_ = "0.0.0.0"; diff --git a/include/crow/routing.h b/include/crow/routing.h index 6c39450bf..2a0180601 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -392,7 +392,7 @@ namespace crow void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { auto* conn = new crow::websocket::Connection(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); - conn->set_max_payload_size(max_payload_); + conn->set_max_payload_size(max_payload_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override From aa75fc108dee8c3528c6c1b46406f06c106a92e1 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 17:58:53 -0700 Subject: [PATCH 09/29] fix wrong comment --- include/crow/app.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/crow/app.h b/include/crow/app.h index 2d2816aa8..37ed64bdf 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -109,7 +109,7 @@ namespace crow max_payload_ = max_payload; } - /// Get the port that Crow will handle requests on + /// Get the default max payload size for websockets uint64_t websocket_max_payload() { return max_payload_; From a30f0f6e4fee203ccc5a51de5ead0f1129bed488 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 18:20:46 -0700 Subject: [PATCH 10/29] fix wrong intializer syntax --- include/crow/routing.h | 2 +- include/crow/websocket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/crow/routing.h b/include/crow/routing.h index 2a0180601..d3b69abbf 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -377,7 +377,7 @@ namespace crow WebSocketRule(std::string rule, App* app): BaseRule(std::move(rule)), app_(app) - max_payload_{app_.websocket_max_payload()} + max_payload_(app_.websocket_max_payload()) {} void validate() override diff --git a/include/crow/websocket.h b/include/crow/websocket.h index afa1f34d1..d7dca5279 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -82,7 +82,7 @@ namespace crow close_handler_(std::move(close_handler)), error_handler_(std::move(error_handler)), accept_handler_(std::move(accept_handler)) - max_payload_bytes_{handler.websocket_max_payload()} + max_payload_bytes_(handler.websocket_max_payload()) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { From 89d99f6ae9fc1d7adf9a718b22c6ec4254f68abc Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 18:46:49 -0700 Subject: [PATCH 11/29] add missing commas --- include/crow/routing.h | 2 +- include/crow/websocket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/crow/routing.h b/include/crow/routing.h index d3b69abbf..af7641ff8 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -376,7 +376,7 @@ namespace crow public: WebSocketRule(std::string rule, App* app): BaseRule(std::move(rule)), - app_(app) + app_(app), max_payload_(app_.websocket_max_payload()) {} diff --git a/include/crow/websocket.h b/include/crow/websocket.h index d7dca5279..4b1121af4 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -81,7 +81,7 @@ namespace crow message_handler_(std::move(message_handler)), close_handler_(std::move(close_handler)), error_handler_(std::move(error_handler)), - accept_handler_(std::move(accept_handler)) + accept_handler_(std::move(accept_handler)), max_payload_bytes_(handler.websocket_max_payload()) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) From 716451762254b003b0eea6463699e1353e9f6079 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 19:10:05 -0700 Subject: [PATCH 12/29] use arrow not . --- include/crow/routing.h | 2 +- include/crow/websocket.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/crow/routing.h b/include/crow/routing.h index af7641ff8..9365160ac 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -377,7 +377,7 @@ namespace crow WebSocketRule(std::string rule, App* app): BaseRule(std::move(rule)), app_(app), - max_payload_(app_.websocket_max_payload()) + max_payload_(app_->websocket_max_payload()) {} void validate() override diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 4b1121af4..fa0a0977c 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -82,7 +82,7 @@ namespace crow close_handler_(std::move(close_handler)), error_handler_(std::move(error_handler)), accept_handler_(std::move(accept_handler)), - max_payload_bytes_(handler.websocket_max_payload()) + max_payload_bytes_(handler->websocket_max_payload()) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { From cdba76739f649957117efedfc9ba3149d6688d8b Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sat, 14 May 2022 19:35:45 -0700 Subject: [PATCH 13/29] fix clang format --- include/crow/app.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/crow/app.h b/include/crow/app.h index 37ed64bdf..342cbe55a 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -474,7 +474,7 @@ namespace crow std::uint8_t timeout_{5}; uint16_t port_ = 80; uint16_t concurrency_ = 2; - uint64_t max_payload_{ UINT64_MAX }; + uint64_t max_payload_{UINT64_MAX}; bool validated_ = false; std::string server_name_ = std::string("Crow/") + VERSION; std::string bindaddr_ = "0.0.0.0"; From a0a192563074c04fa0fe1ceb5b28ec9c0dcbe34c Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sun, 15 May 2022 01:01:24 -0700 Subject: [PATCH 14/29] Pass the max payload in Connection constructor --- include/crow/routing.h | 5 ++--- include/crow/websocket.h | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/crow/routing.h b/include/crow/routing.h index 9365160ac..6757df3c7 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -391,13 +391,12 @@ namespace crow void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { - auto* conn = new crow::websocket::Connection(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); - conn->set_max_payload_size(max_payload_); + auto* conn = new crow::websocket::Connection(req, std::move(adaptor), max_payload_, max_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override { - new crow::websocket::Connection(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #endif diff --git a/include/crow/websocket.h b/include/crow/websocket.h index fa0a0977c..43c7e6cec 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -69,7 +69,7 @@ namespace crow /// /// Requires a request with an "Upgrade: websocket" header.
/// Automatically handles the handshake. - Connection(const crow::request& req, Adaptor&& adaptor, Handler* handler, + Connection(const crow::request& req, Adaptor&& adaptor, Handler* handler, uint64_t max_payload, std::function open_handler, std::function message_handler, std::function close_handler, @@ -82,7 +82,7 @@ namespace crow close_handler_(std::move(close_handler)), error_handler_(std::move(error_handler)), accept_handler_(std::move(accept_handler)), - max_payload_bytes_(handler->websocket_max_payload()) + max_payload_bytes_(max_payload) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { From f0ea634c015a240da758b0c61edca5fcf334fb91 Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sun, 15 May 2022 01:21:34 -0700 Subject: [PATCH 15/29] fix copy paste error --- include/crow/routing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/crow/routing.h b/include/crow/routing.h index 6757df3c7..d7b1379c1 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -391,7 +391,7 @@ namespace crow void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { - auto* conn = new crow::websocket::Connection(req, std::move(adaptor), max_payload_, max_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + auto* conn = new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override From 487c4e6c0866122cd5efec7f6fe7708fb361072e Mon Sep 17 00:00:00 2001 From: "oscar.chen" Date: Sun, 15 May 2022 01:34:29 -0700 Subject: [PATCH 16/29] removing unneeded variable --- include/crow/routing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/crow/routing.h b/include/crow/routing.h index d7b1379c1..3cb1ba1dd 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -391,7 +391,7 @@ namespace crow void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { - auto* conn = new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); + new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL void handle_upgrade(const request& req, response&, SSLAdaptor&& adaptor) override From 57c3b43ac01fe362ba0afb2e1069692e21d6dd60 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Mon, 16 May 2022 19:14:51 +0300 Subject: [PATCH 17/29] Fixed small bugs in code --- include/crow/app.h | 1 + include/crow/routing.h | 11 ++++++++--- include/crow/websocket.h | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/crow/app.h b/include/crow/app.h index 342cbe55a..88a74a57a 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -107,6 +107,7 @@ namespace crow self_t& websocket_max_payload(uint64_t max_payload) { max_payload_ = max_payload; + return *this; } /// Get the default max payload size for websockets diff --git a/include/crow/routing.h b/include/crow/routing.h index 3cb1ba1dd..b44c3228b 100644 --- a/include/crow/routing.h +++ b/include/crow/routing.h @@ -377,7 +377,7 @@ namespace crow WebSocketRule(std::string rule, App* app): BaseRule(std::move(rule)), app_(app), - max_payload_(app_->websocket_max_payload()) + max_payload_(UINT64_MAX) {} void validate() override @@ -391,6 +391,7 @@ namespace crow void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override { + max_payload_ = max_payload_override_ ? max_payload_ : app_->websocket_max_payload(); new crow::websocket::Connection(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_); } #ifdef CROW_ENABLE_SSL @@ -400,9 +401,12 @@ namespace crow } #endif - self_t& maxpayload(uint64_t payload) + /// Override the global payload limit for this single WebSocket rule + self_t& max_payload(uint64_t max_payload) { - max_payload_ = payload; + max_payload_ = max_payload; + max_payload_override_ = true; + return *this; } template @@ -448,6 +452,7 @@ namespace crow std::function error_handler_; std::function accept_handler_; uint64_t max_payload_; + bool max_payload_override_ = false; }; /// Allows the user to assign parameters using functions. diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 43c7e6cec..bbea07a62 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -77,12 +77,12 @@ namespace crow std::function 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)), - max_payload_bytes_(max_payload) + accept_handler_(std::move(accept_handler)) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { From 0f6bfb04ee853294fee9ed4150cb869d661fe935 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Mon, 16 May 2022 19:15:34 +0300 Subject: [PATCH 18/29] added documentation and unit test --- docs/guides/websockets.md | 30 ++++++++++++----- tests/unittest.cpp | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/docs/guides/websockets.md b/docs/guides/websockets.md index de70d62a0..e46934112 100644 --- a/docs/guides/websockets.md +++ b/docs/guides/websockets.md @@ -1,19 +1,19 @@ Websockets are a way of connecting a client and a server without the request response nature of HTTP.

+## Routes To create a websocket in Crow, you need a websocket route.
-A websocket route differs from a normal route quite a bit. While it uses the same `CROW_ROUTE(app, "/url")` macro, that's about where the similarities end.
-A websocket route follows the macro with `.websocket()` which is then followed by a series of methods (with handlers inside) for each event. These are (sorted by order of execution): - -!!! Warning - - By default, Crow allows Clients to send unmasked websocket messages, which is useful for debugging but goes against the protocol specification. Production Crow applications should enforce the protocol by adding `#!cpp #define CROW_ENFORCE_WS_SPEC` to their source code. +A websocket route differs from a normal route quite a bit. It uses A slightly altered `CROW_WEBSOCKET_ROUTE(app, "/url")` macro, which is then followed by a series of methods (with handlers inside) for each event. These are (sorted by order of execution): - `#!cpp onaccept([&](const crow::request&){handler code goes here})` (This handler has to return `bool`) - `#!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){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){handler code goes here})` + +!!! Warning + + By default, Crow allows Clients to send unmasked websocket messages, which is useful for debugging but goes against the protocol specification. Production Crow applications should enforce the protocol by adding `#!cpp #define CROW_ENFORCE_WS_SPEC` to their source code. These event methods and their handlers can be chained. The full Route should look similar to this: ```cpp @@ -32,5 +32,17 @@ CROW_ROUTE(app, "/ws") do_something_else(data); }); ``` -

-For more info go [here](../../reference/classcrow_1_1_web_socket_rule.html). + +## Maximum payload size +[:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow) + +The maximum payload size that a connection accepts can be adjusted either globally by using `#!cpp app.websocket_max_payload()` or per route by using `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload()`. In case a message was sent that exceeded the limit. The connection would be shut down and `onerror` would be triggered. + +!!! note + + By default, This limit is disabled. To disable the global setting in specific routes, you only need to call `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload(UINT64_MAX)`. + + +For more info about websocket routes go [here](../../reference/classcrow_1_1_web_socket_rule.html). + +For more info about websocket connections go [here](../../reference/classcrow_1_1websocket_1_1_connection.html). diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 18810e33d..dc348c534 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -2273,6 +2273,75 @@ TEST_CASE("websocket") app.stop(); } // websocket +TEST_CASE("websocket_max_payload") +{ + 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_WEBSOCKET_ROUTE(app, "/ws") + .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 _ = app.websocket_max_payload(3).bindaddr(LOCALHOST_ADDRESS).port(45461).run_async(); + 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), 45461)); + + + 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); + } + //----------Text---------- + { + std::fill_n(buf, 2048, 0); + char text_message[2 + 5 + 1]("\x81\x05" + "Hello"); + + c.send(asio::buffer(text_message, 7)); + try + { + c.receive(asio::buffer(buf, 2048)); + FAIL_CHECK(); + } + catch (std::exception& e) + { + CROW_LOG_DEBUG << e.what(); + } + } + + app.stop(); +} // websocket_max_payload + #ifdef CROW_ENABLE_COMPRESSION TEST_CASE("zlib_compression") { From 94a0c3555af96235e6fe963dd5d9ab5b9abb823c Mon Sep 17 00:00:00 2001 From: The-EDev Date: Mon, 16 May 2022 19:32:33 +0300 Subject: [PATCH 19/29] formatting --- tests/unittest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unittest.cpp b/tests/unittest.cpp index dc348c534..48d78a7a2 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include ` #include "catch.hpp" #include "crow.h" @@ -2283,9 +2283,9 @@ TEST_CASE("websocket_max_payload") CROW_WEBSOCKET_ROUTE(app, "/ws") .onopen([&](websocket::connection&) { - connected = true; - CROW_LOG_INFO << "Connected websocket and value is " << connected; - }) + 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") From 333b49bf045daf841c4936ac4585ba593de54375 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Wed, 18 May 2022 12:28:51 +0300 Subject: [PATCH 20/29] Replaced `do_not_destroy_` with `adaptor_.shutdown_readwrite()` + formatting --- include/crow/http_server.h | 8 ++++---- include/crow/websocket.h | 23 ++++++++++------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/include/crow/http_server.h b/include/crow/http_server.h index d0ace3a5e..a7002d778 100644 --- a/include/crow/http_server.h +++ b/include/crow/http_server.h @@ -161,16 +161,16 @@ namespace crow void stop() { - should_close_ = false; //Prevent the acceptor from taking new connections + should_close_ = false; //Prevent the acceptor from taking new connections while (handler_->websocket_count.load(std::memory_order_release) != 0) //Wait for the websockets to close properly { } - for(auto& io_service : io_service_pool_) + for (auto& io_service : io_service_pool_) { if (io_service != nullptr) { - CROW_LOG_INFO << "Closing IO service " << &io_service; - io_service->stop(); //Close all io_services (and HTTP connections) + CROW_LOG_INFO << "Closing IO service " << &io_service; + io_service->stop(); //Close all io_services (and HTTP connections) } } diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 54dd7d953..8d53c714e 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -120,9 +120,8 @@ namespace crow signals_.async_wait( [&](const boost::system::error_code& e, int /*signal_number*/){ if (!e){ - CROW_LOG_INFO << "quitting " << this; - do_not_destroy_ = true; - close("Quitter"); + CROW_LOG_INFO << "Quitting Websocket: " << this; + close("Server Application Terminated"); } }); start(crow::utility::base64encode((unsigned char*)digest, 20)); @@ -308,6 +307,7 @@ namespace crow has_mask_ = false; #else close_connection_ = true; + adaptor_.shutdown_readwrite(); adaptor_.close(); if (error_handler_) error_handler_(*this); @@ -333,6 +333,7 @@ namespace crow else { close_connection_ = true; + adaptor_.shutdown_readwrite(); adaptor_.close(); if (error_handler_) error_handler_(*this); @@ -370,6 +371,7 @@ namespace crow else { close_connection_ = true; + adaptor_.shutdown_readwrite(); adaptor_.close(); if (error_handler_) error_handler_(*this); @@ -404,6 +406,7 @@ namespace crow else { close_connection_ = true; + adaptor_.shutdown_readwrite(); adaptor_.close(); if (error_handler_) error_handler_(*this); @@ -440,6 +443,7 @@ namespace crow close_connection_ = true; if (error_handler_) error_handler_(*this); + adaptor_.shutdown_readwrite(); adaptor_.close(); } }); @@ -478,6 +482,7 @@ namespace crow close_connection_ = true; if (error_handler_) error_handler_(*this); + adaptor_.shutdown_readwrite(); adaptor_.close(); } }); @@ -557,6 +562,7 @@ namespace crow } else { + adaptor_.shutdown_readwrite(); adaptor_.close(); close_connection_ = true; if (!is_close_handler_called_) @@ -655,16 +661,7 @@ namespace crow bool error_occured_{false}; bool pong_received_{false}; bool is_close_handler_called_{false}; - - - // **WARNING** - // SETTING THIS PREVENTS THE OBJECT FROM BEING DELETED, - // AND WILL ABSOLUTELY CAUSE A MEMORY LEAK!! - // ONLY USE IF THE APPLICATION IS BEING TERMINATED!! - bool do_not_destroy_{false}; - // **WARNING** - - std::atomic& websocket_count_; + std::atomic& websocket_count_; std::function open_handler_; std::function message_handler_; From 4128e5c27d486e20cdc419f7c44d7a018ede16b6 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Wed, 18 May 2022 12:40:11 +0300 Subject: [PATCH 21/29] further formatting --- include/crow/websocket.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 8d53c714e..22b083ba3 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -106,7 +106,7 @@ namespace crow signals_.clear(); - for (auto snum: handler_->signals()) + for (auto snum : handler_->signals()) signals_.add(snum); // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== @@ -118,12 +118,13 @@ namespace crow s.getDigestBytes(digest); signals_.async_wait( - [&](const boost::system::error_code& e, int /*signal_number*/){ - if (!e){ - CROW_LOG_INFO << "Quitting Websocket: " << this; - close("Server Application Terminated"); - } - }); + [&](const boost::system::error_code& e, int /*signal_number*/) { + if (!e) + { + CROW_LOG_INFO << "Quitting Websocket: " << this; + close("Server Application Terminated"); + } + }); start(crow::utility::base64encode((unsigned char*)digest, 20)); } From cfc4281e3ba4b3eb372c9ed1338d6116a6abe27e Mon Sep 17 00:00:00 2001 From: The-EDev Date: Wed, 18 May 2022 13:49:50 +0300 Subject: [PATCH 22/29] Prevent acceptor from taking new connections while websockets close --- include/crow/http_server.h | 55 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/include/crow/http_server.h b/include/crow/http_server.h index a7002d778..7eefeb06c 100644 --- a/include/crow/http_server.h +++ b/include/crow/http_server.h @@ -161,7 +161,7 @@ namespace crow void stop() { - should_close_ = false; //Prevent the acceptor from taking new connections + should_close_ = true; //Prevent the acceptor from taking new connections while (handler_->websocket_count.load(std::memory_order_release) != 0) //Wait for the websockets to close properly { } @@ -207,33 +207,36 @@ namespace crow void do_accept() { - uint16_t service_idx = pick_io_service_idx(); - asio::io_service& is = *io_service_pool_[service_idx]; - task_queue_length_pool_[service_idx]++; - CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; + if (!should_close_) + { + uint16_t service_idx = pick_io_service_idx(); + asio::io_service& is = *io_service_pool_[service_idx]; + task_queue_length_pool_[service_idx]++; + CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; - auto p = new Connection( - is, handler_, server_name_, middlewares_, - get_cached_date_str_pool_[service_idx], *task_timer_pool_[service_idx], adaptor_ctx_, task_queue_length_pool_[service_idx]); + auto p = new Connection( + is, handler_, server_name_, middlewares_, + get_cached_date_str_pool_[service_idx], *task_timer_pool_[service_idx], adaptor_ctx_, task_queue_length_pool_[service_idx]); - acceptor_.async_accept( - p->socket(), - [this, p, &is, service_idx](boost::system::error_code ec) { - if (!ec) - { - is.post( - [p] { - p->start(); - }); - } - else - { - task_queue_length_pool_[service_idx]--; - CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; - delete p; - } - do_accept(); - }); + acceptor_.async_accept( + p->socket(), + [this, p, &is, service_idx](boost::system::error_code ec) { + if (!ec) + { + is.post( + [p] { + p->start(); + }); + } + else + { + task_queue_length_pool_[service_idx]--; + CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; + delete p; + } + do_accept(); + }); + } } private: From 5fcaa191637c04e1b1d2cf6c36aeaf2c5932d525 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Wed, 18 May 2022 14:08:40 +0300 Subject: [PATCH 23/29] should_close_ -> shutting_down_ --- include/crow/http_server.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/crow/http_server.h b/include/crow/http_server.h index 7eefeb06c..56b24834b 100644 --- a/include/crow/http_server.h +++ b/include/crow/http_server.h @@ -161,7 +161,7 @@ namespace crow void stop() { - should_close_ = true; //Prevent the acceptor from taking new connections + shutting_down_ = true; //Prevent the acceptor from taking new connections while (handler_->websocket_count.load(std::memory_order_release) != 0) //Wait for the websockets to close properly { } @@ -207,7 +207,7 @@ namespace crow void do_accept() { - if (!should_close_) + if (!shutting_down_) { uint16_t service_idx = pick_io_service_idx(); asio::io_service& is = *io_service_pool_[service_idx]; @@ -245,7 +245,7 @@ namespace crow std::vector task_timer_pool_; std::vector> get_cached_date_str_pool_; tcp::acceptor acceptor_; - bool should_close_ = false; + bool shutting_down_ = false; boost::asio::signal_set signals_; boost::asio::deadline_timer tick_timer_; From 7e4f57486c24252d20b6c5fdd8f5ab019e0daa9b Mon Sep 17 00:00:00 2001 From: konrad Date: Sun, 8 May 2022 13:44:45 +0200 Subject: [PATCH 24/29] Add extra log when status code is not defined. Just to document behaviour when returning status that is not well-known. --- docs/guides/routes.md | 11 +++--- include/crow/http_connection.h | 10 ++++-- tests/unittest.cpp | 62 ++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/docs/guides/routes.md b/docs/guides/routes.md index d3d22481a..3d39f8032 100644 --- a/docs/guides/routes.md +++ b/docs/guides/routes.md @@ -11,7 +11,7 @@ Using `/hello` means the client will need to access `http://example.com/hello` i A path can have parameters, for example `/hello/` will allow a client to input an int into the url which will be in the handler (something like `http://example.com/hello/42`).
Parameters can be ``, ``, ``, ``, or ``.
It's worth noting that the parameters also need to be defined in the handler, an example of using parameters would be to add 2 numbers based on input: -```cpp +```cpp CROW_ROUTE(app, "/add//") ([](int a, int b) { @@ -45,7 +45,10 @@ Please note that in order to return a response defined as a parameter you'll nee Alternatively, you can define the response in the body and return it (`#!cpp ([](){return crow::response()})`).
For more information on `crow::response` go [here](../../reference/structcrow_1_1response.html).

+!!! note + When your status code is not well-known e.g. crow::response(123) it will get converted into 500. + ### Return statement A `crow::response` is very strictly tied to a route. If you can have something in a response constructor, you can return it in a handler.

The main return type is `std::string`, although you could also return a `crow::json::wvalue` or `crow::multipart::message` directly.

@@ -60,14 +63,14 @@ to use the returnable class, you only need your class to publicly extend `crow:: Your class should look like the following: ```cpp -class a : public crow::returnable +class a : public crow::returnable { a() : returnable("text/plain"){}; - + ... ... ... - + std::string dump() override { return this.as_string(); diff --git a/include/crow/http_connection.h b/include/crow/http_connection.h index b994a6c50..c24b9d906 100644 --- a/include/crow/http_connection.h +++ b/include/crow/http_connection.h @@ -317,12 +317,16 @@ namespace crow buffers_.reserve(4 * (res.headers.size() + 5) + 3); if (!statusCodes.count(res.code)) - res.code = 500; { - auto& status = statusCodes.find(res.code)->second; - buffers_.emplace_back(status.data(), status.size()); + CROW_LOG_WARNING << this << " status code " + << "(" << res.code << ")" + << " not defined, returning 500 instead"; + res.code = 500; } + auto& status = statusCodes.find(res.code)->second; + buffers_.emplace_back(status.data(), status.size()); + if (res.code >= 400 && res.body.empty()) res.body = statusCodes[res.code].substr(9); diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 18810e33d..3b84e7981 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -571,6 +571,61 @@ TEST_CASE("multi_server") app2.stop(); } // multi_server + +TEST_CASE("undefined_status_code") +{ + SimpleApp app; + CROW_ROUTE(app, "/get123") + ([] { + //this status does not exists statusCodes map defined in include/crow/http_connection.h + const int undefinedStatusCode = 123; + return response(undefinedStatusCode, "this should return 500"); + }); + + CROW_ROUTE(app, "/get200") + ([] { + return response(200, "ok"); + }); + + auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(0).run_async(); + app.wait_for_server_start(); + + asio::io_service is; + auto sendRequestAndGetStatusCode = [&](const std::string& route) -> unsigned { + asio::ip::tcp::socket socket(is); + socket.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string(LOCALHOST_ADDRESS), app.port())); + + boost::asio::streambuf request; + std::ostream request_stream(&request); + request_stream << "GET " << route << " HTTP/1.0\r\n"; + request_stream << "Host: " << LOCALHOST_ADDRESS << "\r\n"; + request_stream << "Accept: */*\r\n"; + request_stream << "Connection: close\r\n\r\n"; + + // Send the request. + boost::asio::write(socket, request); + + boost::asio::streambuf response; + boost::asio::read_until(socket, response, "\r\n"); + + std::istream response_stream(&response); + std::string http_version; + response_stream >> http_version; + unsigned status_code = 0; + response_stream >> status_code; + + return status_code; + }; + + unsigned statusCode = sendRequestAndGetStatusCode("/get200"); + CHECK(statusCode == 200); + + statusCode = sendRequestAndGetStatusCode("/get123"); + CHECK(statusCode == 500); + + app.stop(); +} // undefined_status_code + TEST_CASE("json_read") { { @@ -2025,7 +2080,11 @@ TEST_CASE("send_file") app.handle(req, res); CHECK(200 == res.code); + + REQUIRE(res.headers.count("Content-Type")); CHECK("image/jpeg" == res.headers.find("Content-Type")->second); + + REQUIRE(res.headers.count("Content-Length")); CHECK(to_string(statbuf_cat.st_size) == res.headers.find("Content-Length")->second); } @@ -2039,7 +2098,10 @@ TEST_CASE("send_file") CHECK_NOTHROW(app.handle(req, res)); CHECK(200 == res.code); + REQUIRE(res.headers.count("Content-Type")); CHECK("text/plain" == res.headers.find("Content-Type")->second); + + REQUIRE(res.headers.count("Content-Length")); CHECK(to_string(statbuf_badext.st_size) == res.headers.find("Content-Length")->second); } } // send_file From 564b070d5f10a31611fdd8b58ff198ccfc015ac9 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Wed, 18 May 2022 15:56:16 +0300 Subject: [PATCH 25/29] Made test port static and replaced `REQUIRE` with `CHECK` and `if` Using port 0 seems to cause the test to be blocked Also added methods and status codes to Crow's documentation --- docs/guides/routes.md | 97 ++++++++++++++++++++++++++++++++++++++++++- tests/unittest.cpp | 22 ++++++---- 2 files changed, 108 insertions(+), 11 deletions(-) diff --git a/docs/guides/routes.md b/docs/guides/routes.md index 3d39f8032..e96ce6872 100644 --- a/docs/guides/routes.md +++ b/docs/guides/routes.md @@ -27,6 +27,52 @@ You can change the HTTP methods the route uses from just the default `GET` by us Crow handles `HEAD` and `OPTIONS` methods automatically. So adding those to your handler has no effect. +Crow defines the following methods: +``` +DELETE +GET +HEAD +POST +PUT + +CONNECT +OPTIONS +TRACE + +PATCH +PURGE + +COPY +LOCK +MKCOL +MOVE +PROPFIND +PROPPATCH +SEARCH +UNLOCK +BIND +REBIND +UNBIND +ACL + +REPORT +MKACTIVITY +CHECKOUT +MERGE + +SEARCH +NOTIFY +SUBSCRIBE +UNSUBSCRIBE + +MKCALENDAR + +LINK +UNLINK + +SOURCE +``` + ## Handler Basically a piece of code that gets executed whenever the client calls the associated route, usually in the form of a [lambda expression](https://en.cppreference.com/w/cpp/language/lambda). It can be as simple as `#!cpp ([](){return "Hello World"})`.

@@ -45,10 +91,57 @@ Please note that in order to return a response defined as a parameter you'll nee Alternatively, you can define the response in the body and return it (`#!cpp ([](){return crow::response()})`).
For more information on `crow::response` go [here](../../reference/structcrow_1_1response.html).

+ +Crow defines the following status codes: +``` +100 Continue +101 Switching Protocols + +200 OK +201 Created +202 Accepted +203 Non-Authoritative Information +204 No Content +205 Reset Content +206 Partial Content + +300 Multiple Choices +301 Moved Permanently +302 Found +303 See Other +304 Not Modified +307 Temporary Redirect +308 Permanent Redirect + +400 Bad Request +401 Unauthorized +403 Forbidden +404 Not Found +405 Method Not Allowed +407 Proxy Authentication Required +409 Conflict +410 Gone +413 Payload Too Large +415 Unsupported Media Type +416 Range Not Satisfiable +417 Expectation Failed +428 Precondition Required +429 Too Many Requests +451 Unavailable For Legal Reasons + +500 Internal Server Error +501 Not Implemented +502 Bad Gateway +503 Service Unavailable +504 Gateway Timeout +506 Variant Also Negotiates +``` + !!! note - When your status code is not well-known e.g. crow::response(123) it will get converted into 500. - + If your status code is not defined in the list above (e.g. `crow::response(123)`) Crow will return `500 Internal Server Error` instead. + + ### Return statement A `crow::response` is very strictly tied to a route. If you can have something in a response constructor, you can return it in a handler.

The main return type is `std::string`, although you could also return a `crow::json::wvalue` or `crow::multipart::message` directly.

diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 3b84e7981..296fad334 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -587,7 +587,7 @@ TEST_CASE("undefined_status_code") return response(200, "ok"); }); - auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(0).run_async(); + auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(45471).run_async(); app.wait_for_server_start(); asio::io_service is; @@ -2081,11 +2081,13 @@ TEST_CASE("send_file") CHECK(200 == res.code); - REQUIRE(res.headers.count("Content-Type")); - CHECK("image/jpeg" == res.headers.find("Content-Type")->second); + CHECK(res.headers.count("Content-Type")); + if (res.headers.count("Content-Type")) + CHECK("image/jpeg" == res.headers.find("Content-Type")->second); - REQUIRE(res.headers.count("Content-Length")); - CHECK(to_string(statbuf_cat.st_size) == res.headers.find("Content-Length")->second); + CHECK(res.headers.count("Content-Length")); + if (res.headers.count("Content-Length")) + CHECK(to_string(statbuf_cat.st_size) == res.headers.find("Content-Length")->second); } //Unknown extension check @@ -2098,11 +2100,13 @@ TEST_CASE("send_file") CHECK_NOTHROW(app.handle(req, res)); CHECK(200 == res.code); - REQUIRE(res.headers.count("Content-Type")); - CHECK("text/plain" == res.headers.find("Content-Type")->second); + CHECK(res.headers.count("Content-Type")); + if (res.headers.count("Content-Type")) + CHECK("text/plain" == res.headers.find("Content-Type")->second); - REQUIRE(res.headers.count("Content-Length")); - CHECK(to_string(statbuf_badext.st_size) == res.headers.find("Content-Length")->second); + CHECK(res.headers.count("Content-Length")); + if (res.headers.count("Content-Length")) + CHECK(to_string(statbuf_badext.st_size) == res.headers.find("Content-Length")->second); } } // send_file From f2b63f23f5d7a37612fda19d508480f55451a2a5 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Fri, 20 May 2022 14:14:55 +0300 Subject: [PATCH 26/29] Fix for issue where app.stop() blocks when a websocket is open --- include/crow/app.h | 21 ++++++++++++++++++--- include/crow/http_server.h | 9 +++------ include/crow/websocket.h | 25 ++++++------------------- tests/ssl/ssltest.cpp | 3 +++ tests/unittest.cpp | 7 +++++-- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/include/crow/app.h b/include/crow/app.h index 78cf1d9e7..a6ab3951c 100644 --- a/include/crow/app.h +++ b/include/crow/app.h @@ -19,6 +19,7 @@ #include "crow/http_request.h" #include "crow/http_server.h" #include "crow/task_timer.h" +#include "crow/websocket.h" #ifdef CROW_ENABLE_COMPRESSION #include "crow/compression.h" #endif @@ -59,9 +60,6 @@ namespace crow Crow() {} - - std::atomic websocket_count{0}; - /// Process an Upgrade request /// @@ -349,10 +347,26 @@ namespace crow else #endif { + std::vector websockets_to_close = websockets_; + for (auto websocket : websockets_to_close) + { + CROW_LOG_INFO << "Quitting Websocket: " << websocket; + websocket->close("Server Application Terminated"); + } if (server_) { server_->stop(); } } } + void add_websocket(crow::websocket::connection* conn) + { + websockets_.push_back(conn); + } + + void remove_websocket(crow::websocket::connection* conn) + { + std::remove(websockets_.begin(), websockets_.end(), conn); + } + /// Print the routing paths defined for each HTTP method void debug_print() { @@ -512,6 +526,7 @@ namespace crow bool server_started_{false}; std::condition_variable cv_started_; std::mutex start_mutex_; + std::vector websockets_; }; template using App = Crow; diff --git a/include/crow/http_server.h b/include/crow/http_server.h index 56b24834b..e674bf83c 100644 --- a/include/crow/http_server.h +++ b/include/crow/http_server.h @@ -161,21 +161,18 @@ namespace crow void stop() { - shutting_down_ = true; //Prevent the acceptor from taking new connections - while (handler_->websocket_count.load(std::memory_order_release) != 0) //Wait for the websockets to close properly - { - } + shutting_down_ = true; // Prevent the acceptor from taking new connections for (auto& io_service : io_service_pool_) { if (io_service != nullptr) { CROW_LOG_INFO << "Closing IO service " << &io_service; - io_service->stop(); //Close all io_services (and HTTP connections) + io_service->stop(); // Close all io_services (and HTTP connections) } } CROW_LOG_INFO << "Closing main IO service (" << &io_service_ << ')'; - io_service_.stop(); //Close main io_service + io_service_.stop(); // Close main io_service } void signal_clear() diff --git a/include/crow/websocket.h b/include/crow/websocket.h index 2573ca6d6..fa187c4ab 100644 --- a/include/crow/websocket.h +++ b/include/crow/websocket.h @@ -80,17 +80,16 @@ namespace crow adaptor_(std::move(adaptor)), handler_(handler), max_payload_bytes_(max_payload), - websocket_count_(handler_->websocket_count), 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)), - signals_(adaptor_.get_io_service()) + accept_handler_(std::move(accept_handler)) { if (!boost::iequals(req.get_header_value("upgrade"), "websocket")) { adaptor.close(); + handler_->remove_websocket(this); delete this; return; } @@ -100,16 +99,12 @@ namespace crow if (!accept_handler_(req)) { adaptor.close(); + handler_->remove_websocket(this); delete this; return; } } - - signals_.clear(); - for (auto snum : handler_->signals()) - signals_.add(snum); - // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // Sec-WebSocket-Version: 13 std::string magic = req.get_header_value("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -118,14 +113,6 @@ namespace crow uint8_t digest[20]; s.getDigestBytes(digest); - signals_.async_wait( - [&](const boost::system::error_code& e, int /*signal_number*/) { - if (!e) - { - CROW_LOG_INFO << "Quitting Websocket: " << this; - close("Server Application Terminated"); - } - }); start(crow::utility::base64encode((unsigned char*)digest, 20)); } @@ -460,6 +447,7 @@ namespace crow error_handler_(*this); adaptor_.shutdown_readwrite(); adaptor_.close(); + check_destroy(); } }); } @@ -499,6 +487,7 @@ namespace crow error_handler_(*this); adaptor_.shutdown_readwrite(); adaptor_.close(); + check_destroy(); } }); } @@ -647,7 +636,7 @@ namespace crow if (!is_close_handler_called_) if (close_handler_) close_handler_(*this, "uncleanly"); - websocket_count_--; + handler_->remove_websocket(this); if (sending_buffers_.empty() && !is_reading) delete this; } @@ -677,14 +666,12 @@ namespace crow bool error_occured_{false}; bool pong_received_{false}; bool is_close_handler_called_{false}; - std::atomic& websocket_count_; std::function open_handler_; std::function message_handler_; std::function close_handler_; std::function error_handler_; std::function accept_handler_; - boost::asio::signal_set signals_; }; } // namespace websocket } // namespace crow diff --git a/tests/ssl/ssltest.cpp b/tests/ssl/ssltest.cpp index c57445353..53ee435c2 100644 --- a/tests/ssl/ssltest.cpp +++ b/tests/ssl/ssltest.cpp @@ -75,6 +75,9 @@ TEST_CASE("SSL") { CHECK(std::string("Hello world, I'm keycrt.").substr((z * -1)) == to_test); } + + boost::system::error_code ec; + c.lowest_layer().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec); } /* diff --git a/tests/unittest.cpp b/tests/unittest.cpp index 402cde9d0..6a65fdc4a 100644 --- a/tests/unittest.cpp +++ b/tests/unittest.cpp @@ -7,7 +7,7 @@ #include #include #include -#include ` +#include #include "catch.hpp" #include "crow.h" @@ -2401,10 +2401,13 @@ TEST_CASE("websocket_max_payload") } catch (std::exception& e) { - CROW_LOG_DEBUG << e.what(); + CROW_LOG_DEBUG << "websocket_max_payload test passed due to the exception: " << e.what(); } } + boost::system::error_code ec; + c.lowest_layer().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec); + app.stop(); } // websocket_max_payload From 387c222ea2f05441e1a9fd15c58f5a0bef93fe17 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Fri, 20 May 2022 22:36:32 +0300 Subject: [PATCH 27/29] added creating qs from request body functionality + updated doc --- README.md | 3 ++- docs/guides/query-string.md | 5 +++++ docs/overrides/home.html | 10 ++++++---- include/crow/query_string.h | 21 ++++++++++++++++++--- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e2467fee3..9c99dbb85 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## Description -Crow is a C++ microframework for running web services. It uses routing similar to Python's Flask which makes it easy to use. It is also extremely fast, beating multiple existing C++ frameworks as well as non C++ frameworks. +Crow is a C++ framework for creating HTTP or Websocket web services. It uses routing similar to Python's Flask which makes it easy to use. It is also extremely fast, beating multiple existing C++ frameworks as well as non C++ frameworks. ### Features - Easy Routing (similar to flask). @@ -27,6 +27,7 @@ Crow is a C++ microframework for running web services. It uses routing similar t - Uses modern C++ (11/14) ### Still in development + - [Async support](https://github.com/CrowCpp/Crow/issues/258) - [HTTP/2 support](https://github.com/crowcpp/crow/issues/8) ## Documentation diff --git a/docs/guides/query-string.md b/docs/guides/query-string.md index 9b9e2cb82..e129953a3 100644 --- a/docs/guides/query-string.md +++ b/docs/guides/query-string.md @@ -2,6 +2,11 @@ A query string is the part of the URL that comes after a `?` character, it is us

Crow supports query strings through `crow::request::url_params`. The object is of type `crow::query_string` and can has the following functions:
+ +!!! note "Note      [:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)" + + A query string can be created from a Crow request or a url string. using the request will use the request body, which is useful for requests of type `application/x-www-form-urlencoded`. + ## get(name) Returns the value (as char*) based on the given key (or name). Returns `nullptr` if the key is not found. ## pop(name) diff --git a/docs/overrides/home.html b/docs/overrides/home.html index 53d5ea49e..e6235b9cd 100644 --- a/docs/overrides/home.html +++ b/docs/overrides/home.html @@ -6,7 +6,7 @@ - + @@ -148,6 +148,8 @@ code{

A Fast and Easy to use microframework for the web.

+
+

Crow is a C++ framework for creating HTTP or Websocket web services. It uses routing similar to Python's Flask which makes it easy to use. It is also extremely fast, beating multiple existing C++ frameworks as well as non C++ frameworks.


@@ -178,7 +180,7 @@ code{
-

Easy to get started

+

Easy to get started

#include "crow.h"
@@ -208,14 +210,14 @@ code{
 
-

JSON Support Built-in

+

JSON Support Built-in

-

URL parameter support as well!

+

URL parameter support as well!

CROW_ROUTE(app,"/hello/<int>")
diff --git a/include/crow/query_string.h b/include/crow/query_string.h
index 3f13e8a96..3ec284b98 100644
--- a/include/crow/query_string.h
+++ b/include/crow/query_string.h
@@ -8,8 +8,11 @@
 #include 
 #include 
 
+#include "crow/http_request.h"
+
 namespace crow
 {
+
 // ----------------------------------------------------------------------------
 // qs_parse (modified)
 // https://github.com/bartgrantham/qs_parse
@@ -22,7 +25,7 @@ int qs_strncmp(const char * s, const char * qs, size_t n);
  *  Also decodes the value portion of the k/v pair *in-place*.  In a future
  *  enhancement it will also have a compile-time option of sorting qs_kv
  *  alphabetically by key.  */
-int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);
+int qs_parse(char * qs, char * qs_kv[], int qs_kv_size, bool parse_url);
 
 
 /*  Used by qs_parse to decode the value portion of a k/v pair  */
@@ -96,7 +99,7 @@ inline int qs_strncmp(const char * s, const char * qs, size_t n)
 }
 
 
-inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
+inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size, bool parse_url = true)
 {
     int i, j;
     char * substr_ptr;
@@ -104,7 +107,7 @@ inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
     for(i=0; i
Date: Sat, 21 May 2022 12:32:32 +0300
Subject: [PATCH 28/29] changed functionality to avoid circular include

---
 docs/guides/query-string.md |  4 ----
 docs/guides/routes.md       |  6 ++++++
 include/crow/http_request.h |  9 +++++++++
 include/crow/query_string.h | 21 ++++-----------------
 4 files changed, 19 insertions(+), 21 deletions(-)

diff --git a/docs/guides/query-string.md b/docs/guides/query-string.md
index e129953a3..cb927c669 100644
--- a/docs/guides/query-string.md
+++ b/docs/guides/query-string.md
@@ -3,10 +3,6 @@ A query string is the part of the URL that comes after a `?` character, it is us
 
 Crow supports query strings through `crow::request::url_params`. The object is of type `crow::query_string` and can has the following functions:
-!!! note "Note      [:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)" - - A query string can be created from a Crow request or a url string. using the request will use the request body, which is useful for requests of type `application/x-www-form-urlencoded`. - ## get(name) Returns the value (as char*) based on the given key (or name). Returns `nullptr` if the key is not found. ## pop(name) diff --git a/docs/guides/routes.md b/docs/guides/routes.md index e96ce6872..0f86e20fd 100644 --- a/docs/guides/routes.md +++ b/docs/guides/routes.md @@ -81,6 +81,12 @@ Handlers can also use information from the request by adding it as a parameter ` You can also access the URL parameters in the handler using `#!cpp req.url_params.get("param_name");`. If the parameter doesn't exist, `nullptr` is returned.

+ +!!! note "Note      [:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)" + + parameters inside the body can be parsed using `#!cpp req.get_body_params();`. which is useful for requests of type `application/x-www-form-urlencoded`. Its format is similar to `url_params`. + + For more information on `crow::request` go [here](../../reference/structcrow_1_1request.html).

### Response diff --git a/include/crow/http_request.h b/include/crow/http_request.h index 1d4444cab..4d722c96b 100644 --- a/include/crow/http_request.h +++ b/include/crow/http_request.h @@ -62,6 +62,15 @@ namespace crow return http_ver_major == major && http_ver_minor == minor; } + /// Get the body as parameters in QS format. + + /// + /// This is meant to be used with requests of type "application/x-www-form-urlencoded" + const query_string get_body_params() + { + return query_string(body, false); + } + /// Send data to whoever made this request with a completion handler and return immediately. template void post(CompletionHandler handler) diff --git a/include/crow/query_string.h b/include/crow/query_string.h index 3ec284b98..6cd9a854b 100644 --- a/include/crow/query_string.h +++ b/include/crow/query_string.h @@ -8,8 +8,6 @@ #include #include -#include "crow/http_request.h" - namespace crow { @@ -290,6 +288,7 @@ inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t namespace crow { + struct request; /// A class to represent any data coming after the `?` in the request URL into key-value pairs. class query_string { @@ -334,27 +333,15 @@ namespace crow } - query_string(std::string url) - : url_(std::move(url)) + query_string(std::string params, bool url = true) + : url_(std::move(params)) { if (url_.empty()) return; key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); - int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT); - key_value_pairs_.resize(count); - } - - query_string(request req) - : url_(req.body) - { - if (url_.empty()) - return; - - key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); - - int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, false); + int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url); key_value_pairs_.resize(count); } From cd67331fad6eb9199988b43733562297f596cd35 Mon Sep 17 00:00:00 2001 From: The-EDev Date: Sat, 21 May 2022 14:51:48 +0300 Subject: [PATCH 29/29] formatting --- include/crow/query_string.h | 77 ++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/include/crow/query_string.h b/include/crow/query_string.h index 6cd9a854b..2a60f7ba1 100644 --- a/include/crow/query_string.h +++ b/include/crow/query_string.h @@ -16,14 +16,14 @@ namespace crow // https://github.com/bartgrantham/qs_parse // ---------------------------------------------------------------------------- /* Similar to strncmp, but handles URL-encoding for either string */ -int qs_strncmp(const char * s, const char * qs, size_t n); +int qs_strncmp(const char* s, const char* qs, size_t n); /* Finds the beginning of each key/value pair and stores a pointer in qs_kv. * Also decodes the value portion of the k/v pair *in-place*. In a future * enhancement it will also have a compile-time option of sorting qs_kv * alphabetically by key. */ -int qs_parse(char * qs, char * qs_kv[], int qs_kv_size, bool parse_url); +int qs_parse(char* qs, char* qs_kv[], int qs_kv_size, bool parse_url); /* Used by qs_parse to decode the value portion of a k/v pair */ @@ -97,7 +97,7 @@ inline int qs_strncmp(const char * s, const char * qs, size_t n) } -inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size, bool parse_url = true) +inline int qs_parse(char* qs, char* qs_kv[], int qs_kv_size, bool parse_url = true) { int i, j; char * substr_ptr; @@ -138,7 +138,7 @@ inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size, bool parse_url = #endif return i; -} + } inline int qs_decode(char * qs) @@ -286,7 +286,7 @@ inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t // ---------------------------------------------------------------------------- -namespace crow +namespace crow { struct request; /// A class to represent any data coming after the `?` in the request URL into key-value pairs. @@ -297,35 +297,34 @@ namespace crow query_string() { - } - query_string(const query_string& qs) - : url_(qs.url_) + query_string(const query_string& qs): + url_(qs.url_) { - for(auto p:qs.key_value_pairs_) + for (auto p : qs.key_value_pairs_) { - key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str())); + key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); } } - query_string& operator = (const query_string& qs) + query_string& operator=(const query_string& qs) { url_ = qs.url_; key_value_pairs_.clear(); - for(auto p:qs.key_value_pairs_) + for (auto p : qs.key_value_pairs_) { - key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str())); + key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); } return *this; } - query_string& operator = (query_string&& qs) + query_string& operator=(query_string&& qs) { key_value_pairs_ = std::move(qs.key_value_pairs_); char* old_data = (char*)qs.url_.c_str(); url_ = std::move(qs.url_); - for(auto& p:key_value_pairs_) + for (auto& p : key_value_pairs_) { p += (char*)url_.c_str() - old_data; } @@ -333,8 +332,8 @@ namespace crow } - query_string(std::string params, bool url = true) - : url_(std::move(params)) + query_string(std::string params, bool url = true): + url_(std::move(params)) { if (url_.empty()) return; @@ -345,7 +344,7 @@ namespace crow key_value_pairs_.resize(count); } - void clear() + void clear() { key_value_pairs_.clear(); url_.clear(); @@ -354,38 +353,38 @@ namespace crow friend std::ostream& operator<<(std::ostream& os, const query_string& qs) { os << "[ "; - for(size_t i = 0; i < qs.key_value_pairs_.size(); ++i) { + for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) + { if (i) os << ", "; os << qs.key_value_pairs_[i]; } os << " ]"; return os; - } /// Get a value from a name, used for `?name=value`. /// /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list(). - char* get (const std::string& name) const + char* get(const std::string& name) const { char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size()); return ret; } /// Works similar to \ref get() except it removes the item from the query string. - char* pop (const std::string& name) + char* pop(const std::string& name) { char* ret = get(name); if (ret != nullptr) { - for (unsigned int i = 0; i get_list (const std::string& name, bool use_brackets = true) const + std::vector get_list(const std::string& name, bool use_brackets = true) const { std::vector ret; std::string plus = name + (use_brackets ? "[]" : ""); char* element = nullptr; int count = 0; - while(1) + while (1) { element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); if (!element) @@ -415,17 +414,17 @@ namespace crow } /// Similar to \ref get_list() but it removes the - std::vector pop_list (const std::string& name, bool use_brackets = true) + std::vector pop_list(const std::string& name, bool use_brackets = true) { std::vector ret = get_list(name, use_brackets); if (!ret.empty()) { - for (unsigned int i = 0; i