Merge branch 'master' into blueprint-middleware

This commit is contained in:
Farook Al-Sammarraie 2022-05-23 09:43:17 +03:00 committed by GitHub
commit a6bf90fa8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 465 additions and 101 deletions

View File

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

View File

@ -2,6 +2,7 @@ A query string is the part of the URL that comes after a `?` character, it is us
<br><br>
Crow supports query strings through `crow::request::url_params`. The object is of type `crow::query_string` and can has the following functions:<br>
## get(name)
Returns the value (as char*) based on the given key (or name). Returns `nullptr` if the key is not found.
## pop(name)

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>
@ -35,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.<br><br>
!!! note "Note &nbsp;&nbsp;&nbsp;&nbsp; <span class="tag">[:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)</span>"
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).<br><br>
### Response
@ -45,6 +97,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 +162,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

@ -1,19 +1,19 @@
Websockets are a way of connecting a client and a server without the request response nature of HTTP.<br><br>
## Routes
To create a websocket in Crow, you need a websocket route.<br>
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.<br>
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})`<br><br>
- `#!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);
});
```
<br><br>
For more info go [here](../../reference/classcrow_1_1_web_socket_rule.html).
## Maximum payload size
<span class="tag">[:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)</span>
The maximum payload size that a connection accepts can be adjusted either globally by using `#!cpp app.websocket_max_payload(<value in bytes>)` or per route by using `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload(<value in bytes>)`. 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).

View File

@ -6,7 +6,7 @@
<meta property="og:title" content="CrowCpp"/>
<meta property="og:type" content="website" />
<meta property="og:description" content="A Fast and Easy to use microframework for the web."/>
<meta name="description" content="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.">
<meta name="description" content="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.">
<meta property="og:image" content="assets/og_img.png" />
<meta property="og:url" content="https://crowcpp.org">
<meta property="twitter:card" content="summary_large_image">
@ -148,6 +148,8 @@ code{
<h1 style="text-align:center;">A Fast and Easy to use microframework for the web.</h1>
<hr>
<p style="text-align:center;">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.</p>
<hr>
<section class="csection">
@ -178,7 +180,7 @@ code{
<section class="ssection">
<div class="sdescription">
<h2 style="text-align: center;">Easy to get started</h3>
<h2 style="text-align: center;">Easy to get started</h2>
</div>
<div class="scontent">
<div class="highlight"><pre id="__code_0"><span></span><button class="md-clipboard md-icon" title="Copy to clipboard" data-clipboard-target="#__code_0 > code"></button><code><span class="cp">#include</span> <span class="cpf">"crow.h"</span><span class="cp"></span>
@ -208,14 +210,14 @@ code{
</code></pre></div>
</div>
<div class="sdescription">
<h2 style="text-align: center;">JSON Support Built-in</h3>
<h2 style="text-align: center;">JSON Support Built-in</h2>
</div>
</section>
<section class="ssection">
<div class="sdescription">
<h2 style="text-align: center;">URL parameter support as well!</h3>
<h2 style="text-align: center;">URL parameter support as well!</h2>
</div>
<div class="scontent">
<div class="highlight"><pre id="__code_2"><span></span><button class="md-clipboard md-icon" title="Copy to clipboard" data-clipboard-target="#__code_2 > code"></button><code><span class="cp">CROW_ROUTE</span><span class="p">(</span><span class="n">app</span><span class="p">,</span><span class="s">"/hello/&lt;int&gt;"</span><span class="p">)</span>

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

@ -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
@ -103,6 +104,19 @@ 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;
return *this;
}
/// Get the default max payload size for websockets
uint64_t websocket_max_payload()
{
return max_payload_;
}
self_t& signal_clear()
{
signals_.clear();
@ -115,6 +129,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)
{
@ -300,7 +319,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);
@ -329,10 +347,26 @@ namespace crow
else
#endif
{
std::vector<crow::websocket::connection*> 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()
{
@ -462,6 +496,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";
@ -491,6 +526,7 @@ namespace crow
bool server_started_{false};
std::condition_variable cv_started_;
std::mutex start_mutex_;
std::vector<crow::websocket::connection*> websockets_;
};
template<typename... Middlewares>
using App = Crow<Middlewares...>;

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

@ -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<typename CompletionHandler>
void post(CompletionHandler handler)

View File

@ -161,9 +161,18 @@ namespace crow
void stop()
{
io_service_.stop();
shutting_down_ = true; // Prevent the acceptor from taking new connections
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 +204,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 +242,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

@ -10,19 +10,20 @@
namespace crow
{
// ----------------------------------------------------------------------------
// qs_parse (modified)
// 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);
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 +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)
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 +105,7 @@ inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
// find the beginning of the k/v substrings or the fragment
substr_ptr = qs + strcspn(qs, "?#");
substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
if (substr_ptr[0] != '\0')
substr_ptr++;
else
@ -137,7 +138,7 @@ inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
#endif
return i;
}
}
inline int qs_decode(char * qs)
@ -285,8 +286,9 @@ 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.
class query_string
{
@ -295,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;
}
@ -331,19 +332,19 @@ 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);
int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url);
key_value_pairs_.resize(count);
}
void clear()
void clear()
{
key_value_pairs_.clear();
url_.clear();
@ -352,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<key_value_pairs_.size(); i++)
for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
{
std::string str_item(key_value_pairs_[i]);
if (str_item.substr(0, name.size()+1) == name+'=')
if (str_item.substr(0, name.size() + 1) == name + '=')
{
key_value_pairs_.erase(key_value_pairs_.begin()+i);
key_value_pairs_.erase(key_value_pairs_.begin() + i);
break;
}
}
@ -395,14 +396,14 @@ namespace crow
///
/// Note: Square brackets in the above example are controlled by `use_brackets` boolean (true by default). If set to false, the example becomes `?name=value1,name=value2...name=valuen`
std::vector<char*> get_list (const std::string& name, bool use_brackets = true) const
std::vector<char*> get_list(const std::string& name, bool use_brackets = true) const
{
std::vector<char*> 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)
@ -413,17 +414,17 @@ namespace crow
}
/// Similar to \ref get_list() but it removes the
std::vector<char*> pop_list (const std::string& name, bool use_brackets = true)
std::vector<char*> pop_list(const std::string& name, bool use_brackets = true)
{
std::vector<char*> ret = get_list(name, use_brackets);
if (!ret.empty())
{
for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
{
std::string str_item(key_value_pairs_[i]);
if ((use_brackets ? (str_item.substr(0, name.size()+3) == name+"[]=") : (str_item.substr(0, name.size()+1) == name+'=')))
if ((use_brackets ? (str_item.substr(0, name.size() + 3) == name + "[]=") : (str_item.substr(0, name.size() + 1) == name + '=')))
{
key_value_pairs_.erase(key_value_pairs_.begin()+i--);
key_value_pairs_.erase(key_value_pairs_.begin() + i--);
}
}
}
@ -436,12 +437,12 @@ namespace crow
/// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
///
/// if your query string has both empty brackets and ones with a key inside, use pop_list() to get all the values without a key before running this method.
std::unordered_map<std::string, std::string> get_dict (const std::string& name) const
std::unordered_map<std::string, std::string> get_dict(const std::string& name) const
{
std::unordered_map<std::string, std::string> ret;
int count = 0;
while(1)
while (1)
{
if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
ret.insert(*element);
@ -452,17 +453,17 @@ namespace crow
}
/// Works the same as \ref get_dict() but removes the values from the query string.
std::unordered_map<std::string, std::string> pop_dict (const std::string& name)
std::unordered_map<std::string, std::string> pop_dict(const std::string& name)
{
std::unordered_map<std::string, std::string> ret = get_dict(name);
if (!ret.empty())
{
for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
{
std::string str_item(key_value_pairs_[i]);
if (str_item.substr(0, name.size()+1) == name+'[')
if (str_item.substr(0, name.size() + 1) == name + '[')
{
key_value_pairs_.erase(key_value_pairs_.begin()+i--);
key_value_pairs_.erase(key_value_pairs_.begin() + i--);
}
}
}
@ -472,7 +473,7 @@ namespace crow
std::vector<std::string> keys() const
{
std::vector<std::string> ret;
for (auto element: key_value_pairs_)
for (auto element : key_value_pairs_)
{
std::string str_element(element);
ret.emplace_back(str_element.substr(0, str_element.find('=')));
@ -485,4 +486,4 @@ namespace crow
std::vector<char*> key_value_pairs_;
};
} // end namespace
} // namespace crow

View File

@ -436,7 +436,8 @@ namespace crow
public:
WebSocketRule(std::string rule, App* app):
BaseRule(std::move(rule)),
app_(app)
app_(app),
max_payload_(UINT64_MAX)
{}
void validate() override
@ -450,15 +451,24 @@ namespace crow
void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override
{
new crow::websocket::Connection<SocketAdaptor, App>(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_);
max_payload_ = max_payload_override_ ? max_payload_ : app_->websocket_max_payload();
new crow::websocket::Connection<SocketAdaptor, App>(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
{
new crow::websocket::Connection<SSLAdaptor, App>(req, std::move(adaptor), app_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_);
new crow::websocket::Connection<SSLAdaptor, App>(req, std::move(adaptor), app_, max_payload_, open_handler_, message_handler_, close_handler_, error_handler_, accept_handler_);
}
#endif
/// Override the global payload limit for this single WebSocket rule
self_t& max_payload(uint64_t max_payload)
{
max_payload_ = max_payload;
max_payload_override_ = true;
return *this;
}
template<typename Func>
self_t& onopen(Func f)
{
@ -501,6 +511,8 @@ namespace crow
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_;
uint64_t max_payload_;
bool max_payload_override_ = false;
};
/// Allows the user to assign parameters using functions.

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
{
@ -69,7 +71,7 @@ namespace crow
///
/// Requires a request with an "Upgrade: websocket" header.<br>
/// 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<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,
@ -77,6 +79,7 @@ namespace crow
std::function<bool(const crow::request&)> 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)),
@ -86,6 +89,7 @@ namespace crow
if (!boost::iequals(req.get_header_value("upgrade"), "websocket"))
{
adaptor.close();
handler_->remove_websocket(this);
delete this;
return;
}
@ -95,6 +99,7 @@ namespace crow
if (!accept_handler_(req))
{
adaptor.close();
handler_->remove_websocket(this);
delete this;
return;
}
@ -107,6 +112,7 @@ namespace crow
s.processBytes(magic.data(), magic.size());
uint8_t digest[20];
s.getDigestBytes(digest);
start(crow::utility::base64encode((unsigned char*)digest, 20));
}
@ -200,6 +206,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)
@ -290,6 +301,7 @@ namespace crow
has_mask_ = false;
#else
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -315,6 +327,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -352,6 +365,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -386,6 +400,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -395,7 +410,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),
@ -422,7 +445,9 @@ namespace crow
close_connection_ = true;
if (error_handler_)
error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close();
check_destroy();
}
});
}
@ -460,7 +485,9 @@ namespace crow
close_connection_ = true;
if (error_handler_)
error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close();
check_destroy();
}
});
}
@ -539,6 +566,7 @@ namespace crow
}
else
{
adaptor_.shutdown_readwrite();
adaptor_.close();
close_connection_ = true;
if (!is_close_handler_called_)
@ -608,6 +636,7 @@ namespace crow
if (!is_close_handler_called_)
if (close_handler_)
close_handler_(*this, "uncleanly");
handler_->remove_websocket(this);
if (sending_buffers_.empty() && !is_reading)
delete this;
}
@ -626,6 +655,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};

View File

@ -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);
}
/*

View File

@ -572,6 +572,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")
{
{
@ -2117,8 +2172,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
@ -2131,8 +2192,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
@ -2365,6 +2431,78 @@ 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 << "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
#ifdef CROW_ENABLE_COMPRESSION
TEST_CASE("zlib_compression")
{