Merge branch 'master' into #421-Websockets-should-support-a-maximum-payload

This commit is contained in:
Farook Al-Sammarraie 2022-05-18 20:04:38 +03:00 committed by GitHub
commit 3e6c097733
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 259 additions and 42 deletions

View File

@ -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/<int>` will allow a client to input an int into the url which will be in the handler (something like `http://example.com/hello/42`).<br> A path can have parameters, for example `/hello/<int>` will allow a client to input an int into the url which will be in the handler (something like `http://example.com/hello/42`).<br>
Parameters can be `<int>`, `<uint>`, `<double>`, `<string>`, or `<path>`.<br> Parameters can be `<int>`, `<uint>`, `<double>`, `<string>`, or `<path>`.<br>
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: 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>/<int>") CROW_ROUTE(app, "/add/<int>/<int>")
([](int a, int b) ([](int a, int b)
{ {
@ -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 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 ## 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"})`.<br><br> 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"})`.<br><br>
@ -45,6 +91,56 @@ 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()})`).<br> Alternatively, you can define the response in the body and return it (`#!cpp ([](){return crow::response()})`).<br>
For more information on `crow::response` go [here](../../reference/structcrow_1_1response.html).<br><br> For more information on `crow::response` go [here](../../reference/structcrow_1_1response.html).<br><br>
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
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 ### 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.<br><br> 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.<br><br>
@ -60,14 +156,14 @@ to use the returnable class, you only need your class to publicly extend `crow::
Your class should look like the following: Your class should look like the following:
```cpp ```cpp
class a : public crow::returnable class a : public crow::returnable
{ {
a() : returnable("text/plain"){}; a() : returnable("text/plain"){};
... ...
... ...
... ...
std::string dump() override std::string dump() override
{ {
return this.as_string(); return this.as_string();

View File

@ -19,8 +19,8 @@ sock.onopen = ()=>{
sock.onerror = (e)=>{ sock.onerror = (e)=>{
console.log('error',e) console.log('error',e)
} }
sock.onclose = ()=>{ sock.onclose = (e)=>{
console.log('close') console.log('close', e)
} }
sock.onmessage = (e)=>{ sock.onmessage = (e)=>{
$("#log").val( $("#log").val(

View File

@ -59,6 +59,9 @@ namespace crow
Crow() Crow()
{} {}
std::atomic<int> websocket_count{0};
/// Process an Upgrade request /// Process an Upgrade request
/// ///
@ -128,6 +131,11 @@ namespace crow
return *this; return *this;
} }
std::vector<int> signals()
{
return signals_;
}
/// Set the port that Crow will handle requests on /// Set the port that Crow will handle requests on
self_t& port(std::uint16_t port) self_t& port(std::uint16_t port)
{ {
@ -313,7 +321,6 @@ namespace crow
{ {
server_ = std::move(std::unique_ptr<server_t>(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, nullptr))); server_ = std::move(std::unique_ptr<server_t>(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, nullptr)));
server_->set_tick_function(tick_interval_, tick_function_); server_->set_tick_function(tick_interval_, tick_function_);
server_->signal_clear();
for (auto snum : signals_) for (auto snum : signals_)
{ {
server_->signal_add(snum); server_->signal_add(snum);

View File

@ -317,12 +317,16 @@ namespace crow
buffers_.reserve(4 * (res.headers.size() + 5) + 3); buffers_.reserve(4 * (res.headers.size() + 5) + 3);
if (!statusCodes.count(res.code)) if (!statusCodes.count(res.code))
res.code = 500;
{ {
auto& status = statusCodes.find(res.code)->second; CROW_LOG_WARNING << this << " status code "
buffers_.emplace_back(status.data(), status.size()); << "(" << 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()) if (res.code >= 400 && res.body.empty())
res.body = statusCodes[res.code].substr(9); res.body = statusCodes[res.code].substr(9);

View File

@ -161,9 +161,21 @@ namespace crow
void stop() void stop()
{ {
io_service_.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
{
}
for (auto& io_service : io_service_pool_) 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() void signal_clear()
@ -195,33 +207,36 @@ namespace crow
void do_accept() void do_accept()
{ {
uint16_t service_idx = pick_io_service_idx(); if (!shutting_down_)
asio::io_service& is = *io_service_pool_[service_idx]; {
task_queue_length_pool_[service_idx]++; uint16_t service_idx = pick_io_service_idx();
CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[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<Adaptor, Handler, Middlewares...>( auto p = new Connection<Adaptor, Handler, Middlewares...>(
is, handler_, server_name_, middlewares_, 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]); get_cached_date_str_pool_[service_idx], *task_timer_pool_[service_idx], adaptor_ctx_, task_queue_length_pool_[service_idx]);
acceptor_.async_accept( acceptor_.async_accept(
p->socket(), p->socket(),
[this, p, &is, service_idx](boost::system::error_code ec) { [this, p, &is, service_idx](boost::system::error_code ec) {
if (!ec) if (!ec)
{ {
is.post( is.post(
[p] { [p] {
p->start(); p->start();
}); });
} }
else else
{ {
task_queue_length_pool_[service_idx]--; task_queue_length_pool_[service_idx]--;
CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx]; CROW_LOG_DEBUG << &is << " {" << service_idx << "} queue length: " << task_queue_length_pool_[service_idx];
delete p; delete p;
} }
do_accept(); do_accept();
}); });
}
} }
private: private:
@ -230,6 +245,7 @@ namespace crow
std::vector<detail::task_timer*> task_timer_pool_; std::vector<detail::task_timer*> task_timer_pool_;
std::vector<std::function<std::string()>> get_cached_date_str_pool_; std::vector<std::function<std::string()>> get_cached_date_str_pool_;
tcp::acceptor acceptor_; tcp::acceptor acceptor_;
bool shutting_down_ = false;
boost::asio::signal_set signals_; boost::asio::signal_set signals_;
boost::asio::deadline_timer tick_timer_; boost::asio::deadline_timer tick_timer_;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/array.hpp> #include <boost/array.hpp>
#include "crow/logging.h"
#include "crow/socket_adaptors.h" #include "crow/socket_adaptors.h"
#include "crow/http_request.h" #include "crow/http_request.h"
#include "crow/TinySHA1.hpp" #include "crow/TinySHA1.hpp"
@ -60,6 +61,7 @@ namespace crow
// //
/// A websocket connection. /// A websocket connection.
template<typename Adaptor, typename Handler> template<typename Adaptor, typename Handler>
class Connection : public connection class Connection : public connection
{ {
@ -78,11 +80,13 @@ namespace crow
adaptor_(std::move(adaptor)), adaptor_(std::move(adaptor)),
handler_(handler), handler_(handler),
max_payload_bytes_(max_payload), max_payload_bytes_(max_payload),
websocket_count_(handler_->websocket_count),
open_handler_(std::move(open_handler)), open_handler_(std::move(open_handler)),
message_handler_(std::move(message_handler)), message_handler_(std::move(message_handler)),
close_handler_(std::move(close_handler)), close_handler_(std::move(close_handler)),
error_handler_(std::move(error_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")) if (!boost::iequals(req.get_header_value("upgrade"), "websocket"))
{ {
@ -101,6 +105,11 @@ namespace crow
} }
} }
signals_.clear();
for (auto snum : handler_->signals())
signals_.add(snum);
// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
// Sec-WebSocket-Version: 13 // 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";
@ -108,6 +117,15 @@ namespace crow
s.processBytes(magic.data(), magic.size()); s.processBytes(magic.data(), magic.size());
uint8_t digest[20]; 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 Websocket: " << this;
close("Server Application Terminated");
}
});
start(crow::utility::base64encode((unsigned char*)digest, 20)); start(crow::utility::base64encode((unsigned char*)digest, 20));
} }
@ -296,6 +314,7 @@ namespace crow
has_mask_ = false; has_mask_ = false;
#else #else
close_connection_ = true; close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
@ -321,6 +340,7 @@ namespace crow
else else
{ {
close_connection_ = true; close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
@ -358,6 +378,7 @@ namespace crow
else else
{ {
close_connection_ = true; close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
@ -392,6 +413,7 @@ namespace crow
else else
{ {
close_connection_ = true; close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
@ -436,6 +458,7 @@ namespace crow
close_connection_ = true; close_connection_ = true;
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
} }
}); });
@ -474,6 +497,7 @@ namespace crow
close_connection_ = true; close_connection_ = true;
if (error_handler_) if (error_handler_)
error_handler_(*this); error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
} }
}); });
@ -553,6 +577,7 @@ namespace crow
} }
else else
{ {
adaptor_.shutdown_readwrite();
adaptor_.close(); adaptor_.close();
close_connection_ = true; close_connection_ = true;
if (!is_close_handler_called_) if (!is_close_handler_called_)
@ -622,6 +647,7 @@ namespace crow
if (!is_close_handler_called_) if (!is_close_handler_called_)
if (close_handler_) if (close_handler_)
close_handler_(*this, "uncleanly"); close_handler_(*this, "uncleanly");
websocket_count_--;
if (sending_buffers_.empty() && !is_reading) if (sending_buffers_.empty() && !is_reading)
delete this; delete this;
} }
@ -651,12 +677,14 @@ namespace crow
bool error_occured_{false}; bool error_occured_{false};
bool pong_received_{false}; bool pong_received_{false};
bool is_close_handler_called_{false}; bool is_close_handler_called_{false};
std::atomic<int>& websocket_count_;
std::function<void(crow::websocket::connection&)> open_handler_; std::function<void(crow::websocket::connection&)> open_handler_;
std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_; std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_;
std::function<void(crow::websocket::connection&, const std::string&)> close_handler_; std::function<void(crow::websocket::connection&, const std::string&)> close_handler_;
std::function<void(crow::websocket::connection&)> error_handler_; std::function<void(crow::websocket::connection&)> error_handler_;
std::function<bool(const crow::request&)> accept_handler_; std::function<bool(const crow::request&)> accept_handler_;
boost::asio::signal_set signals_;
}; };
} // namespace websocket } // namespace websocket
} // namespace crow } // namespace crow

View File

@ -571,6 +571,61 @@ TEST_CASE("multi_server")
app2.stop(); app2.stop();
} // multi_server } // 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(45471).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") TEST_CASE("json_read")
{ {
{ {
@ -2025,8 +2080,14 @@ TEST_CASE("send_file")
app.handle(req, res); app.handle(req, res);
CHECK(200 == res.code); CHECK(200 == res.code);
CHECK("image/jpeg" == res.headers.find("Content-Type")->second);
CHECK(to_string(statbuf_cat.st_size) == res.headers.find("Content-Length")->second); CHECK(res.headers.count("Content-Type"));
if (res.headers.count("Content-Type"))
CHECK("image/jpeg" == res.headers.find("Content-Type")->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 //Unknown extension check
@ -2039,8 +2100,13 @@ TEST_CASE("send_file")
CHECK_NOTHROW(app.handle(req, res)); CHECK_NOTHROW(app.handle(req, res));
CHECK(200 == res.code); CHECK(200 == res.code);
CHECK("text/plain" == res.headers.find("Content-Type")->second); CHECK(res.headers.count("Content-Type"));
CHECK(to_string(statbuf_badext.st_size) == res.headers.find("Content-Length")->second); if (res.headers.count("Content-Type"))
CHECK("text/plain" == res.headers.find("Content-Type")->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 } // send_file