Compare commits

...

30 Commits

Author SHA1 Message Date
Nabzokek Zubkenkanakov 4fbd9b471f
Header files documentation (#804)
* Added documentation of "crow::App", "crow::SimpleApp", "crow"
(namespace), all macros defined in "crow/app.h".
* Added documentation entry for file crow/TinySHA1.hpp, namespace sha1,
class sha1::SHA1, namespace crow and namespace crow::websocket
* Documented crow/app.h and also improved crow/TinySHA1.hpp documentation
* Update app.h
2024-04-24 23:35:13 +02:00
bugdea1er 3e9eb1172a Define GET_IO_SERVICE once 2024-04-19 17:06:13 +02:00
bugdea1er e3ff9238a4 Add option to use Boost.Asio 2024-04-19 17:06:13 +02:00
ssams 96e049666e add HTTP status 406 2024-04-19 17:05:48 +02:00
ItsAlbertZhang ad337a8a86 fix: compilation error with libc++ 2024-03-19 16:59:39 +01:00
Tristan CADET d0f1991e38 fix: parse body when unsupported HTTP/2 upgrade is requested
Crow does not support HTTP/2 at the moment.
According to the RFC https://datatracker.ietf.org/doc/html/rfc7540#section-3.2
"A server that does not support HTTP/2 can respond to the request as though the Upgrade header field were absent"
2024-03-17 15:02:25 +01:00
Gulliver da570e1cbd added pip install for mike 2024-03-12 21:52:15 +01:00
sina-rostami 106aeb0f0d Add indentation to json dump method
Closes #747
2024-03-10 21:41:43 +01:00
Corentin Schreiber 049490c2c9
Add configurable exception handler (#637)
* Added exception_handler()

* Fixed worker crash if exception thrown in catch-all handler
2024-03-10 13:52:13 +01:00
dependabot[bot] c95e33ac0a Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-10 13:46:42 +01:00
dependabot[bot] 7e397f7fbf Bump dawidd6/action-download-artifact from 2 to 3
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 2 to 3.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-10 13:46:10 +01:00
dependabot[bot] 0c5a3dcb34 Bump actions/upload-artifact from 3 to 4
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-10 13:45:33 +01:00
StefanoPetrilli db059ce4c4 adds flag to avoid adding blueprints multiple times 2024-03-10 13:11:56 +01:00
StefanoPetrilli 55e604f939 adds a flag to avoid adding static routes multiple times 2024-03-10 13:11:56 +01:00
StefanoPetrilli 8f7e28ef91 adds new tests 2024-03-10 13:11:56 +01:00
StefanoPetrilli 00bd06d6d3 update existing tests 2024-03-10 13:11:56 +01:00
StefanoPetrilli a62956b61a split the different responsibilities of validate into different functions 2024-03-10 13:11:56 +01:00
gittiver fda1109e1a Create dependabot.yml 2024-03-10 13:11:19 +01:00
Harri Porten e68c3d6a8d Fixed some harmless typos. 2024-03-10 13:10:48 +01:00
gittiver 08acd3a5fa Update doxygen-gh-pages.yml, added mike for versioning 2024-03-10 12:07:57 +01:00
bruvey c47540bb8c Update http_connection.h 2024-03-02 10:19:00 +01:00
bruvey d3bd942223 Update http_connection.h 2024-03-02 10:19:00 +01:00
James Knight cb38b9fe4f add spdx license id to compiled header
This commit adjusts the merging script to include a SPDX license
identifier at the start of the single header file. This can help users
of Crow which have environments using license scanning tools to help
manage/monitor used implementation.

Signed-off-by: James Knight <james.d.knight@live.com>
2024-02-23 10:45:30 +01:00
June Han 5ecd04ade2 fix: call after_handlers for legitimate requests without body part 2024-02-21 21:37:51 +01:00
David Petkovsek 1e864a2f32 Handle _CROW_ILL or _CROW_ICD being NOTFOUND
If the variable is NOTFOUND then set it to "". Then at the end after possible list appends. If the variable is not "" then update the target properties.

This gets rid of a warning
2024-02-20 23:22:47 +01:00
witcherofthorns 3f632dd71b small explanations in the example on using CORS middleware in Crow v1.0 2024-02-18 13:54:20 +01:00
Corentin Schreiber df756fed45
Use const std::string& as argument to route() (#684) 2024-01-29 18:10:20 +01:00
JuneHan 973d5fa1cd
fix: minimize the precision loss when dumping double to string (#712)
* fix: minimize the precision loss when dumping double to string
* abandon one-time macro
* replace hard-coded buffer size, replace `sprintf` with `snprintf`
* replace `DECIMAL_DIG` with `DBL_DECIMAL_DIG`
* Update json.h changed to C++11 DECIMAL_DIGIT
* added cfloat include
* identify float and double as different numeral types, and approach their precisions accordingly

---------

Co-authored-by: June Han <jun_h@pretia.co.jp>
Co-authored-by: gittiver <gulliver@traumkristalle.net>
2024-01-29 15:25:26 +01:00
Gulliver fb171f5195 package format changed to zip, version set to >1.1.0 (first release) 2024-01-29 15:16:35 +01:00
bruvey 4555ffd0c9
Fix #754 (#755)
* Update http_response.h
2024-01-26 19:38:05 +01:00
29 changed files with 959 additions and 250 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@ -31,7 +31,7 @@ jobs:
# ubuntu-18.04 does not work due to compile error on asio
# windows-2019 not included to spare free minutes
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Prepare dependencies
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
@ -102,7 +102,7 @@ jobs:
- name: Save report
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest'
with:
name: coveralls.json

View File

@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Prepare dependencies
run: sudo apt-get update && sudo apt-get -yq install libasio-dev doxygen mkdocs graphviz zlib1g-dev gcc clang make cmake python3 python3-pip git openssl libssl-dev
- name: prepate pip dependencies
run: pip3 install mkdocs-material mkdocs-redirects pyyaml mkdocs-meta-descriptions-plugin --no-input
run: pip3 install mkdocs-material mkdocs-redirects pyyaml mkdocs-meta-descriptions-plugin mike --no-input
- name: configure
run: cmake -B build -DCROW_AMALGAMATE=ON
- name: clean generated docs dir
@ -29,7 +29,12 @@ jobs:
run: doxygen
- name: run mkdocs
run: mkdocs build
- name: Setup doc deploy
run: |
git config --global user.name Docs deploy
git config --global user.email docs@dummy.bot.com
- name: run mike
run: mike deploy master
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:

View File

@ -16,7 +16,7 @@ jobs:
if: github.event.workflow_run.conclusion == 'success'
steps:
- name: Download artifact
uses: dawidd6/action-download-artifact@v2
uses: dawidd6/action-download-artifact@v3
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
workflow_conclusion: success

View File

@ -6,6 +6,7 @@ cmake_minimum_required(VERSION 3.15.0 FATAL_ERROR)
# Define the project name and language
project(Crow
LANGUAGES CXX
VERSION 1.1.1
)
# Make sure Findasio.cmake module is found
@ -45,6 +46,7 @@ option(CROW_BUILD_EXAMPLES "Build the examples in the project" ${CROW_I
option(CROW_BUILD_TESTS "Build the tests in the project" ${CROW_IS_MAIN_PROJECT})
option(CROW_AMALGAMATE "Combine all headers into one" OFF)
option(CROW_INSTALL "Add install step for Crow" ON )
option(CROW_USE_BOOST "Use Boost.Asio for Crow" OFF)
# Possible values: ssl, compression
option(CROW_FEATURES "Enable features extending Crow's abilities" "")
@ -61,12 +63,20 @@ target_include_directories(Crow
$<INSTALL_INTERFACE:include>
)
find_package(asio REQUIRED)
target_link_libraries(Crow
INTERFACE
asio::asio
)
if(CROW_USE_BOOST)
find_package(Boost 1.64 COMPONENTS system date_time REQUIRED)
target_link_libraries(Crow
INTERFACE
Boost::boost Boost::system Boost::date_time
)
target_compile_definitions(Crow INTERFACE CROW_USE_BOOST)
else()
find_package(asio REQUIRED)
target_link_libraries(Crow
INTERFACE
asio::asio
)
endif()
target_compile_definitions(Crow INTERFACE "")
@ -153,7 +163,7 @@ if(CROW_INSTALL)
)
endif()
set(CPACK_GENERATOR "DEB")
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_NAME "Crow")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "CrowCpp")
set(CPACK_PACKAGE_VENDOR "CrowCpp")

View File

@ -772,7 +772,7 @@ SHOW_FILES = YES
# Folder Tree View (if specified).
# The default value is: YES.
SHOW_NAMESPACES = NO
SHOW_NAMESPACES = YES
# The FILE_VERSION_FILTER tag can be used to specify a program or script that
# doxygen should invoke to get the current version for each file (typically from

View File

@ -11,11 +11,11 @@
## Description
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.
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).
- Type-safe Handlers.
- Easy Routing (similar to Flask).
- Type-safe handlers.
- Blazingly fast (see [this benchmark](https://github.com/ipkn/crow-benchmark) and [this benchmark](https://github.com/guteksan/REST-CPP-benchmark)).
- Built in JSON support.
- [Mustache](http://mustache.github.io/) based templating library (`crow::mustache`).
@ -77,7 +77,7 @@ CROW_ROUTE(app,"/hello/<int>")
```
Handler arguments type check at compile time
```cpp
// Compile error with message "Handler type is mismatched with URL paramters"
// Compile error with message "Handler type is mismatched with URL parameters"
CROW_ROUTE(app,"/another/<int>")
([](int a, int b){
return crow::response(500);

View File

@ -28,6 +28,13 @@ check_required_components("@PROJECT_NAME@")
get_target_property(_CROW_ILL Crow::Crow INTERFACE_LINK_LIBRARIES)
get_target_property(_CROW_ICD Crow::Crow INTERFACE_COMPILE_DEFINITIONS)
if(_CROW_ILL STREQUAL "_CROW_ILL-NOTFOUND")
set(_CROW_ILL "")
endif()
if(_CROW_ICD STREQUAL "_CROW_ICD-NOTFOUND")
set(_CROW_ICD "")
endif()
list(REMOVE_ITEM _CROW_ILL "ZLIB::ZLIB" "OpenSSL::SSL")
list(REMOVE_ITEM _CROW_ICD "CROW_ENABLE_SSL" "CROW_ENABLE_COMPRESSION")
@ -41,7 +48,13 @@ if("ssl" IN_LIST CROW_FEATURES)
list(APPEND _CROW_ICD "CROW_ENABLE_SSL")
endif()
set_target_properties(Crow::Crow PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${_CROW_ICD}"
INTERFACE_LINK_LIBRARIES "${_CROW_ILL}"
)
if( NOT (_CROW_ICD STREQUAL "" ) )
set_target_properties(Crow::Crow PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "${_CROW_ICD}"
)
endif()
if( NOT (_CROW_ILL STREQUAL "" ) )
set_target_properties(Crow::Crow PROPERTIES
INTERFACE_LINK_LIBRARIES "${_CROW_ILL}"
)
endif()

View File

@ -125,6 +125,7 @@ Crow defines the following status codes:
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
409 Conflict
410 Gone

View File

@ -1,6 +1,14 @@
#include "crow.h"
#include "crow/middlewares/cors.h"
// Warning!
// If you want to use CORS with OPTIONS cache on browser requests,
// be sure to specify each headers you use, please do not use "*"
// else otherwise the browser will ignore you
// Example:
// .headers("Origin", "Content-Type", "Accept", *Your-Headers*)
// .max_age(5);
int main()
{
// Enable CORS

View File

@ -1,8 +1,4 @@
/*
*
* TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based
* on the implementation in boost::uuid::details.
*
/*
* SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1
*
* Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>
@ -19,14 +15,35 @@
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
* \file TinySHA1.hpp
* \author SAURAV MOHAPATRA <mohaps@gmail.com>
* \date 2012-22
* \brief TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based
* on the implementation in boost::uuid::details.
*
* In this file are defined:
* - sha1::SHA1
*/
#ifndef _TINY_SHA1_HPP_
#define _TINY_SHA1_HPP_
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdint.h>
/**
* \namespace sha1
* \brief Here is defined the SHA1 class
*/
namespace sha1
{
/**
* \class SHA1
* \brief A tiny SHA1 algorithm implementation used internally in the
* Crow server (specifically in crow/websocket.h).
*/
class SHA1
{
public:

View File

@ -1,3 +1,20 @@
/**
* \file crow/app.h
* \brief This file includes the definition of the crow::Crow class,
* the crow::App and crow::SimpleApp aliases, and some macros.
*
* In this file are defined:
* - crow::Crow
* - crow::App
* - crow::SimpleApp
* - \ref CROW_ROUTE
* - \ref CROW_BP_ROUTE
* - \ref CROW_WEBSOCKET_ROUTE
* - \ref CROW_MIDDLEWARES
* - \ref CROW_CATCHALL_ROUTE
* - \ref CROW_BP_CATCHALL_ROUTE
*/
#pragma once
#include <chrono>
@ -22,52 +39,185 @@
#include "crow/websocket.h"
#ifdef CROW_ENABLE_COMPRESSION
#include "crow/compression.h"
#endif
#endif // #ifdef CROW_ENABLE_COMPRESSION
#ifdef CROW_MSVC_WORKAROUND
#define CROW_ROUTE(app, url) app.route_dynamic(url)
#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_dynamic(url)
#else
#define CROW_ROUTE(app, url) app.route_dynamic(url) // See the documentation in the comment below.
#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_dynamic(url) // See the documentation in the comment below.
#else // #ifdef CROW_MSVC_WORKAROUND
/**
* \def CROW_ROUTE(app, url)
* \brief Creates a route for app using a rule.
*
* It use crow::Crow::route_dynamic or crow::Crow::route to define
* a rule for your application. It's usage is like this:
*
* ```cpp
* auto app = crow::SimpleApp(); // or crow::App()
* CROW_ROUTE(app, "/")
* ([](){
* return "<h1>Hello, world!</h1>";
* });
* ```
*
* This is the recommended way to define routes in a crow application.
* \see [Page of guide "Routes"](https://crowcpp.org/master/guides/routes/).
*/
#define CROW_ROUTE(app, url) app.template route<crow::black_magic::get_parameter_tag(url)>(url)
/**
* \def CROW_BP_ROUTE(blueprint, url)
* \brief Creates a route for a blueprint using a rule.
*
* It may use crow::Blueprint::new_rule_dynamic or
* crow::Blueprint::new_rule_tagged to define a new rule for
* an given blueprint. It's usage is similar
* to CROW_ROUTE macro:
*
* ```cpp
* crow::Blueprint my_bp();
* CROW_BP_ROUTE(my_bp, "/")
* ([](){
* return "<h1>Hello, world!</h1>";
* });
* ```
*
* This is the recommended way to define routes in a crow blueprint
* because of its compile-time capabilities.
*
* \see [Page of the guide "Blueprints"](https://crowcpp.org/master/guides/blueprints/).
*/
#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged<crow::black_magic::get_parameter_tag(url)>(url)
/**
* \def CROW_WEBSOCKET_ROUTE(app, url)
* \brief Defines WebSocket route for app.
*
* It binds a WebSocket route to app. Easy solution to implement
* WebSockets in your app. The usage syntax of this macro is
* like this:
*
* ```cpp
* auto app = crow::SimpleApp(); // or crow::App()
* CROW_WEBSOCKET_ROUTE(app, "/ws")
* .onopen([&](crow::websocket::connection& conn){
* do_something();
* })
* .onclose([&](crow::websocket::connection& conn, const std::string& reason){
* do_something();
* })
* .onmessage([&](crow::websocket::connection&, const std::string& data, bool is_binary){
* if (is_binary)
* do_something(data);
* else
* do_something_else(data);
* });
* ```
*
* \see [Page of the guide "WebSockets"](https://crowcpp.org/master/guides/websockets/).
*/
#define CROW_WEBSOCKET_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url).websocket<std::remove_reference<decltype(app)>::type>(&app)
/**
* \def CROW_MIDDLEWARES(app, ...)
* \brief Enable a Middleware for an specific route in app
* or blueprint.
*
* It defines the usage of a Middleware in one route. And it
* can be used in both crow::SimpleApp (and crow::App) instances and
* crow::Blueprint. Its usage syntax is like this:
*
* ```cpp
* auto app = crow::SimpleApp(); // or crow::App()
* CROW_ROUTE(app, "/with_middleware")
* .CROW_MIDDLEWARES(app, LocalMiddleware) // Can be used more than one
* ([]() { // middleware.
* return "Hello world!";
* });
* ```
*
* \see [Page of the guide "Middlewares"](https://crowcpp.org/master/guides/middleware/).
*/
#define CROW_MIDDLEWARES(app, ...) template middlewares<typename std::remove_reference<decltype(app)>::type, __VA_ARGS__>()
#endif
#endif // #ifdef CROW_MSVC_WORKAROUND
/**
* \def CROW_CATCHALL_ROUTE(app)
* \brief Defines a custom catchall route for app using a
* custom rule.
*
* It defines a handler when the client make a request for an
* undefined route. Instead of just reply with a `404` status
* code (default behavior), you can define a custom handler
* using this macro.
*
* \see [Page of the guide "Routes" (Catchall routes)](https://crowcpp.org/master/guides/routes/#catchall-routes).
*/
#define CROW_CATCHALL_ROUTE(app) app.catchall_route()
/**
* \def CROW_BP_CATCHALL_ROUTE(blueprint)
* \brief Defines a custom catchall route for blueprint
* using a custom rule.
*
* It defines a handler when the client make a request for an
* undefined route in the blueprint.
*
* \see [Page of the guide "Blueprint" (Define a custom Catchall route)](https://crowcpp.org/master/guides/blueprints/#define-a-custom-catchall-route).
*/
#define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule()
/**
* \namespace crow
* \brief The main namespace of the library. In this namespace
* is defined the most important classes and functions of the
* library.
*
* Within this namespace, the Crow class, Router class, Connection
* class, and other are defined.
*/
namespace crow
{
#ifdef CROW_ENABLE_SSL
using ssl_context_t = asio::ssl::context;
#endif
/// The main server application
///
/// Use `SimpleApp` or `App<Middleware1, Middleware2, etc...>`
/**
* \class Crow
* \brief The main server application class.
*
* Use crow::SimpleApp or crow::App<Middleware1, Middleware2, etc...> instead of
* directly instantiate this class.
*/
template<typename... Middlewares>
class Crow
{
public:
/// This crow application
/// \brief This is the crow application
using self_t = Crow;
/// The HTTP server
/// \brief The HTTP server
using server_t = Server<Crow, SocketAdaptor, Middlewares...>;
#ifdef CROW_ENABLE_SSL
/// An HTTP server that runs on SSL with an SSLAdaptor
/// \brief An HTTP server that runs on SSL with an SSLAdaptor
using ssl_server_t = Server<Crow, SSLAdaptor, Middlewares...>;
#endif
Crow()
{}
/// Construct Crow with a subset of middleware
/// \brief Construct Crow with a subset of middleware
template<typename... Ts>
Crow(Ts&&... ts):
middlewares_(make_middleware_tuple(std::forward<Ts>(ts)...))
{}
/// Process an Upgrade request
/// \brief Process an Upgrade request
///
/// Currently used to upgrade an HTTP connection to a WebSocket connection
template<typename Adaptor>
@ -76,19 +226,19 @@ namespace crow
router_.handle_upgrade(req, res, adaptor);
}
/// Process only the method and URL of a request and provide a route (or an error response)
/// \brief Process only the method and URL of a request and provide a route (or an error response)
std::unique_ptr<routing_handle_result> handle_initial(request& req, response& res)
{
return router_.handle_initial(req, res);
}
/// Process the fully parsed request and generate a response for it
/// \brief Process the fully parsed request and generate a response for it
void handle(request& req, response& res, std::unique_ptr<routing_handle_result>& found)
{
router_.handle<self_t>(req, res, *found);
}
/// Process a fully parsed request from start to finish (primarily used for debugging)
/// \brief Process a fully parsed request from start to finish (primarily used for debugging)
void handle_full(request& req, response& res)
{
auto found = handle_initial(req, res);
@ -96,42 +246,42 @@ namespace crow
handle(req, res, found);
}
/// Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
DynamicRule& route_dynamic(std::string&& rule)
/// \brief Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
DynamicRule& route_dynamic(const std::string& rule)
{
return router_.new_rule_dynamic(std::move(rule));
return router_.new_rule_dynamic(rule);
}
///Create a route using a rule (**Use CROW_ROUTE instead**)
/// \brief Create a route using a rule (**Use CROW_ROUTE instead**)
template<uint64_t Tag>
#ifdef CROW_GCC83_WORKAROUND
auto& route(std::string&& rule)
auto& route(const std::string& rule)
#else
auto route(std::string&& rule)
auto route(const std::string& rule)
#endif
#if defined CROW_CAN_USE_CPP17 && !defined CROW_GCC83_WORKAROUND
-> typename std::invoke_result<decltype(&Router::new_rule_tagged<Tag>), Router, std::string&&>::type
-> typename std::invoke_result<decltype(&Router::new_rule_tagged<Tag>), Router, const std::string&>::type
#elif !defined CROW_GCC83_WORKAROUND
-> typename std::result_of<decltype (&Router::new_rule_tagged<Tag>)(Router, std::string&&)>::type
-> typename std::result_of<decltype (&Router::new_rule_tagged<Tag>)(Router, const std::string&)>::type
#endif
{
return router_.new_rule_tagged<Tag>(std::move(rule));
return router_.new_rule_tagged<Tag>(rule);
}
/// Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
/// \brief Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
CatchallRule& catchall_route()
{
return router_.catchall_rule();
}
/// Set the default max payload size for websockets
/// \brief 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
/// \brief Get the default max payload size for websockets
uint64_t websocket_max_payload()
{
return max_payload_;
@ -154,53 +304,53 @@ namespace crow
return signals_;
}
/// Set the port that Crow will handle requests on
/// \brief Set the port that Crow will handle requests on
self_t& port(std::uint16_t port)
{
port_ = port;
return *this;
}
/// Get the port that Crow will handle requests on
/// \brief Get the port that Crow will handle requests on
std::uint16_t port()
{
return port_;
}
/// Set the connection timeout in seconds (default is 5)
/// \brief Set the connection timeout in seconds (default is 5)
self_t& timeout(std::uint8_t timeout)
{
timeout_ = timeout;
return *this;
}
/// Set the server name
/// \brief Set the server name
self_t& server_name(std::string server_name)
{
server_name_ = server_name;
return *this;
}
/// The IP address that Crow will handle requests on (default is 0.0.0.0)
/// \brief The IP address that Crow will handle requests on (default is 0.0.0.0)
self_t& bindaddr(std::string bindaddr)
{
bindaddr_ = bindaddr;
return *this;
}
/// Get the address that Crow will handle requests on
/// \brief Get the address that Crow will handle requests on
std::string bindaddr()
{
return bindaddr_;
}
/// Run the server on multiple threads using all available threads
/// \brief Run the server on multiple threads using all available threads
self_t& multithreaded()
{
return concurrency(std::thread::hardware_concurrency());
}
/// Run the server on multiple threads using a specific number
/// \brief Run the server on multiple threads using a specific number
self_t& concurrency(std::uint16_t concurrency)
{
if (concurrency < 2) // Crow can have a minimum of 2 threads running
@ -208,30 +358,28 @@ namespace crow
concurrency_ = concurrency;
return *this;
}
/// Get the number of threads that server is using
/// \brief Get the number of threads that server is using
std::uint16_t concurrency()
{
return concurrency_;
}
/// Set the server's log level
/// \brief Set the server's log level
///
/// Possible values are:<br>
/// crow::LogLevel::Debug (0)<br>
/// crow::LogLevel::Info (1)<br>
/// crow::LogLevel::Warning (2)<br>
/// crow::LogLevel::Error (3)<br>
/// crow::LogLevel::Critical (4)<br>
/// Possible values are:
/// - crow::LogLevel::Debug (0)
/// - crow::LogLevel::Info (1)
/// - crow::LogLevel::Warning (2)
/// - crow::LogLevel::Error (3)
/// - crow::LogLevel::Critical (4)
self_t& loglevel(LogLevel level)
{
crow::logger::setLogLevel(level);
return *this;
}
/// Set the response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB)
/// \brief Set the response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB)
///
/// Any streamed response is unaffected by Crow's timer, and therefore won't timeout before a response is fully sent.
self_t& stream_threshold(size_t threshold)
@ -240,19 +388,37 @@ namespace crow
return *this;
}
/// Get the response body size (in bytes) beyond which Crow automatically streams responses
/// \brief Get the response body size (in bytes) beyond which Crow automatically streams responses
size_t& stream_threshold()
{
return res_stream_threshold_;
}
self_t& register_blueprint(Blueprint& blueprint)
{
router_.register_blueprint(blueprint);
return *this;
}
/// Set a custom duration and function to run on every tick
/// \brief Set the function to call to handle uncaught exceptions generated in routes (Default generates error 500).
///
/// The function must have the following signature: void(crow::response&).
/// It must set the response passed in argument to the function, which will be sent back to the client.
/// See Router::default_exception_handler() for the default implementation.
template<typename Func>
self_t& exception_handler(Func&& f)
{
router_.exception_handler() = std::forward<Func>(f);
return *this;
}
std::function<void(crow::response&)>& exception_handler()
{
return router_.exception_handler();
}
/// \brief Set a custom duration and function to run on every tick
template<typename Duration, typename Func>
self_t& tick(Duration d, Func f)
{
@ -262,6 +428,7 @@ namespace crow
}
#ifdef CROW_ENABLE_COMPRESSION
self_t& use_compression(compression::algorithm algorithm)
{
comp_algorithm_ = algorithm;
@ -279,62 +446,57 @@ namespace crow
return compression_used_;
}
#endif
/// A wrapper for `validate()` in the router
///
/// Go through the rules, upgrade them if possible, and add them to the list of rules
void validate()
/// \brief Apply blueprints
void add_blueprint()
{
if (!validated_)
#if defined(__APPLE__) || defined(__MACH__)
if (router_.blueprints().empty()) return;
#endif
for (Blueprint* bp : router_.blueprints())
{
if (bp->static_dir().empty()) continue;
#ifndef CROW_DISABLE_STATIC_DIR
auto static_dir_ = crow::utility::normalize_path(bp->static_dir());
// stat on windows doesn't care whether '/' or '\' is being used. on Linux however, using '\' doesn't work. therefore every instance of '\' gets replaced with '/' then a check is done to make sure the directory ends with '/'.
std::string static_dir_(CROW_STATIC_DIRECTORY);
std::replace(static_dir_.begin(), static_dir_.end(), '\\', '/');
if (static_dir_[static_dir_.length() - 1] != '/')
static_dir_ += '/';
route<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) {
bp->new_rule_tagged<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) {
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(static_dir_ + file_path_partial);
res.end();
});
#if defined(__APPLE__) || defined(__MACH__)
if (!router_.blueprints().empty())
#endif
{
for (Blueprint* bp : router_.blueprints())
{
if (!bp->static_dir().empty())
{
// stat on windows doesn't care whether '/' or '\' is being used. on Linux however, using '\' doesn't work. therefore every instance of '\' gets replaced with '/' then a check is done to make sure the directory ends with '/'.
std::string static_dir_(bp->static_dir());
std::replace(static_dir_.begin(), static_dir_.end(), '\\', '/');
if (static_dir_[static_dir_.length() - 1] != '/')
static_dir_ += '/';
bp->new_rule_tagged<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) {
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(static_dir_ + file_path_partial);
res.end();
});
}
}
}
#endif
router_.validate();
validated_ = true;
}
router_.validate_bp();
}
/// Run the server
/// \brief Go through the rules, upgrade them if possible, and add them to the list of rules
void add_static_dir()
{
if (are_static_routes_added()) return;
auto static_dir_ = crow::utility::normalize_path(CROW_STATIC_DIRECTORY);
route<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([static_dir_](crow::response& res, std::string file_path_partial) {
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(static_dir_ + file_path_partial);
res.end();
});
set_static_routes_added();
}
/// \brief A wrapper for `validate()` in the router
void validate()
{
router_.validate();
}
/// \brief Run the server
void run()
{
#ifndef CROW_DISABLE_STATIC_DIR
add_blueprint();
add_static_dir();
#endif
validate();
#ifdef CROW_ENABLE_SSL
@ -364,7 +526,7 @@ namespace crow
}
}
/// Non-blocking version of \ref run()
/// \brief Non-blocking version of \ref run()
///
/// The output from this method needs to be saved into a variable!
/// Otherwise the call will be made on the same thread.
@ -375,7 +537,7 @@ namespace crow
});
}
/// Stop the server
/// \brief Stop the server
void stop()
{
#ifdef CROW_ENABLE_SSL
@ -407,7 +569,7 @@ namespace crow
websockets_.erase(std::remove(websockets_.begin(), websockets_.end(), conn), websockets_.end());
}
/// Print the routing paths defined for each HTTP method
/// \brief Print the routing paths defined for each HTTP method
void debug_print()
{
CROW_LOG_DEBUG << "Routing:";
@ -417,7 +579,7 @@ namespace crow
#ifdef CROW_ENABLE_SSL
/// Use certificate and key files for SSL
/// \brief Use certificate and key files for SSL
self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename)
{
ssl_used_ = true;
@ -430,7 +592,7 @@ namespace crow
return *this;
}
/// Use .pem file for SSL
/// \brief Use `.pem` file for SSL
self_t& ssl_file(const std::string& pem_filename)
{
ssl_used_ = true;
@ -442,7 +604,7 @@ namespace crow
return *this;
}
/// Use certificate chain and key files for SSL
/// \brief Use certificate chain and key files for SSL
self_t& ssl_chainfile(const std::string& crt_filename, const std::string& key_filename)
{
ssl_used_ = true;
@ -467,6 +629,7 @@ namespace crow
return ssl_used_;
}
#else
template<typename T, typename... Remain>
self_t& ssl_file(T&&, Remain&&...)
{
@ -523,7 +686,7 @@ namespace crow
return utility::get_element_by_type<T, Middlewares...>(middlewares_);
}
/// Wait until the server has properly started
/// \brief Wait until the server has properly started
void wait_for_server_start()
{
{
@ -549,7 +712,7 @@ namespace crow
black_magic::tuple_extract<Middlewares, decltype(fwd)>(fwd))...);
}
/// Notify anything using `wait_for_server_start()` to proceed
/// \brief Notify anything using \ref wait_for_server_start() to proceed
void notify_server_start()
{
std::unique_lock<std::mutex> lock(start_mutex_);
@ -557,17 +720,24 @@ namespace crow
cv_started_.notify_all();
}
void set_static_routes_added() {
static_routes_added_ = true;
}
bool are_static_routes_added() {
return static_routes_added_;
}
private:
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";
size_t res_stream_threshold_ = 1048576;
Router router_;
bool static_routes_added_{false};
#ifdef CROW_ENABLE_COMPRESSION
compression::algorithm comp_algorithm_;
@ -594,7 +764,13 @@ namespace crow
std::mutex start_mutex_;
std::vector<crow::websocket::connection*> websockets_;
};
/// \brief Alias of Crow<Middlewares...>. Useful if you want
/// a instance of an Crow application that require Middlewares
template<typename... Middlewares>
using App = Crow<Middlewares...>;
/// \brief Alias of Crow<>. Useful if you want a instance of
/// an Crow application that doesn't require of Middlewares
using SimpleApp = Crow<>;
} // namespace crow

View File

@ -190,6 +190,7 @@ namespace crow
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
CONFLICT = 409,
GONE = 410,

View File

@ -5,7 +5,7 @@
#include <zlib.h>
// http://zlib.net/manual.html
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
namespace compression
{

View File

@ -1,32 +1,43 @@
#pragma once
#ifdef CROW_USE_BOOST
#include <boost/asio.hpp>
#else
#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#endif
#include <asio.hpp>
#endif
#include <algorithm>
#include <atomic>
#include <chrono>
#include <vector>
#include <memory>
#include <vector>
#include "crow/http_parser_merged.h"
#include "crow/common.h"
#include "crow/parser.h"
#include "crow/compression.h"
#include "crow/http_response.h"
#include "crow/logging.h"
#include "crow/settings.h"
#include "crow/task_timer.h"
#include "crow/middleware_context.h"
#include "crow/middleware.h"
#include "crow/middleware_context.h"
#include "crow/parser.h"
#include "crow/settings.h"
#include "crow/socket_adaptors.h"
#include "crow/compression.h"
#include "crow/task_timer.h"
#include "crow/utility.h"
namespace crow
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using error_code = boost::system::error_code;
#else
using error_code = asio::error_code;
#endif
using tcp = asio::ip::tcp;
#ifdef CROW_ENABLE_DEBUG
static std::atomic<int> connectionCount;
#endif
@ -81,7 +92,7 @@ namespace crow
void start()
{
auto self = this->shared_from_this();
adaptor_.start([self](const asio::error_code& ec) {
adaptor_.start([self](const error_code& ec) {
if (!ec)
{
self->start_deadline();
@ -103,6 +114,7 @@ namespace crow
if (!routing_handle_result_->rule_index)
{
parser_.done();
need_to_call_after_handlers_ = true;
complete_request();
}
}
@ -127,6 +139,12 @@ namespace crow
bool is_invalid_request = false;
add_keep_alive_ = false;
// Create context
ctx_ = detail::context<Middlewares...>();
req_.middleware_context = static_cast<void*>(&ctx_);
req_.middleware_container = static_cast<void*>(middlewares_);
req_.io_service = &adaptor_.get_io_service();
req_.remote_ip_address = adaptor_.remote_endpoint().address().to_string();
add_keep_alive_ = req_.keep_alive;
@ -149,6 +167,9 @@ namespace crow
}
else
{
detail::middleware_call_helper<detail::middleware_call_criteria_only_global,
0, decltype(ctx_), decltype(*middlewares_)>({}, *middlewares_, req_, res, ctx_);
close_connection_ = true;
handler_->handle_upgrade(req_, res, std::move(adaptor_));
return;
@ -167,12 +188,7 @@ namespace crow
res.is_alive_helper_ = [self]() -> bool {
return self->adaptor_.is_open();
};
ctx_ = detail::context<Middlewares...>();
req_.middleware_context = static_cast<void*>(&ctx_);
req_.middleware_container = static_cast<void*>(middlewares_);
req_.io_service = &adaptor_.get_io_service();
detail::middleware_call_helper<detail::middleware_call_criteria_only_global,
0, decltype(ctx_), decltype(*middlewares_)>({}, *middlewares_, req_, res, ctx_);
@ -307,6 +323,7 @@ namespace crow
{status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
{status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
{status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
{status::NOT_ACCEPTABLE, "HTTP/1.1 406 Not Acceptable\r\n"},
{status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
{status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
{status::GONE, "HTTP/1.1 410 Gone\r\n"},
@ -468,7 +485,7 @@ namespace crow
auto self = this->shared_from_this();
adaptor_.socket().async_read_some(
asio::buffer(buffer_),
[self](const asio::error_code& ec, std::size_t bytes_transferred) {
[self](const error_code& ec, std::size_t bytes_transferred) {
bool error_while_reading = true;
if (!ec)
{
@ -511,7 +528,7 @@ namespace crow
auto self = this->shared_from_this();
asio::async_write(
adaptor_.socket(), buffers_,
[self](const asio::error_code& ec, std::size_t /*bytes_transferred*/) {
[self](const error_code& ec, std::size_t /*bytes_transferred*/) {
self->res.clear();
self->res_body_copy_.clear();
if (!self->continue_requested)
@ -542,7 +559,7 @@ namespace crow
inline void do_write_sync(std::vector<asio::const_buffer>& buffers)
{
asio::write(adaptor_.socket(), buffers, [&](asio::error_code ec, std::size_t) {
asio::write(adaptor_.socket(), buffers, [&](error_code ec, std::size_t) {
if (!ec)
{
return false;

View File

@ -1273,7 +1273,14 @@ reexecute:
switch (parser->header_state) {
case h_upgrade:
parser->flags |= F_UPGRADE;
// Crow does not support HTTP/2 at the moment.
// According to the RFC https://datatracker.ietf.org/doc/html/rfc7540#section-3.2
// "A server that does not support HTTP/2 can respond to the request as though the Upgrade header field were absent"
// => `F_UPGRADE` is not set if the header starts by "h2".
// This prevents the parser from skipping the request body.
if (ch != 'h' || p+1 == (data + len) || *(p+1) != '2') {
parser->flags |= F_UPGRADE;
}
parser->header_state = h_general;
break;

View File

@ -1,16 +1,24 @@
#pragma once
#ifdef CROW_USE_BOOST
#include <boost/asio.hpp>
#else
#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#endif
#include <asio.hpp>
#endif
#include "crow/common.h"
#include "crow/ci_map.h"
#include "crow/query_string.h"
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
#endif
/// Find and return the value associated with the key. (returns an empty string if nothing is found)
template<typename T>
inline const std::string& get_header_value(const T& headers, const std::string& key)

View File

@ -248,6 +248,8 @@ namespace crow
if (complete_request_handler_)
{
complete_request_handler_();
manual_length_header = false;
skip_body = false;
}
}
}

View File

@ -1,6 +1,11 @@
#pragma once
#include <chrono>
#ifdef CROW_USE_BOOST
#include <boost/asio.hpp>
#ifdef CROW_ENABLE_SSL
#include <boost/asio/ssl.hpp>
#endif
#else
#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#endif
@ -8,19 +13,29 @@
#ifdef CROW_ENABLE_SSL
#include <asio/ssl.hpp>
#endif
#include <cstdint>
#endif
#include <atomic>
#include <chrono>
#include <cstdint>
#include <future>
#include <vector>
#include <memory>
#include <vector>
#include "crow/version.h"
#include "crow/http_connection.h"
#include "crow/logging.h"
#include "crow/task_timer.h"
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using error_code = boost::system::error_code;
#else
using error_code = asio::error_code;
#endif
using tcp = asio::ip::tcp;
template<typename Handler, typename Adaptor = SocketAdaptor, typename... Middlewares>
@ -52,7 +67,7 @@ namespace crow
{
tick_function_();
tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count()));
tick_timer_.async_wait([this](const asio::error_code& ec) {
tick_timer_.async_wait([this](const error_code& ec) {
if (ec)
return;
on_tick();
@ -128,7 +143,7 @@ namespace crow
{
tick_timer_.expires_after(std::chrono::milliseconds(tick_interval_.count()));
tick_timer_.async_wait(
[this](const asio::error_code& ec) {
[this](const error_code& ec) {
if (ec)
return;
on_tick();
@ -143,7 +158,7 @@ namespace crow
CROW_LOG_INFO << "Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs.";
signals_.async_wait(
[&](const asio::error_code& /*error*/, int /*signal_number*/) {
[&](const error_code& /*error*/, int /*signal_number*/) {
stop();
});
@ -227,7 +242,7 @@ namespace crow
acceptor_.async_accept(
p->socket(),
[this, p, &is, service_idx](asio::error_code ec) {
[this, p, &is, service_idx](error_code ec) {
if (!ec)
{
is.post(

View File

@ -14,6 +14,7 @@
#include <memory>
#include <vector>
#include <cmath>
#include <cfloat>
#include "crow/utility.h"
#include "crow/settings.h"
@ -24,7 +25,7 @@ using std::isinf;
using std::isnan;
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
namespace mustache
{
@ -105,7 +106,8 @@ namespace crow
Signed_integer,
Unsigned_integer,
Floating_point,
Null
Null,
Double_precision_floating_point
};
class rvalue;
@ -781,6 +783,7 @@ namespace crow
switch (r.nt())
{
case num_type::Floating_point: os << r.d(); break;
case num_type::Double_precision_floating_point: os << r.d(); break;
case num_type::Signed_integer: os << r.i(); break;
case num_type::Unsigned_integer: os << r.u(); break;
case num_type::Null: throw std::runtime_error("Number with num_type Null");
@ -1318,7 +1321,9 @@ namespace crow
ui(value) {}
constexpr number(std::int64_t value) noexcept:
si(value) {}
constexpr number(double value) noexcept:
explicit constexpr number(double value) noexcept:
d(value) {}
explicit constexpr number(float value) noexcept:
d(value) {}
} num; ///< Value if type is a number.
std::string s; ///< Value if type is a string.
@ -1357,7 +1362,7 @@ namespace crow
wvalue(float value):
returnable("application/json"), t_(type::Number), nt(num_type::Floating_point), num(static_cast<double>(value)) {}
wvalue(double value):
returnable("application/json"), t_(type::Number), nt(num_type::Floating_point), num(static_cast<double>(value)) {}
returnable("application/json"), t_(type::Number), nt(num_type::Double_precision_floating_point), num(static_cast<double>(value)) {}
wvalue(char const* value):
returnable("application/json"), t_(type::String), s(value) {}
@ -1408,7 +1413,7 @@ namespace crow
return;
case type::Number:
nt = r.nt();
if (nt == num_type::Floating_point)
if (nt == num_type::Floating_point || nt == num_type::Double_precision_floating_point)
num.d = r.d();
else if (nt == num_type::Signed_integer)
num.si = r.i();
@ -1444,7 +1449,7 @@ namespace crow
return;
case type::Number:
nt = r.nt;
if (nt == num_type::Floating_point)
if (nt == num_type::Floating_point || nt == num_type::Double_precision_floating_point)
num.d = r.num.d;
else if (nt == num_type::Signed_integer)
num.si = r.num.si;
@ -1514,7 +1519,7 @@ namespace crow
return *this;
}
wvalue& operator=(double value)
wvalue& operator=(float value)
{
reset();
t_ = type::Number;
@ -1523,6 +1528,15 @@ namespace crow
return *this;
}
wvalue& operator=(double value)
{
reset();
t_ = type::Number;
num.d = value;
nt = num_type::Double_precision_floating_point;
return *this;
}
wvalue& operator=(unsigned short value)
{
reset();
@ -1656,7 +1670,7 @@ namespace crow
}
else
{
#if defined(__APPLE__) || defined(__MACH__) || defined(__FreeBSD__) || defined(__ANDROID__)
#if defined(__APPLE__) || defined(__MACH__) || defined(__FreeBSD__) || defined(__ANDROID__) || defined(_LIBCPP_VERSION)
o = std::unique_ptr<object>(new object(initializer_list));
#else
(*o) = initializer_list;
@ -1675,7 +1689,7 @@ namespace crow
}
else
{
#if defined(__APPLE__) || defined(__MACH__) || defined(__FreeBSD__) || defined(__ANDROID__)
#if defined(__APPLE__) || defined(__MACH__) || defined(__FreeBSD__) || defined(__ANDROID__) || defined(_LIBCPP_VERSION)
o = std::unique_ptr<object>(new object(value));
#else
(*o) = value;
@ -1826,7 +1840,14 @@ namespace crow
out.push_back('"');
}
inline void dump_internal(const wvalue& v, std::string& out) const
inline void dump_indentation_part(std::string& out, const int indent, const char separator, const int indent_level) const
{
out.push_back('\n');
out.append(indent_level * indent, separator);
}
inline void dump_internal(const wvalue& v, std::string& out, const int indent, const char separator, const int indent_level = 0) const
{
switch (v.t_)
{
@ -1835,7 +1856,7 @@ namespace crow
case type::True: out += "true"; break;
case type::Number:
{
if (v.nt == num_type::Floating_point)
if (v.nt == num_type::Floating_point || v.nt == num_type::Double_precision_floating_point)
{
if (isnan(v.num.d) || isinf(v.num.d))
{
@ -1850,11 +1871,22 @@ namespace crow
zero
} f_state;
char outbuf[128];
if (v.nt == num_type::Double_precision_floating_point)
{
#ifdef _MSC_VER
sprintf_s(outbuf, sizeof(outbuf), "%f", v.num.d);
sprintf_s(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d);
#else
snprintf(outbuf, sizeof(outbuf), "%f", v.num.d);
snprintf(outbuf, sizeof(outbuf), "%.*g", DECIMAL_DIG, v.num.d);
#endif
}
else
{
#ifdef _MSC_VER
sprintf_s(outbuf, sizeof(outbuf), "%f", v.num.d);
#else
snprintf(outbuf, sizeof(outbuf), "%f", v.num.d);
#endif
}
char *p = &outbuf[0], *o = nullptr; // o is the position of the first trailing 0
f_state = start;
while (*p != '\0')
@ -1909,6 +1941,12 @@ namespace crow
case type::List:
{
out.push_back('[');
if (indent >= 0)
{
dump_indentation_part(out, indent, separator, indent_level + 1);
}
if (v.l)
{
bool first = true;
@ -1917,17 +1955,34 @@ namespace crow
if (!first)
{
out.push_back(',');
if (indent >= 0)
{
dump_indentation_part(out, indent, separator, indent_level + 1);
}
}
first = false;
dump_internal(x, out);
dump_internal(x, out, indent, separator, indent_level + 1);
}
}
if (indent >= 0)
{
dump_indentation_part(out, indent, separator, indent_level);
}
out.push_back(']');
}
break;
case type::Object:
{
out.push_back('{');
if (indent >= 0)
{
dump_indentation_part(out, indent, separator, indent_level + 1);
}
if (v.o)
{
bool first = true;
@ -1936,13 +1991,29 @@ namespace crow
if (!first)
{
out.push_back(',');
if (indent >= 0)
{
dump_indentation_part(out, indent, separator, indent_level + 1);
}
}
first = false;
dump_string(kv.first, out);
out.push_back(':');
dump_internal(kv.second, out);
if (indent >= 0)
{
out.push_back(' ');
}
dump_internal(kv.second, out, indent, separator, indent_level + 1);
}
}
if (indent >= 0)
{
dump_indentation_part(out, indent, separator, indent_level);
}
out.push_back('}');
}
break;
@ -1954,13 +2025,20 @@ namespace crow
}
public:
std::string dump() const
std::string dump(const int indent, const char separator = ' ') const
{
std::string ret;
ret.reserve(estimate_length());
dump_internal(*this, ret);
dump_internal(*this, ret, indent, separator);
return ret;
}
std::string dump() const
{
static constexpr int DontIndent = -1;
return dump(DontIndent);
}
};
// Used for accessing the internals of a wvalue
@ -1968,14 +2046,16 @@ namespace crow
{
int64_t get(int64_t fallback)
{
if (ref.t() != type::Number || ref.nt == num_type::Floating_point)
if (ref.t() != type::Number || ref.nt == num_type::Floating_point ||
ref.nt == num_type::Double_precision_floating_point)
return fallback;
return ref.num.si;
}
double get(double fallback)
{
if (ref.t() != type::Number || ref.nt != num_type::Floating_point)
if (ref.t() != type::Number || ref.nt != num_type::Floating_point ||
ref.nt == num_type::Double_precision_floating_point)
return fallback;
return ref.num.d;
}

View File

@ -9,7 +9,7 @@
#include <iostream>
#include <utility>
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
/// Local middleware should extend ILocalMiddleware

View File

@ -105,7 +105,7 @@ namespace crow
{
case type::Number:
{
if (rv.nt() == num_type::Floating_point)
if (rv.nt() == num_type::Floating_point || rv.nt() == num_type::Double_precision_floating_point)
return multi_value{rv.d()};
else if (rv.nt() == num_type::Unsigned_integer)
return multi_value{int64_t(rv.u())};

View File

@ -18,7 +18,7 @@
#include "crow/mustache.h"
#include "crow/middleware.h"
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
constexpr const uint16_t INVALID_BP_ID{((uint16_t)-1)};
@ -96,6 +96,15 @@ namespace crow
{}
virtual void validate() = 0;
void set_added() {
added_ = true;
}
bool is_added() {
return added_;
}
std::unique_ptr<BaseRule> upgrade()
{
if (rule_to_upgrade_)
@ -141,6 +150,7 @@ namespace crow
std::string rule_;
std::string name_;
bool added_{false};
std::unique_ptr<BaseRule> rule_to_upgrade_;
@ -1154,11 +1164,18 @@ namespace crow
return static_dir_;
}
DynamicRule& new_rule_dynamic(std::string&& rule)
void set_added() {
added_ = true;
}
bool is_added() {
return added_;
}
DynamicRule& new_rule_dynamic(const std::string& rule)
{
std::string new_rule = std::move(rule);
new_rule = '/' + prefix_ + new_rule;
auto ruleObject = new DynamicRule(new_rule);
std::string new_rule = '/' + prefix_ + rule;
auto ruleObject = new DynamicRule(std::move(new_rule));
ruleObject->custom_templates_base = templates_dir_;
all_rules_.emplace_back(ruleObject);
@ -1166,13 +1183,12 @@ namespace crow
}
template<uint64_t N>
typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(std::string&& rule)
typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(const std::string& rule)
{
std::string new_rule = std::move(rule);
new_rule = '/' + prefix_ + new_rule;
std::string new_rule = '/' + prefix_ + rule;
using RuleT = typename black_magic::arguments<N>::type::template rebind<TaggedRule>;
auto ruleObject = new RuleT(new_rule);
auto ruleObject = new RuleT(std::move(new_rule));
ruleObject->custom_templates_base = templates_dir_;
all_rules_.emplace_back(ruleObject);
@ -1228,6 +1244,7 @@ namespace crow
CatchallRule catchall_rule_;
std::vector<Blueprint*> blueprints_;
detail::middleware_indices mw_indices_;
bool added_{false};
friend class Router;
};
@ -1263,6 +1280,11 @@ namespace crow
return catchall_rule_;
}
void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject)
{
internal_add_rule_object(rule, ruleObject, INVALID_BP_ID, blueprints_);
}
void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject, const uint16_t& BP_index, std::vector<Blueprint*>& blueprints)
{
bool has_trailing_slash = false;
@ -1287,6 +1309,8 @@ namespace crow
per_methods_[method].trie.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index);
}
});
ruleObject->set_added();
}
void register_blueprint(Blueprint& blueprint)
@ -1321,11 +1345,20 @@ namespace crow
}
}
void validate_bp() {
//Take all the routes from the registered blueprints and add them to `all_rules_` to be processed.
detail::middleware_indices blueprint_mw;
validate_bp(blueprints_, blueprint_mw);
}
void validate_bp(std::vector<Blueprint*> blueprints, detail::middleware_indices& current_mw)
{
for (unsigned i = 0; i < blueprints.size(); i++)
{
Blueprint* blueprint = blueprints[i];
if (blueprint->is_added()) continue;
if (blueprint->static_dir_ == "" && blueprint->all_rules_.empty())
{
std::vector<HTTPMethod> methods;
@ -1340,7 +1373,7 @@ namespace crow
current_mw.merge_back(blueprint->mw_indices_);
for (auto& rule : blueprint->all_rules_)
{
if (rule)
if (rule && !rule->is_added())
{
auto upgraded = rule->upgrade();
if (upgraded)
@ -1352,24 +1385,21 @@ namespace crow
}
validate_bp(blueprint->blueprints_, current_mw);
current_mw.pop_back(blueprint->mw_indices_);
blueprint->set_added();
}
}
void validate()
{
//Take all the routes from the registered blueprints and add them to `all_rules_` to be processed.
detail::middleware_indices blueprint_mw;
validate_bp(blueprints_, blueprint_mw);
for (auto& rule : all_rules_)
{
if (rule)
if (rule && !rule->is_added())
{
auto upgraded = rule->upgrade();
if (upgraded)
rule = std::move(upgraded);
rule->validate();
internal_add_rule_object(rule->rule(), rule.get(), INVALID_BP_ID, blueprints_);
internal_add_rule_object(rule->rule(), rule.get());
}
}
for (auto& per_method : per_methods_)
@ -1431,22 +1461,13 @@ namespace crow
CROW_LOG_DEBUG << "Matched rule (upgrade) '" << rules[rule_index]->rule_ << "' " << static_cast<uint32_t>(req.method) << " / " << rules[rule_index]->get_methods();
// any uncaught exceptions become 500s
try
{
rules[rule_index]->handle_upgrade(req, res, std::move(adaptor));
}
catch (std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
res = response(500);
res.end();
return;
}
catch (...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
res = response(500);
exception_handler_(res);
res.end();
return;
}
@ -1504,7 +1525,14 @@ namespace crow
std::vector<uint16_t> bpi = found.blueprint_indices;
if (bps_found[i]->catchall_rule().has_handler())
{
bps_found[i]->catchall_rule().handler_(req, res);
try
{
bps_found[i]->catchall_rule().handler_(req, res);
}
catch (...)
{
exception_handler_(res);
}
#ifdef CROW_ENABLE_DEBUG
return std::string("Redirected to Blueprint \"" + bps_found[i]->prefix() + "\" Catchall rule");
#else
@ -1514,7 +1542,14 @@ namespace crow
}
if (catchall_rule_.has_handler())
{
catchall_rule_.handler_(req, res);
try
{
catchall_rule_.handler_(req, res);
}
catch (...)
{
exception_handler_(res);
}
#ifdef CROW_ENABLE_DEBUG
return std::string("Redirected to global Catchall rule");
#else
@ -1674,23 +1709,14 @@ namespace crow
CROW_LOG_DEBUG << "Matched rule '" << rules[rule_index]->rule_ << "' " << static_cast<uint32_t>(req.method) << " / " << rules[rule_index]->get_methods();
// any uncaught exceptions become 500s
try
{
auto& rule = rules[rule_index];
handle_rule<App>(rule, req, res, found.r_params);
}
catch (std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
res = response(500);
res.end();
return;
}
catch (...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
res = response(500);
exception_handler_(res);
res.end();
return;
}
@ -1757,6 +1783,30 @@ namespace crow
return blueprints_;
}
std::function<void(crow::response&)>& exception_handler()
{
return exception_handler_;
}
static void default_exception_handler(response& res)
{
// any uncaught exceptions become 500s
res = response(500);
try
{
throw;
}
catch (const std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
}
catch (...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
}
}
private:
CatchallRule catchall_rule_;
@ -1772,5 +1822,6 @@ namespace crow
std::array<PerMethod, static_cast<int>(HTTPMethod::InternalMethodCount)> per_methods_;
std::vector<std::unique_ptr<BaseRule>> all_rules_;
std::vector<Blueprint*> blueprints_;
std::function<void(crow::response&)> exception_handler_ = &default_exception_handler;
};
} // namespace crow

View File

@ -1,20 +1,37 @@
#pragma once
#ifdef CROW_USE_BOOST
#include <boost/asio.hpp>
#include <boost/asio/version.hpp>
#ifdef CROW_ENABLE_SSL
#include <boost/asio/ssl.hpp>
#endif
#else
#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#endif
#include <asio.hpp>
#include <asio/version.hpp>
#ifdef CROW_ENABLE_SSL
#include <asio/ssl.hpp>
#endif
#endif
#include "crow/settings.h"
#include <asio/version.hpp>
#if ASIO_VERSION >= 101300 // 1.13.0
#if (CROW_USE_BOOST && BOOST_VERSION >= 107000) || (ASIO_VERSION >= 101300)
#define GET_IO_SERVICE(s) ((asio::io_context&)(s).get_executor().context())
#else
#define GET_IO_SERVICE(s) ((s).get_io_service())
#endif
namespace crow
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using error_code = boost::system::error_code;
#else
using error_code = asio::error_code;
#endif
using tcp = asio::ip::tcp;
/// A wrapper for the asio::ip::tcp::socket and asio::ssl::stream
@ -54,32 +71,32 @@ namespace crow
void close()
{
asio::error_code ec;
error_code ec;
socket_.close(ec);
}
void shutdown_readwrite()
{
asio::error_code ec;
error_code ec;
socket_.shutdown(asio::socket_base::shutdown_type::shutdown_both, ec);
}
void shutdown_write()
{
asio::error_code ec;
error_code ec;
socket_.shutdown(asio::socket_base::shutdown_type::shutdown_send, ec);
}
void shutdown_read()
{
asio::error_code ec;
error_code ec;
socket_.shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec);
}
template<typename F>
void start(F f)
{
f(asio::error_code());
f(error_code());
}
tcp::socket socket_;
@ -119,7 +136,7 @@ namespace crow
{
if (is_open())
{
asio::error_code ec;
error_code ec;
raw_socket().close(ec);
}
}
@ -128,7 +145,7 @@ namespace crow
{
if (is_open())
{
asio::error_code ec;
error_code ec;
raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_both, ec);
}
}
@ -137,7 +154,7 @@ namespace crow
{
if (is_open())
{
asio::error_code ec;
error_code ec;
raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_send, ec);
}
}
@ -146,7 +163,7 @@ namespace crow
{
if (is_open())
{
asio::error_code ec;
error_code ec;
raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec);
}
}
@ -160,7 +177,7 @@ namespace crow
void start(F f)
{
ssl_socket_->async_handshake(asio::ssl::stream_base::server,
[f](const asio::error_code& ec) {
[f](const error_code& ec) {
f(ec);
});
}

View File

@ -1,10 +1,15 @@
#pragma once
#ifdef CROW_USE_BOOST
#include <boost/asio.hpp>
#include <boost/asio/basic_waitable_timer.hpp>
#else
#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#endif
#include <asio.hpp>
#include <asio/basic_waitable_timer.hpp>
#endif
#include <chrono>
#include <functional>
@ -15,6 +20,12 @@
namespace crow
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using error_code = boost::system::error_code;
#else
using error_code = asio::error_code;
#endif
namespace detail
{
@ -112,7 +123,7 @@ namespace crow
if (tasks_.empty()) highest_id_ = 0;
}
void tick_handler(const asio::error_code& ec)
void tick_handler(const error_code& ec)
{
if (ec) return;

View File

@ -11,6 +11,7 @@
#include <sstream>
#include <unordered_map>
#include <random>
#include <algorithm>
#include "crow/settings.h"
@ -702,6 +703,14 @@ namespace crow
return base64decode(data.data(), data.length());
}
inline static std::string normalize_path(const std::string& directoryPath)
{
std::string normalizedPath = directoryPath;
std::replace(normalizedPath.begin(), normalizedPath.end(), '\\', '/');
if (!normalizedPath.empty() && normalizedPath.back() != '/')
normalizedPath += '/';
return normalizedPath;
}
inline static void sanitize_filename(std::string& data, char replacement = '_')
{
@ -715,8 +724,8 @@ namespace crow
// a special device. Thus we search for the string (case-insensitive), and then check if the string ends or if
// is has a dangerous follow up character (.:\/)
auto sanitizeSpecialFile = [](std::string& source, unsigned ofs, const char* pattern, bool includeNumber, char replacement) {
unsigned i = ofs;
size_t len = source.length();
unsigned i = ofs;
size_t len = source.length();
const char* p = pattern;
while (*p)
{

View File

@ -6,8 +6,22 @@
#include "crow/TinySHA1.hpp"
#include "crow/utility.h"
namespace crow
namespace crow // NOTE: Already documented in "crow/app.h"
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using error_code = boost::system::error_code;
#else
using error_code = asio::error_code;
#endif
/**
* \namespace crow::websocket
* \brief Namespace that includes the \ref Connection class
* and \ref connection struct. Useful for WebSockets connection.
*
* Used specially in crow/websocket.h, crow/app.h and crow/routing.h
*/
namespace websocket
{
enum class WebSocketReadState
@ -299,7 +313,7 @@ namespace crow
//asio::async_read(adaptor_.socket(), asio::buffer(&mini_header_, 1),
adaptor_.socket().async_read_some(
asio::buffer(&mini_header_, 2),
[this](const asio::error_code& ec, std::size_t
[this](const error_code& ec, std::size_t
#ifdef CROW_ENABLE_DEBUG
bytes_transferred
#endif
@ -367,7 +381,7 @@ namespace crow
remaining_length16_ = 0;
asio::async_read(
adaptor_.socket(), asio::buffer(&remaining_length16_, 2),
[this](const asio::error_code& ec, std::size_t
[this](const error_code& ec, std::size_t
#ifdef CROW_ENABLE_DEBUG
bytes_transferred
#endif
@ -403,7 +417,7 @@ namespace crow
{
asio::async_read(
adaptor_.socket(), asio::buffer(&remaining_length_, 8),
[this](const asio::error_code& ec, std::size_t
[this](const error_code& ec, std::size_t
#ifdef CROW_ENABLE_DEBUG
bytes_transferred
#endif
@ -447,7 +461,7 @@ namespace crow
{
asio::async_read(
adaptor_.socket(), asio::buffer((char*)&mask_, 4),
[this](const asio::error_code& ec, std::size_t
[this](const error_code& ec, std::size_t
#ifdef CROW_ENABLE_DEBUG
bytes_transferred
#endif
@ -489,7 +503,7 @@ namespace crow
to_read = remaining_length_;
adaptor_.socket().async_read_some(
asio::buffer(buffer_, static_cast<std::size_t>(to_read)),
[this](const asio::error_code& ec, std::size_t bytes_transferred) {
[this](const error_code& ec, std::size_t bytes_transferred) {
is_reading = false;
if (!ec)
@ -641,7 +655,7 @@ namespace crow
auto watch = std::weak_ptr<void>{anchor_};
asio::async_write(
adaptor_.socket(), buffers,
[&, watch](const asio::error_code& ec, std::size_t /*bytes_transferred*/) {
[&, watch](const error_code& ec, std::size_t /*bytes_transferred*/) {
if (!ec && !close_connection_)
{
sending_buffers_.clear();

View File

@ -86,7 +86,8 @@ for x in edges:
assert order.index(x) < order.index(y), 'cyclic include detected'
print(order)
build = [lsc, '#pragma once']
spdx_lsc = '// SPDX-License-Identifier: BSD-3-Clause AND ISC AND MIT'
build = [spdx_lsc, lsc, '#pragma once']
for header in order:
d = open(pt.join(header_path, header), encoding='UTF-8').read()
d_no_depend = re_depends.sub(lambda x: '', d)

View File

@ -19,6 +19,13 @@
using namespace std;
using namespace crow;
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using asio_error_code = boost::system::error_code;
#else
using asio_error_code = asio::error_code;
#endif
#define LOCALHOST_ADDRESS "127.0.0.1"
TEST_CASE("Rule")
@ -532,6 +539,28 @@ TEST_CASE("http_method")
}
} // http_method
TEST_CASE("validate can be called multiple times")
{
SimpleApp app;
CROW_ROUTE(app, "/")([]() { return "1"; });
app.validate();
app.validate();
CROW_ROUTE(app, "/test")([]() { return "1"; });
app.validate();
try
{
CROW_ROUTE(app, "/")([]() { return "1"; });
app.validate();
FAIL_CHECK();
}
catch (std::exception& e)
{
CROW_LOG_DEBUG << e.what();
}
} // validate can be called multiple times
TEST_CASE("server_handling_error_request")
{
static char buf[2048];
@ -921,6 +950,72 @@ TEST_CASE("json_write")
CHECK(R"({"scores":[1,2,3]})" == y.dump());
} // json_write
TEST_CASE("json_write_with_indent")
{
static constexpr int IndentationLevelOne = 1;
static constexpr int IndentationLevelTwo = 2;
static constexpr int IndentationLevelFour = 4;
json::wvalue y;
y["scores"][0] = 1;
y["scores"][1] = "king";
y["scores"][2][0] = "real";
y["scores"][2][1] = false;
y["scores"][2][2] = true;
CHECK(R"({
"scores": [
1,
"king",
[
"real",
false,
true
]
]
})" == y.dump(IndentationLevelOne));
CHECK(R"({
"scores": [
1,
"king",
[
"real",
false,
true
]
]
})" == y.dump(IndentationLevelTwo));
CHECK(R"({
"scores": [
1,
"king",
[
"real",
false,
true
]
]
})" == y.dump(IndentationLevelFour));
static constexpr char TabSeparator = '\t';
CHECK(R"({
"scores": [
1,
"king",
[
"real",
false,
true
]
]
})" == y.dump(IndentationLevelOne, TabSeparator));
} // json_write_with_indent
TEST_CASE("json_copy_r_to_w_to_w_to_r")
{
json::rvalue r = json::load(
@ -1038,11 +1133,13 @@ TEST_CASE("json::wvalue::wvalue(float)")
TEST_CASE("json::wvalue::wvalue(double)")
{
double d = 4.2;
double d = 0.036303908355795146;
json::wvalue value = d;
CHECK(value.t() == json::type::Number);
CHECK(value.dump() == "4.2");
auto dumped_value = value.dump();
CROW_LOG_DEBUG << dumped_value;
CHECK(std::abs(utility::lexical_cast<double>(dumped_value) - d) < numeric_limits<double>::epsilon());
} // json::wvalue::wvalue(double)
TEST_CASE("json::wvalue::wvalue(char const*)")
@ -1867,7 +1964,8 @@ TEST_CASE("middleware_cors")
return "-";
});
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(45451).run_async();
const auto port = 33333;
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(port).run_async();
app.wait_for_server_start();
asio::io_service is;
@ -1875,7 +1973,20 @@ TEST_CASE("middleware_cors")
{
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
asio::ip::address::from_string(LOCALHOST_ADDRESS), port));
c.send(asio::buffer("OPTIONS / HTTP/1.1\r\n\r\n"));
c.receive(asio::buffer(buf, 2048));
c.close();
CHECK(std::string(buf).find("Access-Control-Allow-Origin: *") != std::string::npos);
}
{
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), port));
c.send(asio::buffer("GET /\r\n\r\n"));
@ -1888,7 +1999,7 @@ TEST_CASE("middleware_cors")
{
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
asio::ip::address::from_string(LOCALHOST_ADDRESS), port));
c.send(asio::buffer("GET /origin\r\n\r\n"));
@ -1901,7 +2012,7 @@ TEST_CASE("middleware_cors")
{
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
asio::ip::address::from_string(LOCALHOST_ADDRESS), port));
c.send(asio::buffer("GET /nocors/path\r\n\r\n"));
@ -2821,7 +2932,7 @@ TEST_CASE("websocket_max_payload")
}
}
asio::error_code ec;
asio_error_code ec;
c.lowest_layer().shutdown(asio::socket_base::shutdown_type::shutdown_both, ec);
app.stop();
@ -3139,6 +3250,8 @@ TEST_CASE("blueprint")
bp.register_blueprint(sub_bp);
sub_bp.register_blueprint(sub_sub_bp);
app.add_blueprint();
app.add_static_dir();
app.validate();
{
@ -3221,6 +3334,79 @@ TEST_CASE("blueprint")
}
} // blueprint
TEST_CASE("exception_handler")
{
SimpleApp app;
CROW_ROUTE(app, "/get_error")
([&]() -> std::string {
throw std::runtime_error("some error occurred");
});
CROW_ROUTE(app, "/get_no_error")
([&]() {
return "Hello world";
});
app.validate();
{
request req;
response res;
req.url = "/get_error";
app.handle_full(req, res);
CHECK(500 == res.code);
CHECK(res.body.empty());
}
{
request req;
response res;
req.url = "/get_no_error";
app.handle_full(req, res);
CHECK(200 == res.code);
CHECK(res.body.find("Hello world") != std::string::npos);
}
app.exception_handler([](crow::response& res) {
try
{
throw;
}
catch (const std::exception& e)
{
res = response(501, e.what());
}
});
{
request req;
response res;
req.url = "/get_error";
app.handle_full(req, res);
CHECK(501 == res.code);
CHECK(res.body.find("some error occurred") != std::string::npos);
}
{
request req;
response res;
req.url = "/get_no_error";
app.handle_full(req, res);
CHECK(200 == res.code);
CHECK(res.body.find("some error occurred") == std::string::npos);
CHECK(res.body.find("Hello world") != std::string::npos);
}
} // exception_handler
TEST_CASE("base64")
{
unsigned char sample_bin[] = {0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e};
@ -3258,6 +3444,11 @@ TEST_CASE("base64")
CHECK(crow::utility::base64decode(sample_bin2_enc_np, 6) == sample_bin2_str);
} // base64
TEST_CASE("normalize_path") {
CHECK(crow::utility::normalize_path("/abc/def") == "/abc/def/");
CHECK(crow::utility::normalize_path("path\\to\\directory") == "path/to/directory/");
} // normalize_path
TEST_CASE("sanitize_filename")
{
auto sanitize_filename = [](string s) {
@ -3326,7 +3517,7 @@ TEST_CASE("timeout")
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
auto receive_future = async(launch::async, [&]() {
asio::error_code ec;
asio_error_code ec;
c.receive(asio::buffer(buf, 2048), 0, ec);
return ec;
});
@ -3346,7 +3537,7 @@ TEST_CASE("timeout")
size_t received;
auto receive_future = async(launch::async, [&]() {
asio::error_code ec;
asio_error_code ec;
received = c.receive(asio::buffer(buf, 2048), 0, ec);
return ec;
});
@ -3445,3 +3636,47 @@ TEST_CASE("lexical_cast")
CHECK(utility::lexical_cast<string>(4) == "4");
CHECK(utility::lexical_cast<float>("10", 2) == 10.0f);
}
TEST_CASE("http2_upgrade_is_ignored")
{
// Crow does not support HTTP/2 so upgrade headers must be ignored
// relevant RFC: https://datatracker.ietf.org/doc/html/rfc7540#section-3.2
static char buf[5012];
SimpleApp app;
CROW_ROUTE(app, "/echo").methods("POST"_method)
([](crow::request const& req) {
return req.body;
});
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(45451).run_async();
app.wait_for_server_start();
asio::io_service is;
auto make_request = [&](const std::string& rq) {
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
c.send(asio::buffer(rq));
c.receive(asio::buffer(buf, 2048));
c.close();
return std::string(buf);
};
std::string request =
"POST /echo HTTP/1.1\r\n"
"user-agent: unittest.cpp\r\n"
"host: " LOCALHOST_ADDRESS ":45451\r\n"
"content-length: 48\r\n"
"connection: upgrade\r\n"
"upgrade: h2c\r\n"
"\r\n"
"http2 upgrade is not supported so body is parsed\r\n"
"\r\n";
auto res = make_request(request);
CHECK(res.find("http2 upgrade is not supported so body is parsed") != std::string::npos);
app.stop();
}