mirror of
https://github.com/CrowCpp/Crow.git
synced 2024-06-07 21:10:44 +00:00
Merge branch 'master' into blueprint-middleware
This commit is contained in:
commit
a6bf90fa8b
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 <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();
|
||||
|
@ -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).
|
||||
|
@ -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/<int>"</span><span class="p">)</span>
|
||||
|
@ -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(
|
||||
|
@ -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...>;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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")
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user