Merge branch 'master' into app-constructor

This commit is contained in:
Vladislav 2022-05-23 20:12:55 +03:00 committed by GitHub
commit 94fa9f77f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2063 additions and 543 deletions

View File

@ -2129,7 +2129,10 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED =
PREDEFINED = CROW_ENABLE_SSL \
CROW_ENABLE_COMPRESSION \
CROW_ENABLE_DEBUG \
CROW_ENABLE_LOGGING
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The

View File

@ -12,7 +12,7 @@
## Description
Crow is a C++ microframework for running web services. It uses routing similar to Python's Flask which makes it easy to use. It is also extremely fast, beating multiple existing C++ frameworks as well as non C++ frameworks.
Crow is a C++ framework for creating HTTP or Websocket web services. It uses routing similar to Python's Flask which makes it easy to use. It is also extremely fast, beating multiple existing C++ frameworks as well as non C++ frameworks.
### Features
- Easy Routing (similar to flask).
@ -27,6 +27,7 @@ Crow is a C++ microframework for running web services. It uses routing similar t
- Uses modern C++ (11/14)
### Still in development
- [Async support](https://github.com/CrowCpp/Crow/issues/258)
- [HTTP/2 support](https://github.com/crowcpp/crow/issues/8)
## Documentation

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="150mm"
height="150mm"
viewBox="0 0 150 150"
version="1.1"
id="svg2098"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="fast_light_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview2100"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.73673542"
inkscape:cx="-200.88623"
inkscape:cy="257.89448"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
width="150mm" />
<defs
id="defs2095" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-31.274158,-78.140537)">
<circle
style="fill:none;fill-opacity:1;stroke:#e5f2f8;stroke-width:6.46499;stroke-linecap:round;stroke-opacity:1"
id="path7742"
cx="106.62331"
cy="153.48967"
r="70" />
<circle
style="fill:#515b60;fill-opacity:1;stroke:none;stroke-width:6.46499;stroke-linecap:round;stroke-opacity:1"
id="path7840"
cx="106.80286"
cy="151.15532"
r="10" />
<path
id="rect7980"
style="fill:#515b60;fill-opacity:1;stroke-width:6.46499;stroke-linecap:round"
d="m 156.9346,153.30987 -50.40574,1.47566 -0.0786,-6.1047 z"
sodipodi:nodetypes="cccc"
inkscape:transform-center-x="-24.979325"
inkscape:transform-center-y="-0.23024" />
<circle
style="fill:#e5f2f8;fill-opacity:1;stroke:none;stroke-width:4.99999;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path9189"
cx="56.373787"
cy="185.78271"
r="3" />
<circle
style="fill:#e5f2f8;fill-opacity:1;stroke:none;stroke-width:4.99999;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle9271"
cx="46.658237"
cy="154.20793"
r="3" />
<circle
style="fill:#e5f2f8;fill-opacity:1;stroke:none;stroke-width:4.99999;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle9273"
cx="53.132095"
cy="125.30756"
r="3" />
<circle
style="fill:#e5f2f8;fill-opacity:1;stroke:none;stroke-width:4.99999;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle9275"
cx="73.13047"
cy="102.3995"
r="3" />
<circle
style="fill:#e5f2f8;fill-opacity:1;stroke:none;stroke-width:4.99999;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle9277"
cx="106.98246"
cy="92.060951"
r="3" />
<circle
style="fill:#e5f2f8;fill-opacity:1;stroke:none;stroke-width:4.99999;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle9279"
cx="-73.287689"
cy="-160.29289"
r="3"
transform="rotate(150.48856)" />
<path
id="circle12049"
style="fill:none;stroke:#ff4141;stroke-width:5.66447;stroke-linecap:round;stroke-opacity:1"
d="m 163.0663,127.86979 c 3.88122,8.04831 5.15863,16.53521 5.15863,26.06886 0,12.05511 -3.478,23.2979 -9.48531,32.77966"
sodipodi:nodetypes="csc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="130mm"
height="130mm"
viewBox="0 0 130 130"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="header_light_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.73673541"
inkscape:cx="-122.16055"
inkscape:cy="249.75045"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
height="130mm" />
<defs
id="defs2">
<rect
x="286.39862"
y="431.63394"
width="213.10229"
height="276.89725"
id="rect12469" />
<rect
x="308.11603"
y="465.56741"
width="176.45412"
height="267.39587"
id="rect3773" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-55.42222,-82.637121)">
<path
id="path1009"
style="fill:#e5f2f8;fill-opacity:1;stroke-width:8.47412"
d="M 62 0 L 62 244.93359 L 62 489.86719 L 249.05273 489.86719 L 436.10547 489.86719 L 436.10547 302.15625 L 436.10547 114.44531 L 378.94141 57.222656 L 321.7793 0 L 191.88867 0 L 62 0 z M 228.81055 227.10352 L 246.87695 227.10352 L 246.87695 286.67383 C 251.17383 280.09831 256.2194 275.18294 262.01367 271.92773 C 267.87305 268.67253 274.61133 267.04492 282.22852 267.04492 C 294.79362 267.04492 304.29883 270.95117 310.74414 278.76367 C 317.18945 286.51107 320.41211 297.93685 320.41211 313.04102 L 320.41211 379.05664 L 302.44336 379.05664 L 302.44336 313.62695 C 302.44336 303.27539 300.42513 295.52799 296.38867 290.38477 C 292.35221 285.24154 286.29753 282.66992 278.22461 282.66992 C 268.52409 282.66992 260.87435 285.76237 255.27539 291.94727 C 249.67643 298.13216 246.87695 306.56315 246.87695 317.24023 L 246.87695 379.05664 L 228.81055 379.05664 L 228.81055 227.10352 z M 168.45898 354.25195 L 189.06445 354.25195 L 189.06445 379.05664 L 168.45898 379.05664 L 168.45898 354.25195 z "
transform="matrix(0.26458333,0,0,0.26458333,55.42222,82.637121)" />
<text
xml:space="preserve"
transform="scale(0.26458333)"
id="text3771"
style="font-style:normal;font-weight:normal;font-size:200px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect3773);fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="128mm"
height="128mm"
viewBox="0 0 128 128"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="typesafe_light_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.0419012"
inkscape:cx="114.21428"
inkscape:cy="204.43397"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
height="128mm" />
<defs
id="defs2">
<inkscape:path-effect
effect="mirror_symmetry"
start_point="83.49758,103.2497"
end_point="83.677144,216.55502"
center_point="83.587362,159.90236"
id="path-effect1148"
is_visible="true"
lpeversion="1.1"
mode="free"
discard_orig_path="false"
fuse_paths="true"
oposite_fuse="false"
split_items="false"
split_open="false" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-35.610134,-93.0589)">
<path
style="fill:none;fill-opacity:1;stroke:#e5f2f8;stroke-width:6.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 83.489092,97.893689 C 61.657927,122.25262 42.116054,116.87646 39.272073,119.55834 c -2.61289,2.46396 6.066946,69.88504 44.404869,96.86893 38.252208,-27.10527 46.718298,-94.55352 44.097618,-97.00919 -2.85247,-2.67285 -22.37721,2.76522 -44.285468,-21.524391 z"
id="path857"
sodipodi:nodetypes="csc"
inkscape:original-d="m 83.677144,97.68319 c -21.906891,24.60036 -41.552926,19.18557 -44.405071,21.87515 -2.615268,2.4662 6.082752,70.00779 44.5096,96.94249"
inkscape:path-effect="#path-effect1148"
transform="translate(16.150228,0.25394282)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="202mm"
height="202mm"
viewBox="0 0 202 202"
version="1.1"
id="svg13908"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)"
sodipodi:docname="websocket_light_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview13910"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.73673541"
inkscape:cx="160.16605"
inkscape:cy="343.40687"
inkscape:window-width="1920"
inkscape:window-height="1051"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
width="201.77914mm" />
<defs
id="defs13905" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-4.4986958,-69.044219)">
<path
style="fill:#e5f2f8;stroke-width:0.0841212;fill-opacity:1"
d="m 69.265523,230.77706 -14.111288,-14.11148 8.832818,-8.83251 8.832808,-8.83251 L 51.116516,177.2971 29.41317,155.59363 v -12.26062 -12.26062 h 12.534076 12.534076 v 7.08736 7.08737 l 9.169279,9.16899 9.169279,9.16899 13.396176,-13.39618 13.396175,-13.39617 -9.168982,-9.16928 -9.169,-9.16932 H 55.360589 29.44692 L 16.972808,105.92001 4.4986958,93.385901 H 48.103109 91.707532 l 3.746995,3.764422 c 2.060862,2.070436 11.842143,11.897187 21.736193,21.837197 l 17.9892,18.0727 -4.40824,4.40884 -4.40823,4.40882 8.60118,8.60149 8.60117,8.60148 v 17.74937 17.74937 l -17.49722,-17.49708 -17.49722,-17.49705 -4.45581,4.45833 -4.455823,4.45834 17.460153,17.51827 17.46015,17.51826 H 116.84371 99.107375 l -8.622061,-8.62237 -8.622051,-8.62235 -4.43771,4.43711 -4.437711,4.43712 10.452037,10.45228 10.452028,10.45229 h 43.721543 43.72154 l 10.28448,10.23153 c 5.65645,5.62734 11.2686,11.2109 12.47142,12.4079 l 2.18695,2.17636 H 144.82733 83.376811 Z M 156.184,182.57579 v -24.87875 l -10.45194,-10.4522 -10.45193,-10.45218 8.83245,-8.83275 c 4.85784,-4.85802 8.87197,-8.83278 8.92029,-8.83278 0.0483,0 6.41741,6.33451 14.15356,14.07664 l 14.06572,14.07661 v 30.08708 30.08708 H 168.71808 156.184 Z"
id="path13999" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -6,10 +6,11 @@ Here's how you can install Crow on your favorite GNU/Linux distro.
- boost library & development headers (1.64 or later).
- **(optional)** ZLib for HTTP Compression.
- **(optional)** OpenSSL for HTTPS support.
- **(optional)** CMake and Python3 to build tests and/or examples.
!!!note
- **(optional)** CMake for building tests, examples, and/or installing Crow.
- **(optional)** Python3 to build tests and/or examples.
!!! note
Crow's CI uses `g++-9.3` and `clang-7.0` running on AMD64 (x86_64) and ARM64v8 architectures.
Crow's CI uses `g++-9.4` and `clang-10.0` running on AMD64 (x86_64) and ARM64v8 architectures.
<br><br>
@ -42,6 +43,10 @@ You can also download the `crow_all.h` file and simply include that into your pr
You can ignore `-DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF` if you want to build the Examples and Unit Tests.
!!! note
While building you can set the `CROW_FEATURES` variable (as a `;` separated list). You can use an argument such as `-DCROW_FEATURES="ssl;compression"`.
!!! note
You can uninstall Crow at a later time using `make uninstall`.
@ -76,6 +81,10 @@ find_package(Crow)
target_link_libraries(your_project PUBLIC Crow::Crow)
```
From there CMake should handle compiling and linking your project.
!!! note
For optional features like HTTP Compression or HTTPS you can set the `CROW_FEATURES` variable using lines such as `set(CROW_FEATURES "ssl;compression")`, `set(CROW_FEATURES ssl compression)`, or `set(CROW_FEATURES ssl)`.
### Directly using a compiler
All you need to do is run the following command:
```

View File

@ -58,7 +58,7 @@ This will generate a `crow_all.h` file which you can use in the following steps
4. `make -j12`
!!! note
You can add options like `-DCROW_ENABLE_SSL`, `-DCROW_ENABLE_COMPRESSION`, or `-DCROW_AMALGAMATE` to `cmake ..` to build their tests/examples.
You can add options like `-DCROW_FEATURES="ssl;compression"` or `-DCROW_AMALGAMATE` to `cmake ..` to build optional tests/examples for HTTP Compression or HTTPS.
## Compiling using a compiler directly
All you need to do is run the following command:

View File

@ -1,7 +0,0 @@
Crow Allows a developer to set CORS policies by using the `CORSHandler` middleware.<br><br>
This middleware can be added to the app simply by defining a `#!cpp crow::App<crow::CORSHandler>`. This will use the default CORS rules globally<br><br>
The CORS rules can be modified by first getting the middleware via `#!cpp auto& cors = app.get_middleware<crow::CORSHandler>();`. The rules can be set per URL prefix using `prefix()`, per blueprint using `blueprint()`, or globally via `global()`. These will return a `CORSRules` object which contains the actual rules for the prefix, blueprint, or application. For more details go [here](../../reference/structcrow_1_1_c_o_r_s_handler.html).
`CORSRules` can be modified using the methods `origin()`, `methods()`, `headers()`, `max_age()`, `allow_credentials()`, or `ignore()`. For more details on these methods and what default values they take go [here](../../reference/structcrow_1_1_c_o_r_s_rules.html).

View File

@ -3,6 +3,7 @@
Using `#!cpp crow::utility::base64encode(mystring, mystring.size())` will return a Base64 encoded string. For URL safe Base64 `#!cpp crow::utility::base64encode_urlsafe(mystring, mystring.size())` can be used. The key used in the encoding process can be changed, it is a string containing all 64 characters to be used.
## Decoding
**Introduced in: `v1.0`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0)</span>
Using `#!cpp crow::utility::base64decode(mystring, mystring.size())` with `mystring` being a Base64 encoded string will return a plain-text string. The function works with both normal and URL safe Base64. However it cannot decode a Base64 string encoded with a custom key.

View File

@ -1,5 +1,6 @@
**Introduced in: `v1.0`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0)</span>
Crow supports flask style blueprints.<br>
A blueprint is a limited app. It cannot handle networking. But it can handle routes.<br>
Blueprints allow developers to compartmentalize their Crow applications, making them a lot more modular.<br><br>

View File

@ -1,9 +1,11 @@
**Introduced in: `v0.3`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3)</span>
Crow supports Zlib compression using Gzip or Deflate algorithms.
## HTTP Compression
HTTP compression is by default disabled in crow. Do the following to enable it: <br>
- Define `CROW_ENABLE_COMPRESSION` in your compiler definitions (`g++ main.cpp -DCROW_ENABLE_COMPRESSION` for example) or `CMakeLists.txt`.
- Define `CROW_ENABLE_COMPRESSION` in your compiler definitions (`g++ main.cpp -DCROW_ENABLE_COMPRESSION` for example) or `set(CROW_FEATURES compression)` in `CMakeLists.txt`.
- Call `#!cpp use_compression(crow::compression::algorithm)` on your Crow app.
- When compiling your application, make sure that ZLIB is included as a dependency. Either through `-lz` compiler argument or `find_package(ZLIB)` in CMake.

View File

@ -0,0 +1,43 @@
Crow contains some middlewares that are ready to be used in your application.
<br>
Make sure you understand how to enable and use [middleware](../middleware/).
## Cookies
Include: `crow/middlewares/cookie_parser.h` <br>
Examples: `examples/middlewars/example_cookies.cpp`
This middleware allows to read and write cookies by using `CookieParser`. Once enabled, it parses all incoming cookies.
Cookies can be read and written with the middleware context. All cookie attributes can be changed as well.
```cpp
auto& ctx = app.get_context<crow::CookieParser>(request);
std::string value = ctx.get_cookie("key");
ctx.set_cookie("key", "value")
.path("/")
.max_age(120);
```
!!! note
Make sure `CookieParser` is listed before any other middleware that relies on it.
## CORS
Include: `crow/middlewares/cors.h` <br>
Examples: `examples/middlewars/example_cors.cpp`
This middleware allows to set CORS policies by using `CORSHandler`. Once enabled, it will apply the default CORS rules globally.
The CORS rules can be modified by first getting the middleware via `#!cpp auto& cors = app.get_middleware<crow::CORSHandler>();`. The rules can be set per URL prefix using `prefix()`, per blueprint using `blueprint()`, or globally via `global()`. These will return a `CORSRules` object which contains the actual rules for the prefix, blueprint, or application. For more details go [here](../../reference/structcrow_1_1_c_o_r_s_handler.html).
`CORSRules` can be modified using the methods `origin()`, `methods()`, `headers()`, `max_age()`, `allow_credentials()`, or `ignore()`. For more details on these methods and what default values they take go [here](../../reference/structcrow_1_1_c_o_r_s_rules.html).
```cpp
auto& cors = app.get_middleware<crow::CORSHandler>();
cors
.global()
.headers("X-Custom-Header", "Upgrade-Insecure-Requests")
.methods("POST"_method, "GET"_method)
.prefix("/cors")
.origin("example.com");
```

View File

@ -31,7 +31,8 @@ Writing a log is as simple as `#!cpp CROW_LOG_<LOG LEVEL> << "Hello";` (replace&
Log times are reported in GMT timezone by default. This is because HTTP requires all reported times for requests and responses to be in GMT. This can be changed by using the macro `CROW_USE_LOCALTIMEZONE` which will set **only the log timezone** to the server's local timezone.
## Creating A custom logger
**Introduced in: `v1.0`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0)</span>
Assuming you have an existing logger or Crow's default format just doesn't work for you. Crow allows you to use a custom logger for any log made using the `CROW_LOG_<LOG LEVEL>` macro.<br>
All you need is a class extending `#!cpp crow::ILogHandler` containing the method `#!cpp void log(std::string, crow::LogLevel)`.<br>

View File

@ -1,13 +1,51 @@
Middleware is used for altering and inspecting requests before and after the handler call.
Middleware is used for altering and inspecting requests before and after calling the handler. In Crow it's very similar to middleware in other web frameworks.
All middleware is registered in the Crow application
```cpp
crow::App<FirstMW, SecondMW, ThirdMW> app;
```
and is called in this specified order.
Any middleware requires the following 3 members:
* A context struct for storing the middleware data.
* A `before_handle` method, which is called before the handler. If `res.end()` is called, the operation is halted.
* A context struct for storing request local data.
* A `before_handle` method, which is called before the handler.
* A `after_handle` method, which is called after the handler.
## before_handle
There are two possible signatures for before_handle
!!! warning
As soon as `response.end()` is called, no other handlers and middleware is run, except for after_handlers of already visited middleware.
## Example
A middleware that can be used to guard admin handlers
```cpp
struct AdminAreaGuard
{
struct context
{};
void before_handle(crow::request& req, crow::response& res, context& ctx)
{
if (req.remote_ip_address != ADMIN_IP)
{
res.code = 403;
res.end();
}
}
void after_handle(crow::request& req, crow::response& res, context& ctx)
{}
};
```
### before_handle and after_handle
There are two possible signatures for before_handle and after_handle
1. if you only need to access this middleware's context.
@ -25,43 +63,16 @@ There are two possible signatures for before_handle
}
```
## Local middleware
## after_handle
There are two possible signatures for after_handle
1. if you only need to access this middleware's context.
```cpp
void after_handle(request& req, response& res, context& ctx)
```
2. To get access to other middlewares context
``` cpp
template <typename AllContext>
void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{
auto other_ctx = all_ctx.template get<OtherMiddleware>();
}
```
## Using middleware
All middleware has to be registered in the Crow application and is enabled globally by default.
```cpp
crow::App<FirstMiddleware, SecondMiddleware> app;
```
if you want to enable some middleware only for specific handlers, you have to extend it from `crow::ILocalMiddleware`.
By default, every middleware is called for each request. If you want to enable middleware for specific handlers or blueprints, you have to extend it from `crow::ILocalMiddleware`
```cpp
struct LocalMiddleware : crow::ILocalMiddleware
{
...
```
After this, you can enable it for specific handlers.
After this, you can enable it for specific handlers
```cpp
CROW_ROUTE(app, "/with_middleware")
@ -71,26 +82,14 @@ CROW_ROUTE(app, "/with_middleware")
});
```
## Examples
A local middleware that can be used to guard admin handlers
or blueprints
```cpp
struct AdminAreaGuard : crow::ILocalMiddleware
{
struct context
{};
void before_handle(crow::request& req, crow::response& res, context& ctx)
{
if (req.remote_ip_address != ADMIN_IP)
{
res.code = 403;
res.end();
}
}
void after_handle(crow::request& req, crow::response& res, context& ctx)
{}
};
Blueprint bp("with_middleware");
bp.CROW_MIDDLEWARES(app, FistLocalMiddleware, SecondLocalMiddleware);
```
!!! warning
Local and global middleware are called separately. First all global middleware is run, then all enabled local middleware for the current handler is run. In both cases middleware is called strongly
in the order listed in the Crow application.

View File

@ -1,4 +1,5 @@
**Introduced in: `v0.2`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.2](https://github.com/CrowCpp/Crow/releases/0.2)</span>
Multipart is a way of forming HTTP requests or responses to contain multiple distinct parts.<br>
@ -21,7 +22,8 @@ A message can be created either by defining the headers, boundary, and individua
Once a multipart message has been made, the individual parts can be accessed throughout `msg.parts`, `parts` is an `std::vector`.<br><br>
**Introduced in: `v1.0`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0)</span>
Part headers are organized in a similar way to request and response headers, and can be retrieved via `crow::multipart::get_header_object("header-key")`. This function returns a `crow::multipart::header` object.<br><br>

View File

@ -2,10 +2,13 @@ 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)
**Introduced in: `v0.3`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3)</span>
Works the same as `get`, but removes the returned value.
!!! note
@ -16,15 +19,19 @@ A URL can be `http://example.com?key[]=value1&key[]=value2&key[]=value3`. Using
`#!cpp get_list("key", false)` can be used to parse `http://example.com?key=value1&key=value2&key=value3`
## pop_list(name)
**Introduced in: `v0.3`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3)</span>
Works the same as `get_list` but removes all instances of values having the given key (`use_brackets` is also available here).
## get_dict(name)
Returns an `std::unordered_map<std::string, std::string>` from a query string such as `?key[sub_key1]=value1&key[sub_key2]=value2&key[sub_key3]=value3`.<br>
The key in the map is what's in the brackets (`sub_key1` for example), and the value being what's after the `=` sign (`value1`). The name passed to the function is not part of the returned value.
## pop_dict(name)
**Introduced in: `v0.3`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3)</span>
Works the same as `get_dict` but removing the values from the query string.
!!!warning
!!! warning
if your query string contains both a list and dictionary with the same key, it is best to use `pop_list` before either `get_dict` or `pop_dict`, since a map cannot contain more than one value per key, each item in the list will override the previous and only the last will remain with an empty key.

View File

@ -11,7 +11,7 @@ Using `/hello` means the client will need to access `http://example.com/hello` i
A path can have parameters, for example `/hello/<int>` will allow a client to input an int into the url which will be in the handler (something like `http://example.com/hello/42`).<br>
Parameters can be `<int>`, `<uint>`, `<double>`, `<string>`, or `<path>`.<br>
It's worth noting that the parameters also need to be defined in the handler, an example of using parameters would be to add 2 numbers based on input:
```cpp
```cpp
CROW_ROUTE(app, "/add/<int>/<int>")
([](int a, int b)
{
@ -27,6 +27,52 @@ You can change the HTTP methods the route uses from just the default `GET` by us
Crow handles `HEAD` and `OPTIONS` methods automatically. So adding those to your handler has no effect.
Crow defines the following methods:
```
DELETE
GET
HEAD
POST
PUT
CONNECT
OPTIONS
TRACE
PATCH
PURGE
COPY
LOCK
MKCOL
MOVE
PROPFIND
PROPPATCH
SEARCH
UNLOCK
BIND
REBIND
UNBIND
ACL
REPORT
MKACTIVITY
CHECKOUT
MERGE
SEARCH
NOTIFY
SUBSCRIBE
UNSUBSCRIBE
MKCALENDAR
LINK
UNLINK
SOURCE
```
## Handler
Basically a piece of code that gets executed whenever the client calls the associated route, usually in the form of a [lambda expression](https://en.cppreference.com/w/cpp/language/lambda). It can be as simple as `#!cpp ([](){return "Hello World"})`.<br><br>
@ -35,6 +81,12 @@ Handlers can also use information from the request by adding it as a parameter `
You can also access the URL parameters in the handler using `#!cpp req.url_params.get("param_name");`. If the parameter doesn't exist, `nullptr` is returned.<br><br>
!!! note "Note &nbsp;&nbsp;&nbsp;&nbsp; <span class="tag">[:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)</span>"
parameters inside the body can be parsed using `#!cpp req.get_body_params();`. which is useful for requests of type `application/x-www-form-urlencoded`. Its format is similar to `url_params`.
For more information on `crow::request` go [here](../../reference/structcrow_1_1request.html).<br><br>
### Response
@ -45,6 +97,56 @@ Please note that in order to return a response defined as a parameter you'll nee
Alternatively, you can define the response in the body and return it (`#!cpp ([](){return crow::response()})`).<br>
For more information on `crow::response` go [here](../../reference/structcrow_1_1response.html).<br><br>
Crow defines the following status codes:
```
100 Continue
101 Switching Protocols
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content
300 Multiple Choices
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
307 Temporary Redirect
308 Permanent Redirect
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
407 Proxy Authentication Required
409 Conflict
410 Gone
413 Payload Too Large
415 Unsupported Media Type
416 Range Not Satisfiable
417 Expectation Failed
428 Precondition Required
429 Too Many Requests
451 Unavailable For Legal Reasons
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
506 Variant Also Negotiates
```
!!! note
If your status code is not defined in the list above (e.g. `crow::response(123)`) Crow will return `500 Internal Server Error` instead.
### Return statement
A `crow::response` is very strictly tied to a route. If you can have something in a response constructor, you can return it in a handler.<br><br>
@ -52,20 +154,22 @@ The main return type is `std::string`, although you could also return a `crow::j
For more information on the specific constructors for a `crow::response` go [here](../../reference/structcrow_1_1response.html).
## Returning custom classes
**Introduced in: `v0.3`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3)</span>
If you have your own class you want to return (without converting it to string and returning that), you can use the `crow::returnable` class.<br>
to use the returnable class, you only need your class to publicly extend `crow::returnable`, add a `dump()` method that returns your class as an `std::string`, and add a constructor that has a `Content-Type` header as a string argument.<br><br>
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();
@ -75,12 +179,15 @@ class a : public crow::returnable
<br><br>
## Response codes
**Introduced in: `v1.0`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0)</span>
Instead of assigning a response code, you can use the `crow::status` enum, for example you can replace `crow::response(200)` with `crow::response(crow::status::OK)`
## Catchall routes
**Introduced in: `v0.3`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3)</span>
By default, any request that Crow can't find a route for will return a simple 404 response. You can change that to return a default route using the `CROW_CATCHALL_ROUTE(app)` macro. Defining it is identical to a normal route, even when it comes to the `const crow::request&` and `crow::response&` parameters being optional.
!!! note

View File

@ -1,6 +1,6 @@
Crow supports HTTPS though SSL or TLS.<br><br>
!!!note
!!! note
When mentioning SSL in this documentation, it is often a reference to openSSL, which includes TLS.<br><br>
@ -8,7 +8,7 @@ Crow supports HTTPS though SSL or TLS.<br><br>
To enable SSL, first your application needs to define either a `.crt` and `.key` files, or a `.pem` file. Once you have your files, you can add them to your app like this:<br>
`#!cpp app.ssl_file("/path/to/cert.crt", "/path/to/keyfile.key")` or `#!cpp app.ssl_file("/path/to/pem_file.pem")`. Please note that this method can be part of the app method chain, which means it can be followed by `.run()` or any other method.<br><br>
You also need to define `CROW_ENABLE_SSL` in your compiler definitions (`g++ main.cpp -DCROW_ENABLE_SSL` for example) or `CMakeLists.txt`.
You also need to define `CROW_ENABLE_SSL` in your compiler definitions (`g++ main.cpp -DCROW_ENABLE_SSL` for example) or `set(CROW_FEATURES ssl)` in `CMakeLists.txt`.
You can also set your own SSL context (by using `boost::asio::ssl::context ctx`) and then applying it via the `#!cpp app.ssl(ctx)` method.<br><br>

View File

@ -1,4 +1,6 @@
**Introduced in: `v0.2`**<br><br>
<span class="tag">[:octicons-feed-tag-16: v0.2](https://github.com/CrowCpp/Crow/releases/0.2)</span>
A static file is any file that resides in the server's storage.
Crow supports returning Static files as responses in 2 ways.

View File

@ -1,19 +1,19 @@
Websockets are a way of connecting a client and a server without the request response nature of HTTP.<br><br>
## Routes
To create a websocket in Crow, you need a websocket route.<br>
A websocket route differs from a normal route quite a bit. While it uses the same `CROW_ROUTE(app, "/url")` macro, that's about where the similarities end.<br>
A websocket route follows the macro with `.websocket()` which is then followed by a series of methods (with handlers inside) for each event. These are (sorted by order of execution):
!!! Warning
By default, Crow allows Clients to send unmasked websocket messages, which is useful for debugging but goes against the protocol specification. Production Crow applications should enforce the protocol by adding `#!cpp #define CROW_ENFORCE_WS_SPEC` to their source code.
A websocket route differs from a normal route quite a bit. It uses A slightly altered `CROW_WEBSOCKET_ROUTE(app, "/url")` macro, which is then followed by a series of methods (with handlers inside) for each event. These are (sorted by order of execution):
- `#!cpp onaccept([&](const crow::request&){handler code goes here})` (This handler has to return `bool`)
- `#!cpp onopen([&](crow::websocket::connection& conn){handler code goes here})`
- `#!cpp onmessage([&](crow::websocket::connection& conn, const std::string message, bool is_binary){handler code goes here})`
- `#!cpp onerror([&](crow::websocket::connection& conn){handler code goes here})`
- `#!cpp onclose([&](crow::websocket::connection& conn, const std::string reason){handler code goes here})`<br><br>
- `#!cpp onclose([&](crow::websocket::connection& conn, const std::string reason){handler code goes here})`
!!! Warning
By default, Crow allows Clients to send unmasked websocket messages, which is useful for debugging but goes against the protocol specification. Production Crow applications should enforce the protocol by adding `#!cpp #define CROW_ENFORCE_WS_SPEC` to their source code.
These event methods and their handlers can be chained. The full Route should look similar to this:
```cpp
@ -32,5 +32,17 @@ CROW_ROUTE(app, "/ws")
do_something_else(data);
});
```
<br><br>
For more info go [here](../../reference/classcrow_1_1_web_socket_rule.html).
## Maximum payload size
<span class="tag">[:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow)</span>
The maximum payload size that a connection accepts can be adjusted either globally by using `#!cpp app.websocket_max_payload(<value in bytes>)` or per route by using `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload(<value in bytes>)`. In case a message was sent that exceeded the limit. The connection would be shut down and `onerror` would be triggered.
!!! note
By default, This limit is disabled. To disable the global setting in specific routes, you only need to call `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload(UINT64_MAX)`.
For more info about websocket routes go [here](../../reference/classcrow_1_1_web_socket_rule.html).
For more info about websocket connections go [here](../../reference/classcrow_1_1websocket_1_1_connection.html).

View File

@ -6,11 +6,11 @@
<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 property="og:image" content="/assets/og_img.png" />
<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">
<meta property="twitter:image" content="/assets/og_img.png">
<meta property="twitter:image" content="assets/og_img.png">
{% endblock %}
<!-- Content -->
@ -25,11 +25,11 @@
.ccard{
border-style: solid;
border-width: .1rem;
border-color: #00000080;
border-color: var(--home-border-color);
border-radius: 0.5rem;
width: 10rem;
height: 12rem;
box-shadow: 2px 5px 5px #00000040;
box-shadow: 2px 5px 5px var(--home-shadow-color);
margin-inline: 1.5rem;
margin-bottom: 1rem;
display: inline-block;
@ -51,7 +51,7 @@
border-bottom: solid;
border-width: 0.1rem;
border-image-slice: 1;
border-image-source: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgb(0, 0, 0) 50%, rgba(0,0,0,0) 100%);
border-image-source: var(--home-image-border);
}
.ccard__text{
@ -135,28 +135,42 @@ code{
overflow: hidden;
}
.md-footer-copyright {
color: var(--md-footer-fg-color--light);
font-size: .64rem;
margin: auto .6rem;
padding: .4rem 0;
}
</style>
<img class="clogo" alt="logo" src="/assets/crowlogo_main_color.svg">
<img class="clogo" alt="logo" src="assets/crowlogo_main_color.svg#only-light">
<img class="clogo" alt="logo" src="assets/crowlogo_main_light_color.svg#only-dark">
<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">
<div class="ccard">
<img class="ccard__image" src= /assets/fast_icon.svg>
<img class="ccard__image" src= assets/fast_icon.svg#only-light>
<img class="ccard__image" src= assets/fast_light_icon.svg#only-dark>
<p class="ccard__text">Blazingly Fast</p>
</div>
<div class="ccard">
<img class="ccard__image" src= /assets/header_icon.svg>
<img class="ccard__image" src= assets/header_icon.svg#only-light>
<img class="ccard__image" src= assets/header_light_icon.svg#only-dark>
<p class="ccard__text">Header Only</p>
</div>
<div class="ccard">
<img class="ccard__image" src= /assets/typesafe_icon.svg>
<img class="ccard__image" src= assets/typesafe_icon.svg#only-light>
<img class="ccard__image" src= assets/typesafe_light_icon.svg#only-dark>
<p class="ccard__text">Typesafe handlers</p>
</div>
<div class="ccard">
<img class="ccard__image" src= /assets/websocket_icon.svg>
<img class="ccard__image" src= assets/websocket_icon.svg#only-light>
<img class="ccard__image" src= assets/websocket_light_icon.svg#only-dark>
<p class="ccard__text">Websocket Support</p>
</div>
</section>
@ -166,11 +180,10 @@ 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>
<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>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
@ -197,14 +210,14 @@ code{
</code></pre></div>
</div>
<div class="sdescription">
<h2 style="text-align: center;">JSON Support Built-in</h3>
<h2 style="text-align: center;">JSON Support Built-in</h2>
</div>
</section>
<section class="ssection">
<div class="sdescription">
<h2 style="text-align: center;">URL parameter support as well!</h3>
<h2 style="text-align: center;">URL parameter support as well!</h2>
</div>
<div class="scontent">
<div class="highlight"><pre id="__code_2"><span></span><button class="md-clipboard md-icon" title="Copy to clipboard" data-clipboard-target="#__code_2 > code"></button><code><span class="cp">CROW_ROUTE</span><span class="p">(</span><span class="n">app</span><span class="p">,</span><span class="s">"/hello/&lt;int&gt;"</span><span class="p">)</span>
@ -237,7 +250,7 @@ code{
<div class="dcard">
<a href="https://github.com/CrowCpp/Crow/releases/latest">
<img class="dcard__image" src="/assets/pkg_logos/ubuntu.png">
<img class="dcard__image" src="assets/pkg_logos/ubuntu.png">
<p class="dcard__title">.deb file</p>
<p class="dcard__description">for Ubuntu/Debian based systems</p>
</a>
@ -245,7 +258,7 @@ code{
<div class="dcard">
<a href="https://aur.archlinux.org/packages/crow">
<img class="dcard__image" src="/assets/pkg_logos/arch.png">
<img class="dcard__image" src="assets/pkg_logos/arch.png">
<p class="dcard__title">AUR</p>
<p class="dcard__description">for Arch Linux based systems</p>
</a>
@ -253,7 +266,7 @@ code{
<div class="dcard">
<a href="https://vcpkg.io">
<img class="dcard__image" src="/assets/pkg_logos/vcpkg.png">
<img class="dcard__image" src="assets/pkg_logos/vcpkg.png">
<p class="dcard__title">VCPKG</p>
<p class="dcard__description">for Windows systems</p>
</a>
@ -261,7 +274,7 @@ code{
<div class="dcard">
<a href="https://conan.io/center/crowcpp-crow">
<img class="dcard__image" src="/assets/pkg_logos/conan.png">
<img class="dcard__image" src="assets/pkg_logos/conan.png">
<p class="dcard__title">Conan Center</p>
<p class="dcard__description">for developers using the conan package manager</p>
</a>
@ -269,7 +282,7 @@ code{
<div class="dcard">
<a href="https://github.com/CrowCpp/Crow/releases/latest">
<img class="dcard__image" src="/assets/pkg_logos/github.png">
<img class="dcard__image" src="assets/pkg_logos/github.png">
<p class="dcard__title">Header File</p>
<p class="dcard__description">Download Crow directly from github</p>
</a>
@ -282,9 +295,9 @@ code{
<h3 style="text-align:center;">The 1000 mile journey begins with a single step. Get started by installing Crow and building you first application. Or go through the guides if you're stuck somewhere.<h3>
<dev class="sbuttons">
<a href="/getting_started/setup/" title="Get Started" class="md-button crow-button">Get Started</a>
<a href="/guides/app/" title="Guides" class="md-button crow-button">Guides</a>
<a href="/reference/index.html" title="API Reference" class="md-button crow-button">API Reference</a>
<a href="getting_started/setup/" title="Get Started" class="md-button crow-button">Get Started</a>
<a href="guides/app/" title="Guides" class="md-button crow-button">Guides</a>
<a href="reference/index.html" title="API Reference" class="md-button crow-button">API Reference</a>
</dev>
@ -302,7 +315,7 @@ function makeCard(name, img_url, ref, size = 6)
{
img_url = AvatarImage(name);
}
return `<a title="${name}" href="${ref}" style=\"border-style: solid;border-width: .1rem;border-color: #00000080;border-radius: ${size/12}rem;width: ${size}rem;height: ${size}rem;box-shadow: 2px 5px 5px #00000040;margin-inline: ${size/12}rem;margin-bottom: ${size/12}rem;margin-top: ${size/12}rem;display: inline-block;\"><img style=\"width: ${size/2}rem;height: ${size/2}rem; border-radius: ${size/10}rem;margin-left: auto;margin-right: auto;display: block;margin-top: ${size/7.7}rem;\" src=\"${img_url}\"><p style=\"text-align: center;font-size: ${size/7.7}rem;\">${finalName}</p></a>`;
return `<a title="${name}" href="${ref}" style=\"border-style: solid;border-width: .1rem;border-color: var(--home-border-color);border-radius: ${size/12}rem;width: ${size}rem;height: ${size}rem;box-shadow: 2px 5px 5px var(--home-shadow-color);margin-inline: ${size/12}rem;margin-bottom: ${size/12}rem;margin-top: ${size/12}rem;display: inline-block;\"><img style=\"width: ${size/2}rem;height: ${size/2}rem; border-radius: ${size/10}rem;margin-left: auto;margin-right: auto;display: block;margin-top: ${size/7.7}rem;\" src=\"${img_url}\"><p style=\"text-align: center;font-size: ${size/7.7}rem;\">${finalName}</p></a>`;
}
function fixLong(name)
@ -504,7 +517,7 @@ hidden
<div class="md-footer-meta__inner md-grid">
<!-- Copyright and theme information -->
<div class="md-footer-copyright">
<div class="md-footer-copyright" style="flex: 1;display: flex;justify-content: left;">
{% if config.copyright %}
<div class="md-footer-copyright__highlight">
{{ config.copyright }}
@ -513,7 +526,7 @@ hidden
{{ extracopyright }}
</div>
<a style="margin: auto .6rem; font-size: .64rem;" href="/privacy_policy.html">Privacy Policy</a>
<a style="margin: auto .6rem; font-size: .64rem;text-align: center;flex: 1;display: flex;justify-content: center;" href="privacy_policy.html">Privacy Policy</a>
<!-- Social links -->
{% include "partials/social.html" %}

View File

@ -22,6 +22,15 @@
{% import "partials/language.html" as lang with context %}
<style>
.md-footer-copyright {
color: var(--md-footer-fg-color--light);
font-size: .64rem;
margin: auto .6rem;
padding: .4rem 0;
}
</style>
<!-- Footer -->
<footer class="md-footer">

View File

@ -0,0 +1,37 @@
<!--
Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
<!-- Social links -->
<div class="md-social" style="flex: 1;display: flex;justify-content: right;">
{% for social in config.extra.social %}
{% set title = social.name %}
{% if not title and "//" in social.link %}
{% set _, url = social.link.split("//") %}
{% set title = url.split("/")[0] %}
{% endif %}
<a
href="{{ social.link }}"
target="_blank" rel="noopener"
title="{{ title | e }}"
class="md-social__link"
>
{% include ".icons/" ~ social.icon ~ ".svg" %}
</a>
{% endfor %}
</div>

View File

@ -1,23 +1,67 @@
:root {
[data-md-color-scheme="crow-light"]{
--md-primary-fg-color: #24404f;
--md-accent-fg-color: #122027;
--md-typeset-a-color: var(--md-accent-fg-color) !important;
--md-accent-fg-color: #122027;
--md-typeset-a-color: var(--md-accent-fg-color) !important;
--md-default-bg-color: #e5f2f8;
--md-code-bg-color: #cfcfcf !important;
--md-code-hl-comment-color: var(--md-code-fg-color) !important;
--md-code-hl-generic-color: var(--md-code-fg-color) !important;
--md-code-hl-variable-color: var(--md-code-fg-color) !important;
--md-code-fg-color: #fff !important;
--md-code-hl-punctuation-color: #fff !important;
--md-code-fg-color: #1d1d1d !important;
--md-code-hl-punctuation-color: #1d1d1d !important;
--home-border-color: #00000080;
--home-shadow-color: #00000040;
--home-image-border: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgb(0, 0, 0) 50%, rgba(0,0,0,0) 100%);
}
[data-md-color-scheme="crow-dark"]{
--md-primary-fg-color: #24404f;
--md-accent-fg-color: #445e6d;
--md-default-fg-color: rgba(255, 255, 255, 0.87);
--md-default-fg-color--light: rgba(255, 255, 255, 0.54);
--md-default-fg-color--lighter: rgba(255, 255, 255, 0.32);
--md-default-fg-color--lightest: rgba(255, 255, 255, 0.07);
--md-typeset-a-color: var(--md-accent-fg-color) !important;
--md-default-bg-color: #1a2124;
--md-code-bg-color: #2f2f2f !important;
--md-code-hl-comment-color: var(--md-code-fg-color) !important;
--md-code-hl-generic-color: var(--md-code-fg-color) !important;
--md-code-hl-variable-color: var(--md-code-fg-color) !important;
--md-code-fg-color: #adadad !important;
--md-code-hl-punctuation-color: #adadad !important;
--home-border-color: #ffffff20;
--home-shadow-color: #00000040;
--home-image-border: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0) 100%);
}
.md-typeset code {
background-color: #cfcfcf;
color: #1d1d1d;
padding: 0.2rem;
}
.md-typeset .md-button {
color: var(--md-default-fg-color--light);
border-radius: 0.5rem;
}
/*
:root{
--md-default-fg-color: rgba(255, 255, 255, 0.87);
--md-default-fg-color--light: rgba(255, 255, 255, 0.54);
--md-default-fg-color--lighter: rgba(255, 255, 255, 0.32);
--md-default-fg-color--lightest: rgba(255, 255, 255, 0.07);
--md-default-bg-color: #1a2124;
--md-typeset-a-color: var(--md-accent-fg-color) !important;
}
.md-typeset code {
background-color: #2f2f2f;
}
.highlight .c, .highlight .c1, .highlight .ch, .highlight .cm, .highlight .cs, .highlight .sd {
color: #606060 !important;
}
@ -43,4 +87,3 @@
.highlight .n {
color: #1d1d1d;
}

View File

@ -6,3 +6,51 @@
.code {
border-radius: 5px;
}
.tag
{
background-color: var(--md-primary-fg-color);
color: var(--md-default-bg-color);
border-radius: 50px;
padding-left: 0.15em;
padding-right: 0.35em;
padding-top: 0.45em;
padding-bottom: 0.35em;
}
.tag a
{
color: var(--md-default-bg-color);
}
.tag a:hover
{
color: var(--md-default-bg-color);
}
.md-typeset :is(.emojione, .twemoji, .gemoji)
{
vertical-align: text-bottom;
}
.md-typeset :is(.emojione, .twemoji, .gemoji) svg
{
width: 1.25em;
}
[data-md-color-scheme="crow-dark"] img[src$="#only-dark"]
{
display: block;
}
[data-md-color-scheme="crow-light"] img[src$="#only-light"]
{
display: block;
}
[data-md-color-scheme="crow-light"] img[src$="#only-dark"]
{
display: none;
}
[data-md-color-scheme="crow-dark"] img[src$="#only-light"]
{
display: none;
}

5
docs/versions.json Normal file
View File

@ -0,0 +1,5 @@
[
{"version": "master", "title": "master", "aliases": []},
{"version": "1.0", "title": "1.0+3", "aliases": []},
{"version": "0.3", "title": "0.3+4", "aliases": []}
]

View File

@ -87,6 +87,10 @@ add_executable(example_cors middlewares/example_cors.cpp)
add_warnings_optimizations(example_cors)
target_link_libraries(example_cors PUBLIC Crow::Crow)
add_executable(example_cookies middlewares/example_cookies.cpp)
add_warnings_optimizations(example_cookies)
target_link_libraries(example_cookies PUBLIC Crow::Crow)
if(MSVC)
add_executable(example_vs example_vs.cpp)
add_warnings_optimizations(example_vs)

View File

@ -5,11 +5,13 @@ struct RequestLogger
struct context
{};
// This method is run before handling the request
void before_handle(crow::request& req, crow::response& /*res*/, context& /*ctx*/)
{
CROW_LOG_INFO << "Request to:" + req.url;
}
// This method is run after handling the request
void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
};
@ -23,6 +25,7 @@ struct SecretContentGuard : crow::ILocalMiddleware
void before_handle(crow::request& /*req*/, crow::response& res, context& /*ctx*/)
{
// A request can be aborted prematurely
res.write("SECRET!");
res.code = 403;
res.end();
@ -32,10 +35,28 @@ struct SecretContentGuard : crow::ILocalMiddleware
{}
};
struct RequestAppend : crow::ILocalMiddleware
{
// Values from this context can be accessed from handlers
struct context
{
std::string message;
};
void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
void after_handle(crow::request& /*req*/, crow::response& res, context& ctx)
{
// The response can be modified
res.write(" + (" + ctx.message + ")");
}
};
int main()
{
// ALL middleware (including per handler) is listed
crow::App<RequestLogger, SecretContentGuard> app;
crow::App<RequestLogger, SecretContentGuard, RequestAppend> app;
CROW_ROUTE(app, "/")
([]() {
@ -48,7 +69,20 @@ int main()
return "";
});
app.port(18080).run();
crow::Blueprint bp("bp", "c", "c");
// Register middleware on all routes on a specific blueprint
// This also applies to sub blueprints
bp.CROW_MIDDLEWARES(app, RequestAppend);
CROW_BP_ROUTE(bp, "/")
([&](const crow::request& req) {
// Get RequestAppends context
auto& ctx = app.get_context<RequestAppend>(req);
ctx.message = "World";
return "Hello:";
});
app.register_blueprint(bp);
app.port(18080).run();
return 0;
}

View File

@ -1,4 +1,4 @@
//#define CROW_STATIC_DRIECTORY "alternative_directory/"
//#define CROW_STATIC_DIRECTORY "alternative_directory/"
//#define CROW_STATIC_ENDPOINT "/alternative_endpoint/<path>"
//#define CROW_DISABLE_STATIC_DIR
#include "crow.h"

View File

@ -0,0 +1,32 @@
#include "crow.h"
#include "crow/middlewares/cookie_parser.h"
int main()
{
// Include CookieParser middleware
crow::App<crow::CookieParser> app;
CROW_ROUTE(app, "/read")
([&](const crow::request& req) {
auto& ctx = app.get_context<crow::CookieParser>(req);
// Read cookies with get_cookie
auto value = ctx.get_cookie("key");
return "value: " + value;
});
CROW_ROUTE(app, "/write")
([&](const crow::request& req) {
auto& ctx = app.get_context<crow::CookieParser>(req);
// Store cookies with set_cookie
ctx.set_cookie("key", "word")
// configure additional parameters
.path("/")
.max_age(120)
.httponly();
return "ok!";
});
app.port(18080).run();
return 0;
}

View File

@ -10,8 +10,7 @@ int main()
std::mutex mtx;
std::unordered_set<crow::websocket::connection*> users;
CROW_ROUTE(app, "/ws")
.websocket()
CROW_WEBSOCKET_ROUTE(app, "/ws")
.onopen([&](crow::websocket::connection& conn) {
CROW_LOG_INFO << "new websocket connection from " << conn.get_remote_ip();
std::lock_guard<std::mutex> _(mtx);

View File

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

View File

@ -19,6 +19,7 @@
#include "crow/http_request.h"
#include "crow/http_server.h"
#include "crow/task_timer.h"
#include "crow/websocket.h"
#ifdef CROW_ENABLE_COMPRESSION
#include "crow/compression.h"
#endif
@ -29,7 +30,8 @@
#else
#define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)
#define CROW_BP_ROUTE(blueprint, url) blueprint.new_rule_tagged<crow::black_magic::get_parameter_tag(url)>(url)
#define CROW_MIDDLEWARES(app, ...) middlewares<decltype(app), __VA_ARGS__>()
#define CROW_WEBSOCKET_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url).websocket<decltype(app)>(&app)
#define CROW_MIDDLEWARES(app, ...) middlewares<std::remove_reference<decltype(app)>::type, __VA_ARGS__>()
#endif
#define CROW_CATCHALL_ROUTE(app) app.catchall_route()
#define CROW_BP_CATCHALL_ROUTE(blueprint) blueprint.catchall_rule()
@ -67,7 +69,7 @@ namespace crow
/// Process an Upgrade request
///
/// Currently used to upgrrade an HTTP connection to a WebSocket connection
/// Currently used to upgrade an HTTP connection to a WebSocket connection
template<typename Adaptor>
void handle_upgrade(const request& req, response& res, Adaptor&& adaptor)
{
@ -77,7 +79,7 @@ namespace crow
/// Process the request and generate a response for it
void handle(request& req, response& res)
{
router_.handle(req, res);
router_.handle<self_t>(req, res);
}
/// Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
@ -108,6 +110,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();
@ -120,6 +135,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)
{
@ -245,7 +265,8 @@ namespace crow
#ifndef CROW_DISABLE_STATIC_DIR
route<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([](crow::response& res, std::string file_path_partial) {
res.set_static_file_info(CROW_STATIC_DIRECTORY + file_path_partial);
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(CROW_STATIC_DIRECTORY + file_path_partial);
res.end();
});
@ -258,7 +279,8 @@ namespace crow
if (!bp->static_dir().empty())
{
bp->new_rule_tagged<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([bp](crow::response& res, std::string file_path_partial) {
res.set_static_file_info(bp->static_dir() + '/' + file_path_partial);
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(bp->static_dir() + '/' + file_path_partial);
res.end();
});
}
@ -303,7 +325,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);
@ -332,10 +353,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()
{
@ -346,7 +383,7 @@ namespace crow
#ifdef CROW_ENABLE_SSL
/// use certificate and key files for SSL
/// Use certificate and key files for SSL
self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename)
{
ssl_used_ = true;
@ -359,7 +396,7 @@ namespace crow
return *this;
}
/// use .pem file for SSL
/// Use .pem file for SSL
self_t& ssl_file(const std::string& pem_filename)
{
ssl_used_ = true;
@ -371,6 +408,19 @@ namespace crow
return *this;
}
/// 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;
ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
ssl_context_.set_verify_mode(boost::asio::ssl::verify_client_once);
ssl_context_.use_certificate_chain_file(crt_filename);
ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem);
ssl_context_.set_options(
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3);
return *this;
}
self_t& ssl(boost::asio::ssl::context&& ctx)
{
ssl_used_ = true;
@ -394,6 +444,17 @@ namespace crow
return *this;
}
template<typename T, typename... Remain>
self_t& ssl_chainfile(T&&, Remain&&...)
{
// We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
static_assert(
// make static_assert dependent to T; always false
std::is_base_of<T, void>::value,
"Define CROW_ENABLE_SSL to enable ssl support.");
return *this;
}
template<typename T>
self_t& ssl(T&&)
{
@ -452,6 +513,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";
@ -481,6 +543,7 @@ namespace crow
bool server_started_{false};
std::condition_variable cv_started_;
std::mutex start_mutex_;
std::vector<crow::websocket::connection*> websockets_;
};
template<typename... Middlewares>
using App = Crow<Middlewares...>;

View File

@ -325,7 +325,7 @@ constexpr crow::HTTPMethod method_from_string(const char* str)
throw std::runtime_error("invalid http method");
}
constexpr crow::HTTPMethod operator""_method(const char* str, size_t /*len*/)
constexpr crow::HTTPMethod operator"" _method(const char* str, size_t /*len*/)
{
return method_from_string( str );
}

View File

@ -129,27 +129,12 @@ namespace crow
}
if (req.upgrade)
{
#ifdef CROW_ENABLE_SSL
if (handler_->ssl_used())
{
if (req.get_header_value("upgrade") == "h2")
{
// TODO(ipkn): HTTP/2
// currently, ignore upgrade header
}
}
else if (req.get_header_value("upgrade") == "h2c")
// h2 or h2c headers
if (req.get_header_value("upgrade").substr(0, 2) == "h2")
{
// TODO(ipkn): HTTP/2
// currently, ignore upgrade header
}
#else
if (req.get_header_value("upgrade") == "h2c")
{
// TODO(ipkn): HTTP/2
// currently, ignore upgrade header
}
#endif
else
{
close_connection_ = true;
@ -176,7 +161,7 @@ namespace crow
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_);
0, decltype(ctx_), decltype(*middlewares_)>({}, *middlewares_, req, res, ctx_);
if (!res.completed_)
{
@ -213,7 +198,7 @@ namespace crow
detail::middleware_call_criteria_only_global,
(static_cast<int>(sizeof...(Middlewares)) - 1),
decltype(ctx_),
decltype(*middlewares_)>(*middlewares_, ctx_, req_, res);
decltype(*middlewares_)>({}, *middlewares_, ctx_, req_, res);
}
#ifdef CROW_ENABLE_COMPRESSION
if (handler_->compression_used())
@ -332,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);
@ -432,6 +421,7 @@ namespace crow
{
is_writing = true;
boost::asio::write(adaptor_.socket(), buffers_); // Write the response start / headers
cancel_deadline_timer();
if (res.body.length() > 0)
{
std::string buf;

View File

@ -62,6 +62,15 @@ namespace crow
return http_ver_major == major && http_ver_minor == minor;
}
/// Get the body as parameters in QS format.
///
/// This is meant to be used with requests of type "application/x-www-form-urlencoded"
const query_string get_body_params()
{
return query_string(body, false);
}
/// Send data to whoever made this request with a completion handler and return immediately.
template<typename CompletionHandler>
void post(CompletionHandler handler)

View File

@ -19,11 +19,7 @@ namespace crow
template<typename Adaptor, typename Handler, typename... Middlewares>
class Connection;
namespace detail
{
template<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper;
} // namespace detail
class Router;
/// HTTP response
struct response
@ -31,8 +27,7 @@ namespace crow
template<typename Adaptor, typename Handler, typename... Middlewares>
friend class crow::Connection;
template<typename F, typename App, typename... Middlewares>
friend struct crow::detail::handler_middleware_wrapper;
friend class Router;
int code{200}; ///< The Status code for the response.
std::string body; ///< The actual payload containing the response data.
@ -242,17 +237,20 @@ namespace crow
{
std::size_t last_dot = path.find_last_of(".");
std::string extension = path.substr(last_dot + 1);
std::string mimeType = "";
code = 200;
this->add_header("Content-length", std::to_string(file_info.statbuf.st_size));
this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
if (extension != "")
if (!extension.empty())
{
mimeType = mime_types.at(extension);
if (mimeType != "")
this->add_header("Content-Type", mimeType);
const auto mimeType = mime_types.find(extension);
if (mimeType != mime_types.end())
{
this->add_header("Content-Type", mimeType->second);
}
else
this->add_header("content-Type", "text/plain");
{
this->add_header("Content-Type", "text/plain");
}
}
}
else

View File

@ -161,9 +161,18 @@ namespace crow
void stop()
{
io_service_.stop();
shutting_down_ = true; // Prevent the acceptor from taking new connections
for (auto& io_service : io_service_pool_)
io_service->stop();
{
if (io_service != nullptr)
{
CROW_LOG_INFO << "Closing IO service " << &io_service;
io_service->stop(); // Close all io_services (and HTTP connections)
}
}
CROW_LOG_INFO << "Closing main IO service (" << &io_service_ << ')';
io_service_.stop(); // Close main io_service
}
void signal_clear()
@ -182,7 +191,9 @@ namespace crow
uint16_t min_queue_idx = 0;
// TODO improve load balancing
for (uint16_t i = 1; i < task_queue_length_pool_.size() && task_queue_length_pool_[min_queue_idx] > 0; i++)
// size_t is used here to avoid the security issue https://codeql.github.com/codeql-query-help/cpp/cpp-comparison-with-wider-type/
// even though the max value of this can be only uint16_t as concurrency is uint16_t.
for (size_t i = 1; i < task_queue_length_pool_.size() && task_queue_length_pool_[min_queue_idx] > 0; i++)
// No need to check other io_services if the current one has no tasks
{
if (task_queue_length_pool_[i] < task_queue_length_pool_[min_queue_idx])
@ -193,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:
@ -228,6 +242,7 @@ namespace crow
std::vector<detail::task_timer*> task_timer_pool_;
std::vector<std::function<std::string()>> get_cached_date_str_pool_;
tcp::acceptor acceptor_;
bool shutting_down_ = false;
boost::asio::signal_set signals_;
boost::asio::deadline_timer tick_timer_;

View File

@ -60,7 +60,7 @@ namespace crow
prefix = "CRITICAL";
break;
}
std::cerr << "(" << timestamp() << ") [" << prefix << "] " << message << std::endl;
std::cerr << std::string("(") + timestamp() + std::string(") [") + prefix + std::string("] ") + message << std::endl;
}
private:

View File

@ -92,6 +92,18 @@ namespace crow
static constexpr bool value = decltype(f<T>(nullptr))::value;
};
template<typename MW>
struct is_middleware_global
{
template<typename C>
static std::false_type f(typename check_global_call_false<MW>::template get<C>*);
template<typename C>
static std::true_type f(...);
static const bool value = decltype(f<MW>(nullptr))::value;
};
template<typename MW, typename Context, typename ParentContext>
typename std::enable_if<!is_before_handle_arity_3_impl<MW>::value>::type
before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
@ -121,17 +133,17 @@ namespace crow
}
template<template<typename QueryMW> class CallCriteria, // Checks if QueryMW should be called in this context
template<typename CallCriteria,
int N, typename Context, typename Container>
typename std::enable_if<(N < std::tuple_size<typename std::remove_reference<Container>::type>::value), bool>::type
middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx)
{
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
if (!CallCriteria<CurrentMW>::value)
if (!cc.template enabled<CurrentMW>(N))
{
return middleware_call_helper<CallCriteria, N + 1, Context, Container>(middlewares, req, res, ctx);
return middleware_call_helper<CallCriteria, N + 1, Context, Container>(cc, middlewares, req, res, ctx);
}
using parent_context_t = typename Context::template partial<N - 1>;
@ -142,7 +154,7 @@ namespace crow
return true;
}
if (middleware_call_helper<CallCriteria, N + 1, Context, Container>(middlewares, req, res, ctx))
if (middleware_call_helper<CallCriteria, N + 1, Context, Container>(cc, middlewares, req, res, ctx))
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
return true;
@ -151,88 +163,68 @@ namespace crow
return false;
}
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N >= std::tuple_size<typename std::remove_reference<Container>::type>::value), bool>::type
middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/)
middleware_call_helper(const CallCriteria& /*cc*/, Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/)
{
return false;
}
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N < 0)>::type
after_handlers_call_helper(Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/)
after_handlers_call_helper(const CallCriteria& /*cc*/, Container& /*middlewares*/, Context& /*context*/, request& /*req*/, response& /*res*/)
{
}
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N == 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res)
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res)
{
using parent_context_t = typename Context::template partial<N - 1>;
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
if (CallCriteria<CurrentMW>::value)
if (cc.template enabled<CurrentMW>(N))
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
}
}
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N > 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res)
template<typename CallCriteria, int N, typename Context, typename Container>
typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res)
{
using parent_context_t = typename Context::template partial<N - 1>;
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
if (CallCriteria<CurrentMW>::value)
if (cc.template enabled<CurrentMW>(N))
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
}
after_handlers_call_helper<CallCriteria, N - 1, Context, Container>(middlewares, ctx, req, res);
after_handlers_call_helper<CallCriteria, N - 1, Context, Container>(cc, middlewares, ctx, req, res);
}
// A CallCriteria that accepts only global middleware
template<typename MW>
struct middleware_call_criteria_only_global
{
template<typename C>
static std::false_type f(typename check_global_call_false<MW>::template get<C>*);
template<typename C>
static std::true_type f(...);
static const bool value = decltype(f<MW>(nullptr))::value;
template<typename MW>
constexpr bool enabled(int) const
{
return is_middleware_global<MW>::value;
}
};
// wrapped_handler_call transparently wraps a handler call behind (req, res, args...)
template<typename F, typename... Args>
typename std::enable_if<black_magic::is_callable<F, const crow::request, crow::response&, Args...>::value>::type
wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args)
{
static_assert(std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
f(req, res, std::forward<Args>(args)...);
}
template<typename F, typename... Args>
typename std::enable_if<black_magic::is_callable<F, crow::request&, crow::response&, Args...>::value && !black_magic::is_callable<F, const crow::request, crow::response&, Args...>::value>::type
wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args)
{
static_assert(std::is_same<void, decltype(f(std::declval<crow::request&>(), std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
f(req, res, std::forward<Args>(args)...);
}
template<typename F, typename... Args>
typename std::enable_if<black_magic::is_callable<F, crow::response&, Args...>::value>::type
typename std::enable_if<black_magic::CallHelper<F, black_magic::S<Args...>>::value, void>::type
wrapped_handler_call(crow::request& /*req*/, crow::response& res, const F& f, Args&&... args)
{
static_assert(std::is_same<void, decltype(f(std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");
f(res, std::forward<Args>(args)...);
res = crow::response(f(std::forward<Args>(args)...));
res.end();
}
template<typename F, typename... Args>
typename std::enable_if<black_magic::is_callable<F, crow::request, Args...>::value>::type
typename std::enable_if<
!black_magic::CallHelper<F, black_magic::S<Args...>>::value &&
black_magic::CallHelper<F, black_magic::S<crow::request&, Args...>>::value,
void>::type
wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args)
{
static_assert(!std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<Args>()...))>::value,
@ -243,79 +235,96 @@ namespace crow
}
template<typename F, typename... Args>
typename std::enable_if<black_magic::is_callable<F, Args...>::value>::type
typename std::enable_if<
!black_magic::CallHelper<F, black_magic::S<Args...>>::value &&
!black_magic::CallHelper<F, black_magic::S<crow::request&, Args...>>::value &&
black_magic::CallHelper<F, black_magic::S<crow::response&, Args...>>::value,
void>::type
wrapped_handler_call(crow::request& /*req*/, crow::response& res, const F& f, Args&&... args)
{
static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");
static_assert(std::is_same<void, decltype(f(std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
res = crow::response(f(std::forward<Args>(args)...));
res.end();
f(res, std::forward<Args>(args)...);
}
template<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper
template<typename F, typename... Args>
typename std::enable_if<
!black_magic::CallHelper<F, black_magic::S<Args...>>::value &&
!black_magic::CallHelper<F, black_magic::S<crow::request&, Args...>>::value &&
!black_magic::CallHelper<F, black_magic::S<crow::response&, Args...>>::value &&
black_magic::CallHelper<F, black_magic::S<const crow::request&, crow::response&, Args...>>::value,
void>::type
wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args)
{
// CallCriteria bound to the current Middlewares pack
template<typename MW>
struct middleware_call_criteria
static_assert(std::is_same<void, decltype(f(std::declval<crow::request&>(), std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
f(req, res, std::forward<Args>(args)...);
}
// wrapped_handler_call transparently wraps a handler call behind (req, res, args...)
template<typename F, typename... Args>
typename std::enable_if<
!black_magic::CallHelper<F, black_magic::S<Args...>>::value &&
!black_magic::CallHelper<F, black_magic::S<crow::request&, Args...>>::value &&
!black_magic::CallHelper<F, black_magic::S<crow::response&, Args...>>::value &&
!black_magic::CallHelper<F, black_magic::S<const crow::request&, crow::response&, Args...>>::value,
void>::type
wrapped_handler_call(crow::request& req, crow::response& res, const F& f, Args&&... args)
{
static_assert(std::is_same<void, decltype(f(std::declval<crow::request&>(), std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
f(req, res, std::forward<Args>(args)...);
}
template<bool Reversed>
struct middleware_call_criteria_dynamic
{};
template<>
struct middleware_call_criteria_dynamic<false>
{
middleware_call_criteria_dynamic(const std::vector<int>& indices):
indices(indices), slider(0) {}
template<typename>
bool enabled(int mw_index) const
{
static constexpr bool value = black_magic::has_type<MW, std::tuple<Middlewares...>>::value;
};
template<typename... Args>
void operator()(crow::request& req, crow::response& res, Args&&... args) const
{
auto& ctx = *reinterpret_cast<typename App::context_t*>(req.middleware_context);
auto& container = *reinterpret_cast<typename App::mw_container_t*>(req.middleware_container);
auto glob_completion_handler = std::move(res.complete_request_handler_);
res.complete_request_handler_ = [] {};
middleware_call_helper<middleware_call_criteria,
0, typename App::context_t, typename App::mw_container_t>(container, req, res, ctx);
if (res.completed_)
if (slider < int(indices.size()) && indices[slider] == mw_index)
{
glob_completion_handler();
return;
slider++;
return true;
}
res.complete_request_handler_ = [&ctx, &container, &req, &res, &glob_completion_handler] {
after_handlers_call_helper<
middleware_call_criteria,
std::tuple_size<typename App::mw_container_t>::value - 1,
typename App::context_t,
typename App::mw_container_t>(container, ctx, req, res);
glob_completion_handler();
};
wrapped_handler_call(req, res, f, std::forward<Args>(args)...);
return false;
}
F f;
private:
const std::vector<int>& indices;
mutable int slider;
};
template<typename Route, typename App, typename... Middlewares>
struct handler_call_bridge
template<>
struct middleware_call_criteria_dynamic<true>
{
template<typename MW>
using check_app_contains = typename black_magic::has_type<MW, typename App::mw_container_t>;
middleware_call_criteria_dynamic(const std::vector<int>& indices):
indices(indices), slider(int(indices.size()) - 1) {}
static_assert(black_magic::all_true<(std::is_base_of<crow::ILocalMiddleware, Middlewares>::value)...>::value,
"Local middleware has to inherit crow::ILocalMiddleware");
static_assert(black_magic::all_true<(check_app_contains<Middlewares>::value)...>::value,
"Local middleware has to be listed in app middleware");
template<typename F>
void operator()(F&& f) const
template<typename>
bool enabled(int mw_index) const
{
auto wrapped = handler_middleware_wrapper<F, App, Middlewares...>{std::forward<F>(f)};
tptr->operator()(std::move(wrapped));
if (slider >= 0 && indices[slider] == mw_index)
{
slider--;
return true;
}
return false;
}
Route* tptr;
private:
const std::vector<int>& indices;
mutable int slider;
};
} // namespace detail

View File

@ -38,14 +38,14 @@ namespace crow
struct context : private partial_context<Middlewares...>
//struct context : private Middlewares::context... // simple but less type-safe
{
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N == 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N > 0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
template<typename CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res);
template<typename CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res);
template<template<typename QueryMW> class CallCriteria, int N, typename Context, typename Container>
template<typename CallCriteria, int N, typename Context, typename Container>
friend typename std::enable_if<(N < std::tuple_size<typename std::remove_reference<Container>::type>::value), bool>::type
middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx);
template<typename T>
typename T::context& get()

View File

@ -1,4 +1,6 @@
#pragma once
#include <iomanip>
#include <boost/optional.hpp>
#include <boost/algorithm/string/trim.hpp>
#include "crow/http_request.h"
#include "crow/http_response.h"
@ -30,10 +32,144 @@ namespace crow
struct CookieParser
{
// Cookie stores key, value and attributes
struct Cookie
{
enum class SameSitePolicy
{
Strict,
Lax,
None
};
template<typename U>
Cookie(const std::string& key, U&& value):
Cookie()
{
key_ = key;
value_ = std::forward<U>(value);
}
// format cookie to HTTP header format
std::string dump() const
{
const static char* HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT";
std::stringstream ss;
ss << key_ << '=';
ss << (value_.empty() ? "\"\"" : value_);
dumpString(ss, !domain_.empty(), "Domain=", domain_);
dumpString(ss, !path_.empty(), "Path=", path_);
dumpString(ss, secure_, "Secure");
dumpString(ss, httponly_, "HttpOnly");
if (expires_at_)
{
ss << DIVIDER << "Expires="
<< std::put_time(expires_at_.get_ptr(), HTTP_DATE_FORMAT);
}
if (max_age_)
{
ss << DIVIDER << "Max-Age=" << *max_age_;
}
if (same_site_)
{
ss << DIVIDER << "SameSite=";
switch (*same_site_)
{
case SameSitePolicy::Strict:
ss << "Strict";
break;
case SameSitePolicy::Lax:
ss << "Lax";
break;
case SameSitePolicy::None:
ss << "None";
break;
}
}
return ss.str();
}
// Expires attribute
Cookie& expires(const std::tm& time)
{
expires_at_ = time;
return *this;
}
// Max-Age attribute
Cookie& max_age(long long seconds)
{
max_age_ = seconds;
return *this;
}
// Domain attribute
Cookie& domain(const std::string& name)
{
domain_ = name;
return *this;
}
// Path attribute
Cookie& path(const std::string& path)
{
path_ = path;
return *this;
}
// Secured attribute
Cookie& secure()
{
secure_ = true;
return *this;
}
// HttpOnly attribute
Cookie& httponly()
{
httponly_ = true;
return *this;
}
// SameSite attribute
Cookie& same_site(SameSitePolicy ssp)
{
same_site_ = ssp;
return *this;
}
private:
Cookie() = default;
static void dumpString(std::stringstream& ss, bool cond, const char* prefix,
const std::string& value = "")
{
if (cond)
{
ss << DIVIDER << prefix << value;
}
}
private:
std::string key_;
std::string value_;
boost::optional<long long> max_age_{};
std::string domain_ = "";
std::string path_ = "";
bool secure_ = false;
bool httponly_ = false;
boost::optional<std::tm> expires_at_{};
boost::optional<SameSitePolicy> same_site_{};
static constexpr const char* DIVIDER = "; ";
};
struct context
{
std::unordered_map<std::string, std::string> jar;
std::unordered_map<std::string, std::string> cookies_to_add;
std::vector<Cookie> cookies_to_add;
std::string get_cookie(const std::string& key) const
{
@ -43,14 +179,17 @@ namespace crow
return {};
}
void set_cookie(const std::string& key, const std::string& value)
template<typename U>
Cookie& set_cookie(const std::string& key, U&& value)
{
cookies_to_add.emplace(key, value);
cookies_to_add.emplace_back(key, std::forward<U>(value));
return cookies_to_add.back();
}
};
void before_handle(request& req, response& res, context& ctx)
{
// TODO(dranikpg): remove copies, use string_view with c++17
int count = req.headers.count("Cookie");
if (!count)
return;
@ -97,12 +236,9 @@ namespace crow
void after_handle(request& /*req*/, response& res, context& ctx)
{
for (auto& cookie : ctx.cookies_to_add)
for (const auto& cookie : ctx.cookies_to_add)
{
if (cookie.second.empty())
res.add_header("Set-Cookie", cookie.first + "=\"\"");
else
res.add_header("Set-Cookie", cookie.first + "=" + cookie.second);
res.add_header("Set-Cookie", cookie.dump());
}
}
};

View File

@ -182,17 +182,17 @@ namespace crow
CORSRules default_ = CORSRules(this);
};
CORSRules& CORSRules::prefix(const std::string& prefix)
inline CORSRules& CORSRules::prefix(const std::string& prefix)
{
return handler_->prefix(prefix);
}
CORSRules& CORSRules::blueprint(const Blueprint& bp)
inline CORSRules& CORSRules::blueprint(const Blueprint& bp)
{
return handler_->blueprint(bp);
}
CORSRules& CORSRules::global()
inline CORSRules& CORSRules::global()
{
return handler_->global();
}

View File

@ -42,7 +42,7 @@ namespace crow
return empty;
}
/// Same as \ref get_header_value_Object() but for \ref multipart.header
/// Same as \ref get_header_value_object() but for \ref multipart.header
template<typename T>
inline const header& get_header_object(const T& headers, const std::string& key)
{

View File

@ -10,19 +10,20 @@
namespace crow
{
// ----------------------------------------------------------------------------
// qs_parse (modified)
// https://github.com/bartgrantham/qs_parse
// ----------------------------------------------------------------------------
/* Similar to strncmp, but handles URL-encoding for either string */
int qs_strncmp(const char * s, const char * qs, size_t n);
int qs_strncmp(const char* s, const char* qs, size_t n);
/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
* Also decodes the value portion of the k/v pair *in-place*. In a future
* enhancement it will also have a compile-time option of sorting qs_kv
* alphabetically by key. */
int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);
int qs_parse(char* qs, char* qs_kv[], int qs_kv_size, bool parse_url);
/* Used by qs_parse to decode the value portion of a k/v pair */
@ -96,7 +97,7 @@ inline int qs_strncmp(const char * s, const char * qs, size_t n)
}
inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
inline int qs_parse(char* qs, char* qs_kv[], int qs_kv_size, bool parse_url = true)
{
int i, j;
char * substr_ptr;
@ -104,7 +105,7 @@ inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
// find the beginning of the k/v substrings or the fragment
substr_ptr = qs + strcspn(qs, "?#");
substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
if (substr_ptr[0] != '\0')
substr_ptr++;
else
@ -137,7 +138,7 @@ inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
#endif
return i;
}
}
inline int qs_decode(char * qs)
@ -285,8 +286,9 @@ inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t
// ----------------------------------------------------------------------------
namespace crow
namespace crow
{
struct request;
/// A class to represent any data coming after the `?` in the request URL into key-value pairs.
class query_string
{
@ -295,35 +297,34 @@ namespace crow
query_string()
{
}
query_string(const query_string& qs)
: url_(qs.url_)
query_string(const query_string& qs):
url_(qs.url_)
{
for(auto p:qs.key_value_pairs_)
for (auto p : qs.key_value_pairs_)
{
key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str()));
key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
}
}
query_string& operator = (const query_string& qs)
query_string& operator=(const query_string& qs)
{
url_ = qs.url_;
key_value_pairs_.clear();
for(auto p:qs.key_value_pairs_)
for (auto p : qs.key_value_pairs_)
{
key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str()));
key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
}
return *this;
}
query_string& operator = (query_string&& qs)
query_string& operator=(query_string&& qs)
{
key_value_pairs_ = std::move(qs.key_value_pairs_);
char* old_data = (char*)qs.url_.c_str();
url_ = std::move(qs.url_);
for(auto& p:key_value_pairs_)
for (auto& p : key_value_pairs_)
{
p += (char*)url_.c_str() - old_data;
}
@ -331,19 +332,19 @@ namespace crow
}
query_string(std::string url)
: url_(std::move(url))
query_string(std::string params, bool url = true):
url_(std::move(params))
{
if (url_.empty())
return;
key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url);
key_value_pairs_.resize(count);
}
void clear()
void clear()
{
key_value_pairs_.clear();
url_.clear();
@ -352,38 +353,38 @@ namespace crow
friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
{
os << "[ ";
for(size_t i = 0; i < qs.key_value_pairs_.size(); ++i) {
for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i)
{
if (i)
os << ", ";
os << qs.key_value_pairs_[i];
}
os << " ]";
return os;
}
/// Get a value from a name, used for `?name=value`.
///
/// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list().
char* get (const std::string& name) const
char* get(const std::string& name) const
{
char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
return ret;
}
/// Works similar to \ref get() except it removes the item from the query string.
char* pop (const std::string& name)
char* pop(const std::string& name)
{
char* ret = get(name);
if (ret != nullptr)
{
for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
{
std::string str_item(key_value_pairs_[i]);
if (str_item.substr(0, name.size()+1) == name+'=')
if (str_item.substr(0, name.size() + 1) == name + '=')
{
key_value_pairs_.erase(key_value_pairs_.begin()+i);
key_value_pairs_.erase(key_value_pairs_.begin() + i);
break;
}
}
@ -395,14 +396,14 @@ namespace crow
///
/// Note: Square brackets in the above example are controlled by `use_brackets` boolean (true by default). If set to false, the example becomes `?name=value1,name=value2...name=valuen`
std::vector<char*> get_list (const std::string& name, bool use_brackets = true) const
std::vector<char*> get_list(const std::string& name, bool use_brackets = true) const
{
std::vector<char*> ret;
std::string plus = name + (use_brackets ? "[]" : "");
char* element = nullptr;
int count = 0;
while(1)
while (1)
{
element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
if (!element)
@ -413,17 +414,17 @@ namespace crow
}
/// Similar to \ref get_list() but it removes the
std::vector<char*> pop_list (const std::string& name, bool use_brackets = true)
std::vector<char*> pop_list(const std::string& name, bool use_brackets = true)
{
std::vector<char*> ret = get_list(name, use_brackets);
if (!ret.empty())
{
for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
{
std::string str_item(key_value_pairs_[i]);
if ((use_brackets ? (str_item.substr(0, name.size()+3) == name+"[]=") : (str_item.substr(0, name.size()+1) == name+'=')))
if ((use_brackets ? (str_item.substr(0, name.size() + 3) == name + "[]=") : (str_item.substr(0, name.size() + 1) == name + '=')))
{
key_value_pairs_.erase(key_value_pairs_.begin()+i--);
key_value_pairs_.erase(key_value_pairs_.begin() + i--);
}
}
}
@ -436,12 +437,12 @@ namespace crow
/// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
///
/// if your query string has both empty brackets and ones with a key inside, use pop_list() to get all the values without a key before running this method.
std::unordered_map<std::string, std::string> get_dict (const std::string& name) const
std::unordered_map<std::string, std::string> get_dict(const std::string& name) const
{
std::unordered_map<std::string, std::string> ret;
int count = 0;
while(1)
while (1)
{
if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
ret.insert(*element);
@ -452,17 +453,17 @@ namespace crow
}
/// Works the same as \ref get_dict() but removes the values from the query string.
std::unordered_map<std::string, std::string> pop_dict (const std::string& name)
std::unordered_map<std::string, std::string> pop_dict(const std::string& name)
{
std::unordered_map<std::string, std::string> ret = get_dict(name);
if (!ret.empty())
{
for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
{
std::string str_item(key_value_pairs_[i]);
if (str_item.substr(0, name.size()+1) == name+'[')
if (str_item.substr(0, name.size() + 1) == name + '[')
{
key_value_pairs_.erase(key_value_pairs_.begin()+i--);
key_value_pairs_.erase(key_value_pairs_.begin() + i--);
}
}
}
@ -472,7 +473,7 @@ namespace crow
std::vector<std::string> keys() const
{
std::vector<std::string> ret;
for (auto element: key_value_pairs_)
for (auto element : key_value_pairs_)
{
std::string str_element(element);
ret.emplace_back(str_element.substr(0, str_element.find('=')));
@ -485,4 +486,4 @@ namespace crow
std::vector<char*> key_value_pairs_;
};
} // end namespace
} // namespace crow

View File

@ -7,6 +7,8 @@
#include <memory>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <algorithm>
#include <type_traits>
#include "crow/common.h"
#include "crow/http_response.h"
@ -22,6 +24,63 @@ namespace crow
constexpr const uint16_t INVALID_BP_ID{((uint16_t)-1)};
namespace detail
{
/// Typesafe wrapper for storing lists of middleware as their indices in the App
struct middleware_indices
{
template<typename App>
void push()
{}
template<typename App, typename MW, typename... Middlewares>
void push()
{
using MwContainer = typename App::mw_container_t;
static_assert(black_magic::has_type<MW, MwContainer>::value, "Middleware must be present in app");
static_assert(std::is_base_of<crow::ILocalMiddleware, MW>::value, "Middleware must extend ILocalMiddleware");
int idx = black_magic::tuple_index<MW, MwContainer>::value;
indices_.push_back(idx);
push<App, Middlewares...>();
}
void merge_front(const detail::middleware_indices& other)
{
indices_.insert(indices_.begin(), other.indices_.cbegin(), other.indices_.cend());
}
void merge_back(const detail::middleware_indices& other)
{
indices_.insert(indices_.end(), other.indices_.cbegin(), other.indices_.cend());
}
void pop_back(const detail::middleware_indices& other)
{
indices_.resize(indices_.size() - other.indices_.size());
}
bool empty() const
{
return indices_.empty();
}
// Sorts indices and filters out duplicates to allow fast lookups with traversal
void pack()
{
std::sort(indices_.begin(), indices_.end());
indices_.erase(std::unique(indices_.begin(), indices_.end()), indices_.end());
}
const std::vector<int>& indices()
{
return indices_;
}
private:
std::vector<int> indices_;
};
} // namespace detail
/// A base class for all rules.
///
@ -74,7 +133,6 @@ namespace crow
}
}
std::string custom_templates_base;
const std::string& rule() { return rule_; }
@ -87,6 +145,8 @@ namespace crow
std::unique_ptr<BaseRule> rule_to_upgrade_;
detail::middleware_indices mw_indices_;
friend class Router;
friend class Blueprint;
template<typename T>
@ -368,13 +428,16 @@ namespace crow
///
/// Provides the interface for the user to put in the necessary handlers for a websocket to work.
template<typename App>
class WebSocketRule : public BaseRule
{
using self_t = WebSocketRule;
public:
WebSocketRule(std::string rule):
BaseRule(std::move(rule))
WebSocketRule(std::string rule, App* app):
BaseRule(std::move(rule)),
app_(app),
max_payload_(UINT64_MAX)
{}
void validate() override
@ -388,15 +451,24 @@ namespace crow
void handle_upgrade(const request& req, response&, SocketAdaptor&& adaptor) override
{
new crow::websocket::Connection<SocketAdaptor>(req, std::move(adaptor), 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>(req, std::move(adaptor), 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)
{
@ -433,11 +505,14 @@ namespace crow
}
protected:
App* app_;
std::function<void(crow::websocket::connection&)> open_handler_;
std::function<void(crow::websocket::connection&, const std::string&, bool)> message_handler_;
std::function<void(crow::websocket::connection&, const std::string&)> close_handler_;
std::function<void(crow::websocket::connection&)> error_handler_;
std::function<bool(const crow::request&)> accept_handler_;
uint64_t max_payload_;
bool max_payload_override_ = false;
};
/// Allows the user to assign parameters using functions.
@ -448,9 +523,11 @@ namespace crow
struct RuleParameterTraits
{
using self_t = T;
WebSocketRule& websocket()
template<typename App>
WebSocketRule<App>& websocket(App* app)
{
auto p = new WebSocketRule(static_cast<self_t*>(this)->rule_);
auto p = new WebSocketRule<App>(static_cast<self_t*>(this)->rule_, app);
static_cast<self_t*>(this)->rule_to_upgrade_.reset(p);
return *p;
}
@ -474,6 +551,14 @@ namespace crow
static_cast<self_t*>(this)->methods_ |= 1 << static_cast<int>(method);
return static_cast<self_t&>(*this);
}
/// Enable local middleware for this handler
template<typename App, typename... Middlewares>
self_t& middlewares()
{
static_cast<self_t*>(this)->mw_indices_.template push<App, Middlewares...>();
return static_cast<self_t&>(*this);
}
};
/// A rule that can change its parameters during runtime.
@ -607,16 +692,6 @@ namespace crow
detail::routing_handler_call_helper::call_params<decltype(handler_)>{handler_, params, req, res});
}
/// Enable local middleware for this handler
template<typename App, typename... Middlewares>
crow::detail::handler_call_bridge<TaggedRule<Args...>, App, Middlewares...>
middlewares()
{
// the handler_call_bridge allows the functor to be placed directly after this function
// instead of wrapping it with more parentheses
return {this};
}
private:
std::function<void(crow::request&, crow::response&, Args...)> handler_;
};
@ -1128,6 +1203,12 @@ namespace crow
return catchall_rule_;
}
template<typename App, typename... Middlewares>
void middlewares()
{
mw_indices_.push<App, Middlewares...>();
}
private:
void apply_blueprint(Blueprint& blueprint)
{
@ -1153,6 +1234,7 @@ namespace crow
std::vector<std::unique_ptr<BaseRule>> all_rules_;
CatchallRule catchall_rule_;
std::vector<Blueprint*> blueprints_;
detail::middleware_indices mw_indices_;
friend class Router;
};
@ -1199,6 +1281,8 @@ namespace crow
rule_without_trailing_slash.pop_back();
}
ruleObject->mw_indices_.pack();
ruleObject->foreach_method([&](int method) {
per_methods_[method].rules.emplace_back(ruleObject);
per_methods_[method].trie.add(rule, per_methods_[method].rules.size() - 1, BP_index != INVALID_BP_ID ? blueprints[BP_index]->prefix().length() : 0, BP_index);
@ -1207,7 +1291,7 @@ namespace crow
// request to '/about' url matches '/about/' rule
if (has_trailing_slash)
{
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);
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);
}
});
}
@ -1244,7 +1328,7 @@ namespace crow
}
}
void validate_bp(std::vector<Blueprint*> blueprints)
void validate_bp(std::vector<Blueprint*> blueprints, detail::middleware_indices& current_mw)
{
for (unsigned i = 0; i < blueprints.size(); i++)
{
@ -1259,6 +1343,8 @@ namespace crow
per_methods_[i].trie.add(blueprint->prefix(), 0, blueprint->prefix().length(), i);
}
}
current_mw.merge_back(blueprint->mw_indices_);
for (auto& rule : blueprint->all_rules_)
{
if (rule)
@ -1267,17 +1353,20 @@ namespace crow
if (upgraded)
rule = std::move(upgraded);
rule->validate();
rule->mw_indices_.merge_front(current_mw);
internal_add_rule_object(rule->rule(), rule.get(), i, blueprints);
}
}
validate_bp(blueprint->blueprints_);
validate_bp(blueprint->blueprints_, current_mw);
current_mw.pop_back(blueprint->mw_indices_);
}
}
void validate()
{
//Take all the routes from the registered blueprints and add them to `all_rules_` to be processed.
validate_bp(blueprints_);
detail::middleware_indices blueprint_mw;
validate_bp(blueprints_, blueprint_mw);
for (auto& rule : all_rules_)
{
@ -1442,6 +1531,7 @@ namespace crow
return std::string();
}
template<typename App>
void handle(request& req, response& res)
{
HTTPMethod method_actual = req.method;
@ -1468,7 +1558,6 @@ namespace crow
allow = allow.substr(0, allow.size() - 2);
res = response(204);
res.set_header("Allow", allow);
res.manual_length_header = true;
res.end();
return;
}
@ -1486,7 +1575,6 @@ namespace crow
allow = allow.substr(0, allow.size() - 2);
res = response(204);
res.set_header("Allow", allow);
res.manual_length_header = true;
res.end();
return;
}
@ -1554,7 +1642,8 @@ namespace crow
// any uncaught exceptions become 500s
try
{
rules[rule_index]->handle(req, res, std::get<2>(found));
auto& rule = rules[rule_index];
handle_rule<App>(rule, req, res, std::get<2>(found));
}
catch (std::exception& e)
{
@ -1572,6 +1661,49 @@ namespace crow
}
}
template<typename App>
typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value != 0, void>::type
handle_rule(BaseRule* rule, crow::request& req, crow::response& res, const crow::routing_params& rp)
{
if (!rule->mw_indices_.empty())
{
auto& ctx = *reinterpret_cast<typename App::context_t*>(req.middleware_context);
auto& container = *reinterpret_cast<typename App::mw_container_t*>(req.middleware_container);
detail::middleware_call_criteria_dynamic<false> crit_fwd(rule->mw_indices_.indices());
auto glob_completion_handler = std::move(res.complete_request_handler_);
res.complete_request_handler_ = [] {};
detail::middleware_call_helper<decltype(crit_fwd),
0, typename App::context_t, typename App::mw_container_t>(crit_fwd, container, req, res, ctx);
if (res.completed_)
{
glob_completion_handler();
return;
}
res.complete_request_handler_ = [&rule, &ctx, &container, &req, &res, &glob_completion_handler] {
detail::middleware_call_criteria_dynamic<true> crit_bwd(rule->mw_indices_.indices());
detail::after_handlers_call_helper<
decltype(crit_bwd),
std::tuple_size<typename App::mw_container_t>::value - 1,
typename App::context_t,
typename App::mw_container_t>(crit_bwd, container, ctx, req, res);
glob_completion_handler();
};
}
rule->handle(req, res, rp);
}
template<typename App>
typename std::enable_if<std::tuple_size<typename App::mw_container_t>::value == 0, void>::type
handle_rule(BaseRule* rule, crow::request& req, crow::response& res, const crow::routing_params& rp)
{
rule->handle(req, res, rp);
}
void debug_print()
{
for (int i = 0; i < static_cast<int>(HTTPMethod::InternalMethodCount); i++)

View File

@ -59,6 +59,6 @@
#if __cplusplus > 201103L
#define CROW_GCC83_WORKAROUND
#else
#error "GCC 8.1 - 8.3 has a bug that prevents crow from compiling with C++11. Please update GCC to > 8.3 or use C++ > 11."
#error "GCC 8.1 - 8.3 has a bug that prevents Crow from compiling with C++11. Please update GCC to > 8.3 or use C++ > 11."
#endif
#endif

View File

@ -233,6 +233,8 @@ namespace crow
template<template<typename... Args> class U>
using rebind = U<T...>;
};
// Check whether the template function can be called with specific arguments
template<typename F, typename Set>
struct CallHelper;
template<typename F, typename... Args>
@ -263,22 +265,6 @@ namespace crow
struct has_type<T, std::tuple<T, Ts...>> : std::true_type
{};
// Find index of type in tuple
template<class T, class Tuple>
struct tuple_index;
template<class T, class... Types>
struct tuple_index<T, std::tuple<T, Types...>>
{
static const int value = 0;
};
template<class T, class U, class... Types>
struct tuple_index<T, std::tuple<U, Types...>>
{
static const int value = 1 + tuple_index<T, std::tuple<Types...>>::value;
};
// Extract element from forward tuple or get default
#ifdef CROW_CAN_USE_CPP14
template<typename T, typename Tup>
@ -302,18 +288,21 @@ namespace crow
{
return T{};
}
// Find index of type in tuple
template<class T, class Tuple>
struct tuple_index;
// Check F is callable with Args
template<typename F, typename... Args>
struct is_callable
template<class T, class... Types>
struct tuple_index<T, std::tuple<T, Types...>>
{
template<typename F2, typename... Args2>
static std::true_type __test(decltype(std::declval<F2>()(std::declval<Args2>()...))*);
static const int value = 0;
};
template<typename F2, typename... Args2>
static std::false_type __test(...);
static constexpr bool value = decltype(__test<F, Args...>(nullptr))::value;
template<class T, class U, class... Types>
struct tuple_index<T, std::tuple<U, Types...>>
{
static const int value = 1 + tuple_index<T, std::tuple<Types...>>::value;
};
// Kind of fold expressions in C++11

View File

@ -1,6 +1,7 @@
#pragma once
#include <boost/algorithm/string/predicate.hpp>
#include <boost/array.hpp>
#include "crow/logging.h"
#include "crow/socket_adaptors.h"
#include "crow/http_request.h"
#include "crow/TinySHA1.hpp"
@ -60,7 +61,8 @@ namespace crow
//
/// A websocket connection.
template<typename Adaptor>
template<typename Adaptor, typename Handler>
class Connection : public connection
{
public:
@ -69,18 +71,25 @@ namespace crow
///
/// Requires a request with an "Upgrade: websocket" header.<br>
/// Automatically handles the handshake.
Connection(const crow::request& req, Adaptor&& adaptor,
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,
std::function<void(crow::websocket::connection&)> error_handler,
std::function<bool(const crow::request&)> accept_handler):
adaptor_(std::move(adaptor)),
open_handler_(std::move(open_handler)), message_handler_(std::move(message_handler)), close_handler_(std::move(close_handler)), error_handler_(std::move(error_handler)), accept_handler_(std::move(accept_handler))
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)),
error_handler_(std::move(error_handler)),
accept_handler_(std::move(accept_handler))
{
if (!boost::iequals(req.get_header_value("upgrade"), "websocket"))
{
adaptor.close();
handler_->remove_websocket(this);
delete this;
return;
}
@ -90,6 +99,7 @@ namespace crow
if (!accept_handler_(req))
{
adaptor.close();
handler_->remove_websocket(this);
delete this;
return;
}
@ -102,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));
}
@ -195,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)
@ -285,6 +301,7 @@ namespace crow
has_mask_ = false;
#else
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -310,6 +327,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -347,6 +365,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -381,6 +400,7 @@ namespace crow
else
{
close_connection_ = true;
adaptor_.shutdown_readwrite();
adaptor_.close();
if (error_handler_)
error_handler_(*this);
@ -390,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),
@ -417,7 +445,9 @@ namespace crow
close_connection_ = true;
if (error_handler_)
error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close();
check_destroy();
}
});
}
@ -455,7 +485,9 @@ namespace crow
close_connection_ = true;
if (error_handler_)
error_handler_(*this);
adaptor_.shutdown_readwrite();
adaptor_.close();
check_destroy();
}
});
}
@ -534,6 +566,7 @@ namespace crow
}
else
{
adaptor_.shutdown_readwrite();
adaptor_.close();
close_connection_ = true;
if (!is_close_handler_called_)
@ -603,12 +636,14 @@ 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;
}
private:
Adaptor adaptor_;
Handler* handler_;
std::vector<std::string> sending_buffers_;
std::vector<std::string> write_buffers_;
@ -620,6 +655,7 @@ namespace crow
WebSocketReadState state_{WebSocketReadState::MiniHeader};
uint16_t remaining_length16_{0};
uint64_t remaining_length_{0};
uint64_t max_payload_bytes_{UINT64_MAX};
bool close_connection_{false};
bool is_reading{false};
bool has_mask_{false};

View File

@ -7,81 +7,103 @@ site_url: https://crowcpp.org
edit_uri: ""
theme:
name: material
font: false
language: 'en'
features:
navigation.tabs
favicon: 'assets/favicon.svg'
logo: 'assets/favicon.svg'
icon:
repo: fontawesome/brands/github-square
static_templates:
- privacy_policy.html
custom_dir: docs/overrides
name: material
font: false
language: 'en'
features:
navigation.tabs
favicon: 'assets/favicon.svg'
logo: 'assets/favicon.svg'
icon:
repo: fontawesome/brands/github-square
static_templates:
- privacy_policy.html
palette:
- media: "(prefers-color-scheme: light)"
scheme: crow-light
toggle:
icon: fontawesome/solid/sun
name: Using Light Theme
- media: "(prefers-color-scheme: dark)"
scheme: crow-dark
toggle:
icon: fontawesome/solid/moon
name: Using Dark Theme
custom_dir: docs/overrides
markdown_extensions:
- admonition
- pymdownx.highlight
- pymdownx.tabbed
- pymdownx.superfences
- pymdownx.inlinehilite
- pymdownx.keys
- toc:
permalink: true
- admonition
- pymdownx.highlight
- pymdownx.tabbed
- pymdownx.superfences
- pymdownx.inlinehilite
- pymdownx.keys
- attr_list
- pymdownx.emoji:
emoji_index: !!python/name:materialx.emoji.twemoji
emoji_generator: !!python/name:materialx.emoji.to_svg
nav:
- Home: index.md
- Getting Started:
- Setup:
- Linux: getting_started/setup/linux.md
- MacOS: getting_started/setup/macos.md
- Windows: getting_started/setup/windows.md
- Your First Application: getting_started/your_first_application.md
- Guides:
- Different parts of Crow:
- App: guides/app.md
- Routes: guides/routes.md
- Logging: guides/logging.md
- JSON: guides/json.md
- Templating (Mustache): guides/templating.md
- Multipart: guides/multipart.md
- Query Strings: guides/query-string.md
- Middleware: guides/middleware.md
- SSL: guides/ssl.md
- Static Files: guides/static.md
- Blueprints: guides/blueprints.md
- Compression: guides/compression.md
- Websockets: guides/websockets.md
- Base64: guides/base64.md
- Writing Tests: guides/testing.md
- Using Crow:
- HTTP Authorization: guides/auth.md
- CORS Setup: guides/CORS.md
- Server setup:
- Proxies: guides/proxies.md
- Systemd run on startup: guides/syste.md
- API Reference:
- API Reference: 'reference/index.html'
- Home: index.md
- Getting Started:
- Setup:
- Linux: getting_started/setup/linux.md
- MacOS: getting_started/setup/macos.md
- Windows: getting_started/setup/windows.md
- Your First Application: getting_started/your_first_application.md
- Guides:
- Different parts of Crow:
- App: guides/app.md
- Routes: guides/routes.md
- Logging: guides/logging.md
- JSON: guides/json.md
- Templating (Mustache): guides/templating.md
- Multipart: guides/multipart.md
- Query Strings: guides/query-string.md
- Middleware: guides/middleware.md
- SSL: guides/ssl.md
- Static Files: guides/static.md
- Blueprints: guides/blueprints.md
- Compression: guides/compression.md
- Websockets: guides/websockets.md
- Base64: guides/base64.md
- Writing Tests: guides/testing.md
- Using Crow:
- HTTP Authorization: guides/auth.md
- Included Middlewares: guides/included-middleware.md
- Server setup:
- Proxies: guides/proxies.md
- Systemd run on startup: guides/syste.md
- API Reference:
- API Reference: 'reference/index.html'
extra:
analytics:
provider: matomo
id: 1
link: //thee.dev/matomo/
social:
- icon: fontawesome/brands/github
link: https://github.com/crowcpp/crow
- icon: fontawesome/brands/gitter
link: https://gitter.im/crowfork/community
version:
provider: mike
default: latest
analytics:
provider: matomo
id: 1
link: //thee.dev/matomo/
social:
- icon: fontawesome/brands/github
link: https://github.com/crowcpp/crow
- icon: fontawesome/brands/gitter
link: https://gitter.im/crowfork/community
plugins:
- redirects:
redirect_maps:
'getting_started/setup/': 'getting_started/setup/linux.md'
- meta-descriptions
- redirects:
redirect_maps:
'getting_started/setup/': 'getting_started/setup/linux.md'
- meta-descriptions
- search
extra_css:
- 'stylesheets/colors.css'
- 'stylesheets/latofonts.css'
- 'stylesheets/extra.css'
- 'stylesheets/colors.css'
- 'stylesheets/latofonts.css'
- 'stylesheets/extra.css'
copyright: 'Copyright &copy; 2020-2022 CrowCpp'

45
scripts/generateDocumentationAndDeploy.sh Normal file → Executable file
View File

@ -2,14 +2,14 @@
################################################################################
# Title : generateDocumentationAndDeploy.sh
# Date created : 2016/02/22
# Notes : This script was modified to suit Crow and work with mkdocs and Drone.io CI.
# Notes : This script was modified to suit Crow and work with mkdocs (multiple versions) and Drone.io CI.
__AUTHOR__="Jeroen de Bruijn"
# Preconditions:
# - Packages doxygen doxygen-doc doxygen-latex doxygen-gui graphviz
# must be installed.
# - Doxygen configuration file must have the destination directory empty and
# source code directory with a $(TRAVIS_BUILD_DIR) prefix.
# - An gh-pages branch should already exist. See below for mor info on hoe to
# - A gh-pages branch should already exist. See below for more info on how to
# create a gh-pages branch.
#
# Required global variables:
@ -19,6 +19,7 @@ __AUTHOR__="Jeroen de Bruijn"
# - GH_REPO_NAME : The name of the repository.
# - GH_REPO_REF : The GitHub reference to the repository.
# - GH_REPO_TOKEN : Secure token to the github repository.
# - DOCS_VERSION : Used for custom version documentation (defaults to master).
#
# For information on how to encrypt variables for Travis CI please go to
# https://docs.travis-ci.com/user/environment-variables/#Encrypted-Variables
@ -30,15 +31,25 @@ __AUTHOR__="Jeroen de Bruijn"
# the gh-pages branch of a repository specified by GH_REPO_REF.
# Before this script is used there should already be a gh-pages branch in the
# repository.
#
# The branch should be empty except for an empty '.nojekyll' file and an
# 'index.html' file that redirects to master/index.html. Optionally you would
# also need a 'CNAME' file containing your custom domain (without http or www).
#
################################################################################
################################################################################
################################################################################
##### Setup this script and get the current gh-pages branch. #####
##### Setup this script and get the current gh-pages branch. #####
echo 'Setting up the script...'
# Exit with nonzero exit code if anything fails
set -e
DOCS_VERSION=${DOCS_VERSION:-"master"}
echo "Using $DOCS_VERSION as version."
# Create a clean working directory for this script.
mkdir code_docs
cd code_docs
@ -55,29 +66,38 @@ git config --global push.default simple
git config user.name "Drone CI"
git config user.email "drone@drone.io"
# Remove everything currently in the gh-pages branch.
# Create a new directory for this version of the docs if one doesn't exist.
# Then navigate to that directory.
mkdir -p $DOCS_VERSION
cd $DOCS_VERSION
################################################################################
##### Generate the MkDocs documentation to replace the old docs. #####
echo 'Removing old documentation...'
# Remove everything currently in the version directory.
# GitHub is smart enough to know which files have changed and which files have
# stayed the same and will only update the changed files. So the gh-pages branch
# can be safely cleaned, and it is sure that everything pushed later is the new
# documentation.
cp CNAME ..
rm -rf *
mv ../CNAME .
# Copy the mkdocs documentation to the work directory and generate the mkdocs
echo 'Generating MkDocs documentation...'
# Copy the mkdocs documentation to the work directory and generate the mkdocs'
# 'site' directory
cp ../../mkdocs.yml .
cp -r ../../docs .
cp ../../../mkdocs.yml .
cp -r ../../../docs .
mkdocs build -v
# Need to create a .nojekyll file to allow filenames starting with an underscore
# to be seen on the gh-pages site. Therefore creating an empty .nojekyll file.
# Presumably this is only needed when the SHORT_NAMES option in Doxygen is set
# to NO, which it is by default. So creating the file just in case.
echo "" > .nojekyll
#echo "" > .nojekyll
################################################################################
##### Generate the Doxygen code documentation and log the output. #####
##### Generate the Doxygen code documentation and log the output. #####
echo 'Generating Doxygen code documentation...'
# Redirect both stderr and stdout to the log file AND the console.
doxygen $DOXYFILE 2>&1 | tee doxygen.log
@ -89,9 +109,10 @@ mv site/* .
rm -r site
rm mkdocs.yml
rm -r docs
mv versions.json ../
################################################################################
##### Upload the documentation to the gh-pages branch of the repository. #####
##### Upload the documentation to the gh-pages branch of the repository. #####
# Only upload if Doxygen successfully created the documentation.
# Check this by verifying that the reference directory (for doxygen) and
# the file index.html (for mkdocs) both exist.

View File

@ -44,7 +44,8 @@ else:
middlewares_actual = middlewares
print("Middlewares: " + str(middlewares_actual))
re_depends = re.compile('^#include "(.*)"', re.MULTILINE)
re_depends = re.compile('^#include \"(.*)\"\n', re.MULTILINE)
re_pragma = re.compile('^(.*)#pragma once(.*)\n', re.MULTILINE)
headers = [x.rsplit(sep, 1)[-1] for x in glob(pt.join(header_path, '*.h*'))]
headers += ['crow'+sep + x.rsplit(sep, 1)[-1] for x in glob(pt.join(header_path, 'crow'+sep+'*.h*'))]
headers += [('crow'+sep+'middlewares'+sep + x + '.h') for x in middlewares_actual]
@ -85,10 +86,12 @@ for x in edges:
assert order.index(x) < order.index(y), 'cyclic include detected'
print(order)
build = [lsc]
build = [lsc, '#pragma once']
for header in order:
d = open(pt.join(header_path, header)).read()
build.append(re_depends.sub(lambda x: '\n', d))
build.append('\n')
d_no_depend = re_depends.sub(lambda x: '', d)
d_no_pragma = re_pragma.sub(lambda x: '', d_no_depend)
build.append(d_no_pragma)
#build.append('\n')
open(output_path, 'w').write('\n'.join(build))

View File

@ -0,0 +1 @@
Test file with a strange extension.

View File

@ -55,6 +55,7 @@ TEST_CASE("SSL")
size_t x = 0;
size_t y = 0;
long z = 0;
while (x < 121)
{
@ -63,7 +64,20 @@ TEST_CASE("SSL")
buf[y] = '\0';
}
CHECK(std::string("Hello world, I'm keycrt.") == std::string(buf));
std::string to_test(buf);
if ((z = to_test.length() - 24) >= 0)
{
CHECK(std::string("Hello world, I'm keycrt.") == to_test.substr(z));
}
else
{
CHECK(std::string("Hello world, I'm keycrt.").substr((z * -1)) == to_test);
}
boost::system::error_code ec;
c.lowest_layer().shutdown(boost::asio::socket_base::shutdown_type::shutdown_both, ec);
}
/*

View File

@ -8,6 +8,7 @@
#include <vector>
#include <thread>
#include <chrono>
#include <type_traits>
#include "catch.hpp"
#include "crow.h"
@ -571,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")
{
{
@ -1253,7 +1309,11 @@ struct IntSettingMiddleware
std::vector<std::string> test_middleware_context_vector;
struct FirstMW
struct empty_type
{};
template<bool Local>
struct FirstMW : public std::conditional<Local, crow::ILocalMiddleware, empty_type>::type
{
struct context
{
@ -1272,38 +1332,40 @@ struct FirstMW
}
};
struct SecondMW
template<bool Local>
struct SecondMW : public std::conditional<Local, crow::ILocalMiddleware, empty_type>::type
{
struct context
{};
template<typename AllContext>
void before_handle(request& req, response& res, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("2 before");
if (req.url == "/break") res.end();
all_ctx.template get<FirstMW<Local>>().v.push_back("2 before");
if (req.url.find("/break") != std::string::npos) res.end();
}
template<typename AllContext>
void after_handle(request&, response&, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("2 after");
all_ctx.template get<FirstMW<Local>>().v.push_back("2 after");
}
};
struct ThirdMW
template<bool Local>
struct ThirdMW : public std::conditional<Local, crow::ILocalMiddleware, empty_type>::type
{
struct context
{};
template<typename AllContext>
void before_handle(request&, response&, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("3 before");
all_ctx.template get<FirstMW<Local>>().v.push_back("3 before");
}
template<typename AllContext>
void after_handle(request&, response&, context&, AllContext& all_ctx)
{
all_ctx.template get<FirstMW>().v.push_back("3 after");
all_ctx.template get<FirstMW<Local>>().v.push_back("3 after");
}
};
@ -1316,7 +1378,7 @@ TEST_CASE("middleware_context")
// or change the order of FirstMW and SecondMW
// App<IntSettingMiddleware, SecondMW, FirstMW> app;
App<IntSettingMiddleware, FirstMW, SecondMW, ThirdMW> app;
App<IntSettingMiddleware, FirstMW<false>, SecondMW<false>, ThirdMW<false>> app;
int x{};
CROW_ROUTE(app, "/")
@ -1326,7 +1388,7 @@ TEST_CASE("middleware_context")
x = ctx.val;
}
{
auto& ctx = app.get_context<FirstMW>(req);
auto& ctx = app.get_context<FirstMW<false>>(req);
ctx.v.push_back("handle");
}
@ -1335,7 +1397,7 @@ TEST_CASE("middleware_context")
CROW_ROUTE(app, "/break")
([&](const request& req) {
{
auto& ctx = app.get_context<FirstMW>(req);
auto& ctx = app.get_context<FirstMW<false>>(req);
ctx.v.push_back("handle");
}
@ -1465,7 +1527,92 @@ TEST_CASE("app_constructor")
app(OnlyMoveConstructor(1), SecondMW{});
} // app_constructor
TEST_CASE("middleware_cookieparser")
TEST_CASE("middleware_blueprint")
{
// Same logic as middleware_context, but middleware is added with blueprints
static char buf[2048];
App<FirstMW<true>, SecondMW<true>, ThirdMW<true>> app;
Blueprint bp1("a", "c1", "c1");
bp1.CROW_MIDDLEWARES(app, FirstMW<true>);
Blueprint bp2("b", "c2", "c2");
bp2.CROW_MIDDLEWARES(app, SecondMW<true>);
Blueprint bp3("c", "c3", "c3");
CROW_BP_ROUTE(bp2, "/")
.CROW_MIDDLEWARES(app, ThirdMW<true>)([&app](const crow::request& req) {
{
auto& ctx = app.get_context<FirstMW<true>>(req);
ctx.v.push_back("handle");
}
return "";
});
CROW_BP_ROUTE(bp3, "/break")
.CROW_MIDDLEWARES(app, SecondMW<true>, ThirdMW<true>)([&app](const crow::request& req) {
{
auto& ctx = app.get_context<FirstMW<true>>(req);
ctx.v.push_back("handle");
}
return "";
});
bp1.register_blueprint(bp3);
bp1.register_blueprint(bp2);
app.register_blueprint(bp1);
auto _ = app.bindaddr(LOCALHOST_ADDRESS).port(45451).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), 45451));
c.send(asio::buffer("GET /a/b/\r\n\r\n"));
c.receive(asio::buffer(buf, 2048));
c.close();
}
{
auto& out = test_middleware_context_vector;
CHECK(7 == out.size());
CHECK("1 before" == out[0]);
CHECK("2 before" == out[1]);
CHECK("3 before" == out[2]);
CHECK("handle" == out[3]);
CHECK("3 after" == out[4]);
CHECK("2 after" == out[5]);
CHECK("1 after" == out[6]);
}
{
asio::ip::tcp::socket c(is);
c.connect(asio::ip::tcp::endpoint(
asio::ip::address::from_string(LOCALHOST_ADDRESS), 45451));
c.send(asio::buffer("GET /a/c/break\r\n\r\n"));
c.receive(asio::buffer(buf, 2048));
c.close();
}
{
auto& out = test_middleware_context_vector;
CHECK(4 == out.size());
CHECK("1 before" == out[0]);
CHECK("2 before" == out[1]);
CHECK("2 after" == out[2]);
CHECK("1 after" == out[3]);
}
app.stop();
} // middleware_blueprint
TEST_CASE("middleware_cookieparser_parse")
{
static char buf[2048];
@ -1512,9 +1659,56 @@ TEST_CASE("middleware_cookieparser")
CHECK("val\"ue4" == value4);
}
app.stop();
} // middleware_cookieparser
} // middleware_cookieparser_parse
TEST_CASE("middleware_cookieparser_format")
{
using Cookie = CookieParser::Cookie;
auto valid = [](const std::string& s, int parts) {
return std::count(s.begin(), s.end(), ';') == parts - 1;
};
// basic
{
auto c = Cookie("key", "value");
auto s = c.dump();
CHECK(valid(s, 1));
CHECK(s == "key=value");
}
// max-age + domain
{
auto c = Cookie("key", "value")
.max_age(123)
.domain("example.com");
auto s = c.dump();
CHECK(valid(s, 3));
CHECK(s.find("key=value") != std::string::npos);
CHECK(s.find("Max-Age=123") != std::string::npos);
CHECK(s.find("Domain=example.com") != std::string::npos);
}
// samesite + secure
{
auto c = Cookie("key", "value")
.secure()
.same_site(Cookie::SameSitePolicy::None);
auto s = c.dump();
CHECK(valid(s, 3));
CHECK(s.find("Secure") != std::string::npos);
CHECK(s.find("SameSite=None") != std::string::npos);
}
// expires
{
auto tp = boost::posix_time::time_from_string("2000-11-01 23:59:59.000");
auto c = Cookie("key", "value")
.expires(boost::posix_time::to_tm(tp));
auto s = c.dump();
CHECK(valid(s, 2));
CHECK(s.find("Expires=Wed, 01 Nov 2000 23:59:59 GMT") != std::string::npos);
}
} // middleware_cookieparser_format
TEST_CASE("middleware_cors")
{
static char buf[5012];
@ -1939,8 +2133,10 @@ TEST_CASE("multipart")
TEST_CASE("send_file")
{
struct stat statbuf;
stat("tests/img/cat.jpg", &statbuf);
struct stat statbuf_cat;
stat("tests/img/cat.jpg", &statbuf_cat);
struct stat statbuf_badext;
stat("tests/img/filewith.badext", &statbuf_badext);
SimpleApp app;
@ -1957,6 +2153,12 @@ TEST_CASE("send_file")
res.end();
});
CROW_ROUTE(app, "/filewith.badext")
([](const crow::request&, crow::response& res) {
res.set_static_file_info("tests/img/filewith.badext");
res.end();
});
app.validate();
//File not found check
@ -1983,8 +2185,33 @@ 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.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
{
request req;
response res;
req.url = "/filewith.badext";
req.http_ver_major = 1;
CHECK_NOTHROW(app.handle(req, res));
CHECK(200 == res.code);
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
@ -2035,15 +2262,15 @@ TEST_CASE("stream_response")
// magic number is 102 (it's the size of the headers, which is how much this line below needs to read)
const size_t headers_bytes = 102;
while (received_headers_bytes < headers_bytes)
received_headers_bytes += c.receive(asio::buffer(buf, 2048));
received_headers_bytes += c.receive(asio::buffer(buf, 102));
received += received_headers_bytes - headers_bytes; //add any extra that might have been received to the proper received count
while (received < key_response_size)
{
asio::streambuf::mutable_buffers_type bufs = b.prepare(16384);
size_t n = c.receive(bufs);
size_t n(0);
n = c.receive(bufs);
b.commit(n);
received += n;
@ -2052,8 +2279,6 @@ TEST_CASE("stream_response")
is >> s;
CHECK(key_response.substr(received - n, n) == s);
//std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
app.stop();
@ -2069,10 +2294,10 @@ TEST_CASE("websocket")
SimpleApp app;
CROW_ROUTE(app, "/ws").websocket().onopen([&](websocket::connection&) {
connected = true;
CROW_LOG_INFO << "Connected websocket and value is " << connected;
})
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")
@ -2219,6 +2444,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")
{