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>
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:
```cpp
```cpp
CROW_ROUTE(app, "/add/<int>/<int>")
([](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 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"})`.<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>
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
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:
```cpp
class a : public crow::returnable
class a : public crow::returnable
{
a() : returnable("text/plain"){};
...
...
...
std::string dump() override
{
return this.as_string();

View File

@ -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(

View File

@ -59,6 +59,9 @@ namespace crow
Crow()
{}
std::atomic<int> websocket_count{0};
/// Process an Upgrade request
///
@ -128,6 +131,11 @@ namespace crow
return *this;
}
std::vector<int> signals()
{
return signals_;
}
/// Set the port that Crow will handle requests on
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_->set_tick_function(tick_interval_, tick_function_);
server_->signal_clear();
for (auto snum : signals_)
{
server_->signal_add(snum);

View File

@ -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);

View File

@ -161,9 +161,21 @@ namespace crow
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_)
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()
@ -195,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 (!shutting_down_)
{
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<Adaptor, Handler, 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]);
auto p = new Connection<Adaptor, Handler, 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]);
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:
@ -230,6 +245,7 @@ namespace crow
std::vector<detail::task_timer*> task_timer_pool_;
std::vector<std::function<std::string()>> get_cached_date_str_pool_;
tcp::acceptor acceptor_;
bool shutting_down_ = false;
boost::asio::signal_set signals_;
boost::asio::deadline_timer tick_timer_;

View File

@ -1,6 +1,7 @@
#pragma once
#include <boost/algorithm/string/predicate.hpp>
#include <boost/array.hpp>
#include "crow/logging.h"
#include "crow/socket_adaptors.h"
#include "crow/http_request.h"
#include "crow/TinySHA1.hpp"
@ -60,6 +61,7 @@ namespace crow
//
/// A websocket connection.
template<typename Adaptor, typename Handler>
class Connection : public connection
{
@ -78,11 +80,13 @@ 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))
accept_handler_(std::move(accept_handler)),
signals_(adaptor_.get_io_service())
{
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-Version: 13
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());
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));
}
@ -296,6 +314,7 @@ namespace crow
has_mask_ = false;
#else
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -321,6 +340,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -358,6 +378,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -392,6 +413,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -436,6 +458,7 @@ namespace crow
close_connection_ = true;
if (error_handler_)
error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close();
}
});
@ -474,6 +497,7 @@ namespace crow
close_connection_ = true;
if (error_handler_)
error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close();
}
});
@ -553,6 +577,7 @@ namespace crow
}
else
{
adaptor_.shutdown_readwrite();
adaptor_.close();
close_connection_ = true;
if (!is_close_handler_called_)
@ -622,6 +647,7 @@ namespace crow
if (!is_close_handler_called_)
if (close_handler_)
close_handler_(*this, "uncleanly");
websocket_count_--;
if (sending_buffers_.empty() && !is_reading)
delete this;
}
@ -651,12 +677,14 @@ namespace crow
bool error_occured_{false};
bool pong_received_{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&, const std::string&, bool)> message_handler_;
std::function<void(crow::websocket::connection&, const std::string&)> close_handler_;
std::function<void(crow::websocket::connection&)> error_handler_;
std::function<bool(const crow::request&)> accept_handler_;
boost::asio::signal_set signals_;
};
} // namespace websocket
} // namespace crow

View File

@ -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(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")
{
{
@ -2025,8 +2080,14 @@ TEST_CASE("send_file")
app.handle(req, res);
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
@ -2039,8 +2100,13 @@ TEST_CASE("send_file")
CHECK_NOTHROW(app.handle(req, res));
CHECK(200 == res.code);
CHECK("text/plain" == res.headers.find("Content-Type")->second);
CHECK(to_string(statbuf_badext.st_size) == res.headers.find("Content-Length")->second);
CHECK(res.headers.count("Content-Type"));
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