Merge branch 'master' into ws_close

This commit is contained in:
Farook Al-Sammarraie 2022-05-14 18:56:39 +03:00 committed by GitHub
commit 9f99be55e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 9343 additions and 6958 deletions

View File

@ -1,8 +1,97 @@
---
BasedOnStyle: Google
BreakBeforeBraces: Mozilla
IndentWidth: '2'
TabWidth: '2'
BasedOnStyle: Mozilla
AccessModifierOffset: '-4'
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: 'false'
AlignEscapedNewlines: Left
AlignOperands: 'true' #AlignAfterOperator
AlignTrailingComments: 'true'
AllowShortBlocksOnASingleLine: 'true'
AllowShortCaseLabelsOnASingleLine: 'true'
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: 'true'
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: 'false'
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'false'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'true'
BinPackParameters: 'true'
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: 'true'
AfterClass: 'true'
AfterControlStatement: Always
AfterEnum: 'true'
AfterFunction: 'true'
AfterNamespace: 'true'
AfterObjCDeclaration: 'true'
AfterStruct: 'true'
AfterUnion: 'true'
AfterExternBlock: 'true'
BeforeCatch: 'true'
BeforeElse: 'true'
# BeforeLambdaBody: 'false'
# BeforeWhile: 'false'
IndentBraces: 'false'
SplitEmptyFunction: 'false'
SplitEmptyRecord: 'false'
SplitEmptyNamespace: 'false'
BreakBeforeTernaryOperators: 'false'
BreakBeforeBinaryOperators: 'false'
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
ColumnLimit: '0'
CompactNamespaces: 'false'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
ContinuationIndentWidth: '2'
Cpp11BracedListStyle: 'true'
FixNamespaceComments: 'true'
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 2
# CaseSensitive: true
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: '<[[:alnum:].]+>'
Priority: 4
- Regex: '.*'
Priority: 1
SortPriority: 0
IndentCaseLabels: 'true'
IndentWidth: '4'
IndentWrappedFunctionNames: 'true'
Language: Cpp
MaxEmptyLinesToKeep: '3'
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments: 'false'
SortIncludes: 'false'
SortUsingDeclarations: 'false'
SpaceAfterCStyleCast: 'false'
SpaceAfterLogicalNot: 'false'
SpaceAfterTemplateKeyword: 'false'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCpp11BracedList: 'false'
SpaceBeforeCtorInitializerColon: 'false'
SpaceBeforeInheritanceColon: 'true'
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: 'true'
SpaceInEmptyParentheses: 'false'
SpacesBeforeTrailingComments: '1'
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInContainerLiterals: 'false'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
#SpacesInLineCommentPrefix:
# Minimum: 1
# Maximum: -1
Standard: Cpp11
TabWidth: '4'
UseTab: Never
...

View File

@ -12,6 +12,12 @@ steps:
environment:
COVERALLS_REPO_TOKEN:
from_secret: coveralls_token
APP_ID:
from_secret: appid
APP_INSTALLATION:
from_secret: appins
PK:
from_secret: pk
commands:
- export DEBIAN_FRONTEND=noninteractive
@ -19,7 +25,7 @@ steps:
- export TRAVIS_JOB_ID=$DRONE_BUILD_NUMBER
- export COVERALLS_PULL_REQUEST=$DRONE_PULL_REQUEST
- apt-get -y update
- apt-get -y install libboost-all-dev doxygen mkdocs graphviz zlib1g-dev gcc clang make cmake python3 python3-pip git openssl libssl-dev
- apt-get -y install libboost-all-dev doxygen mkdocs graphviz zlib1g-dev gcc clang clang-format make cmake python3 python3-pip git openssl libssl-dev jq wget curl
- git clone https://github.com/CrowCpp/cpp-coveralls.git
- cd cpp-coveralls
- pip3 install . --no-input
@ -28,16 +34,21 @@ steps:
- export CXX=/usr/bin/g++
- mkdir build
- cd build
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
- cd ..
- coveralls --verbose --exclude-pattern .*/http_parser_merged.h --exclude-pattern .*/TinySHA1.hpp
- git clone https://github.com/CrowCpp/crow-clang-format.git
- cd crow-clang-format
- chmod +x crow-clang-format.sh get-access-token.sh make-jwt.sh
- ./crow-clang-format.sh
- cd ..
- export CC=/usr/bin/clang
- export CXX=/usr/bin/clang++
- mkdir build-clang
- cd build-clang
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
@ -66,7 +77,7 @@ steps:
- mkdir build
- cd build
- cmake --version
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
- cd ..
@ -74,7 +85,7 @@ steps:
- export CXX=/usr/bin/clang++
- mkdir build-clang
- cd build-clang
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
@ -122,7 +133,7 @@ steps:
- export CXX=/usr/bin/g++
- mkdir build
- cd build
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
- cd ..
@ -131,7 +142,7 @@ steps:
- export CXX=/usr/bin/clang++
- mkdir build-clang
- cd build-clang
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
- cd ..
@ -163,7 +174,7 @@ steps:
- mkdir build
- cd build
- cmake --version
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4
- cd ..
@ -171,7 +182,7 @@ steps:
- export CXX=/usr/bin/clang++
- mkdir build-clang
- cd build-clang
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
- make -j4
- ctest -V -j4

1
.github/FUNDING.yml vendored
View File

@ -1,3 +1,4 @@
# These are supported funding model platforms
open_collective: crow
github: CrowCpp

View File

@ -46,7 +46,7 @@ before_script:
- mkdir build
- cd build
- cmake --version
- cmake .. -DCROW_ENABLE_COMPRESSION=ON -DCROW_ENABLE_SSL=ON -DCROW_AMALGAMATE=ON
- cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON
script: make -j4 && ctest -V -j4

View File

@ -37,8 +37,8 @@ option(CROW_BUILD_TESTS "Build the tests in the project" ${CROW_I
option(CROW_AMALGAMATE "Combine all headers into one" OFF)
option(CROW_INSTALL "Add install step for Crow" ON )
option(CROW_ENABLE_SSL "Enable SSL capabilities (OpenSSL)" OFF)
option(CROW_ENABLE_COMPRESSION "Enable compression capabilities (ZLIB)" OFF)
# Possible values: ssl, compression
option(CROW_FEATURES "Enable features extending Crow's abilities" "")
#####################################
# Define Targets
@ -61,13 +61,13 @@ target_link_libraries(Crow
Threads::Threads
)
if(CROW_ENABLE_COMPRESSION)
if("compression" IN_LIST CROW_FEATURES)
find_package(ZLIB REQUIRED)
target_link_libraries(Crow INTERFACE ZLIB::ZLIB)
target_compile_definitions(Crow INTERFACE CROW_ENABLE_COMPRESSION)
endif()
if(CROW_ENABLE_SSL)
if("ssl" IN_LIST CROW_FEATURES)
find_package(OpenSSL REQUIRED)
target_link_libraries(Crow INTERFACE OpenSSL::SSL)
target_compile_definitions(Crow INTERFACE CROW_ENABLE_SSL)
@ -92,11 +92,11 @@ endif()
# Tests
if(NOT MSVC AND CROW_BUILD_TESTS)
if(NOT CROW_ENABLE_COMPRESSION)
message(STATUS "Compression tests are omitted. (Configure with CROW_ENABLE_COMPRESSION=ON to enable them)")
if(NOT "compression" IN_LIST CROW_FEATURES)
message(STATUS "Compression tests are omitted. (Configure with CROW_FEATURES containing 'compression' to enable them)")
endif()
if(NOT CROW_ENABLE_SSL)
message(STATUS "SSL tests are omitted. (Configure with CROW_ENABLE_SSL=ON to enable them)")
if(NOT "ssl" IN_LIST CROW_FEATURES)
message(STATUS "SSL tests are omitted. (Configure with CROW_FEATURES containing 'ssl' to enable them)")
else()
add_test(NAME ssl_test COMMAND ${CMAKE_CURRENT_BINARY_DIR}/tests/ssl/ssltest)
endif()
@ -142,3 +142,16 @@ set(CPACK_DEBIAN_PACKAGE_DEBUG OFF)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
include(CPack)
#####################################
# Uninstall Files
#####################################
if(NOT TARGET uninstall)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
IMMEDIATE @ONLY)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
endif()

View File

@ -38,7 +38,7 @@ PROJECT_NAME = Crow
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 0.3
PROJECT_NUMBER = 1.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@ -680,7 +680,7 @@ GENERATE_DEPRECATEDLIST= YES
# sections, marked by \if <section_label> ... \endif and \cond <section_label>
# ... \endcond blocks.
ENABLED_SECTIONS = $(RAPIDJSON_SECTIONS)
ENABLED_SECTIONS =
# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
# initial value of a variable or macro / define can have for it to appear in the
@ -2129,14 +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 = RAPIDJSON_DOXYGEN_RUNNING \
"RAPIDJSON_NAMESPACE_BEGIN=namespace rapidjson {" \
"RAPIDJSON_NAMESPACE_END=}" \
RAPIDJSON_REMOVEFPTR_(x)=x \
RAPIDJSON_ENABLEIF_RETURN(cond \
"returntype)=RAPIDJSON_REMOVEFPTR_ returntype" \
RAPIDJSON_DISABLEIF_RETURN(cond \
"returntype)=RAPIDJSON_REMOVEFPTR_ returntype"
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
@ -2145,7 +2141,7 @@ PREDEFINED = RAPIDJSON_DOXYGEN_RUNNING \
# definition found in the source code.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
EXPAND_AS_DEFINED = RAPIDJSON_NOEXCEPT
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
# remove all references to function-like macros that are alone on a line, have

View File

@ -1,7 +1,7 @@
BSD 3-Clause License
Copyright (c) 2014-2017, ipkn
2020-2021, CrowCpp
2020-2022, CrowCpp
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -29,4 +29,4 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The Crow logo and other graphic material (excluding third party logos) used are under exclusive Copyright (c) 2021, Farook Al-Sammarraie (The-EDev), All rights reserved.
The Crow logo and other graphic material (excluding third party logos) used are under exclusive Copyright (c) 2021-2022, Farook Al-Sammarraie (The-EDev), All rights reserved.

View File

@ -34,9 +34,12 @@ Available [here](https://crowcpp.org).
## Examples
#### Disclaimer
If you are using version v0.3, then you have to put `#define CROW_MAIN` at the top of one and only one source file.
#### Hello World
```cpp
#define CROW_MAIN
#include "crow.h"
int main()

View File

@ -4,11 +4,17 @@ include(CMakeFindDependencyMacro)
find_dependency(Boost 1.64 COMPONENTS system date_time)
find_dependency(Threads)
if(CROW_ENABLE_COMPRESSION)
set(CROW_INSTALLED_FEATURES "@CROW_FEATURES@")
if(NOT DEFINED CROW_FEATURES)
set(CROW_FEATURES ${CROW_INSTALLED_FEATURES})
endif()
if("compression" IN_LIST CROW_FEATURES)
find_dependency(ZLIB)
endif()
if(CROW_ENABLE_SSL)
if("ssl" IN_LIST CROW_FEATURES)
find_dependency(OpenSSL)
endif()
@ -21,12 +27,12 @@ get_target_property(_CROW_ICD Crow::Crow INTERFACE_COMPILE_DEFINITIONS)
list(REMOVE_ITEM _CROW_ILL "ZLIB::ZLIB" "OpenSSL::SSL")
list(REMOVE_ITEM _CROW_ICD "CROW_ENABLE_SSL" "CROW_ENABLE_COMPRESSION")
if(CROW_ENABLE_COMPRESSION)
if("compression" IN_LIST CROW_FEATURES)
list(APPEND _CROW_ILL "ZLIB::ZLIB")
list(APPEND _CROW_ICD "CROW_ENABLE_COMPRESSION")
endif()
if(CROW_ENABLE_SSL)
if("ssl" IN_LIST CROW_FEATURES)
list(APPEND _CROW_ILL "OpenSSL::SSL")
list(APPEND _CROW_ICD "CROW_ENABLE_SSL")
endif()

View File

@ -0,0 +1,22 @@
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
endif()
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
foreach(file ${files})
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
exec_program(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
if(NOT "${rm_retval}" STREQUAL 0)
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
endif()
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
endif()
endforeach()

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

@ -1,146 +0,0 @@
This page explains how to set Crow up for use with your project (***For versions 0.3+2 and lower***).
##Requirements
- C++ compiler with C++11 support.
- Crow's CI uses g++-9.3 and clang-7.0 running on AMD64 (x86_64) and ARM64v8
- boost library (1.64 or later).
- **(optional)** ZLib for HTTP Compression.
- **(optional)** CMake and Python3 to build tests and/or examples.
<br><br>
##Installing Requirements
!!! note
The Linux requirements are for developing and compiling a Crow application. Running a built application requires the actual libraries rather than just the development headers.
###Ubuntu
`sudo apt-get install build-essential libboost-all-dev`
###Arch
`sudo pacman -S python boost boost-libs`
###Non Debian based GNU/Linux
Use your package manager to install the following:
- GCC and G++ (or Clang and Clang++)
- Boost Development headers (sometimes part of the Boost package itself)
###OSX
`brew install boost`
###Windows
Microsoft Visual Studio 2019 (older versions not tested)
##Downloading
Either run `git clone https://github.com/crowcpp/crow.git` or download `crow_all.h` from the releases section. You can also download a zip of the project on github.
##Includes folder
1. Copy the `/includes` folder to your project's root folder.
2. Add `#!cpp #include "path/to/includes/crow.h"` to your `.cpp` file.
3. For any middlewares, add `#!cpp #include "path/to/includes/middlewares/some_middleware.h"`.
<br><br>
##Single header file
If you've downloaded `crow_all.h`, you can skip to step **4**.
1. Make sure you have python 3 installed.
2. Open a terminal (or `cmd.exe`) instance in `/path/to/crow/scripts`.
3. Run `python merge_all.py ../include crow_all.h` (replace `/` with `\` if you're on Windows).
4. Copy the `crow_all.h` file to where you put your libraries (if you don't know where this is, you can put it anywhere).
5. Add `#!cpp #include "path/to/crow_all.h"` to your `.cpp` file.
<br><br>
**Note**: All middlewares are included with the merged header file, if you would like to include or exclude middlewares use the `-e` or `-i` arguments.
<br><br>
##building via CLI
To build a crow Project, do the following:
###GCC (G++)
- Release: `g++ main.cpp -lpthread -lboost_system`.
- Debug: `g++ main.cpp -ggdb -lpthread -lboost_system -DCROW_ENABLE_DEBUG`.
- SSL: `g++ main.cpp -lssl -lcrypto -lpthread -lboost_system -DCROW_ENABLE_SSL`.
###Clang
- Release: `clang++ main.cpp -lpthread -lboost_system`.
- Debug: `clang++ main.cpp -g -lpthread -lboost_system -DCROW_ENABLE_DEBUG`.
- SSL: `clang++ main.cpp -lssl -lcrypto -lpthread -lboost_system -DCROW_ENABLE_SSL`.
###Microsoft Visual Studio 2019
The following guide will use `example_with_all.cpp` as the Crow application for demonstration purposes.
1. Generate `crow_all.h` following [Single header file](#single-header-file).
2. `git clone https://github.com/microsoft/vcpkg.git`
3. `.\vcpkg\bootstrap-vcpkg.bat`
4. `.\vcpkg\vcpkg integrate install`
5. Create empty Visual Studio project.
6. In solution explorer, right click the name of your project then click `Open Folder in File Explorer`.
7. Copy `crow_all.h`, `example_with_all.cpp`, `vcpkg.json` to opened folder.
8. Add `crow_all.h` to `Header Files` and `example_with_all.cpp` to `Source Files`.
9. In solution explorer, right click the name of your project then click `Properties`.
10. Under `vcpkg`, set `Use Vcpkg Manifest` to `Yes` and `Additional Options` to `--feature-flags="versions"`.
11. Set `Debug/Release` and `x64/x86`.
12. Run.
##building via CMake
Add the following to your `CMakeLists.txt`:
``` cmake linenums="1"
find_package(Threads)
find_package(ZLIB)
find_package(OpenSSL)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIR})
endif()
if (NOT CMAKE_BUILD_TYPE)
message(STATUS "No build type selected, default to Release")
set(CMAKE_BUILD_TYPE "Release")
endif()
if (MSVC)
set(Boost_USE_STATIC_LIBS "On")
find_package( Boost 1.70 COMPONENTS system thread regex REQUIRED )
else()
find_package( Boost 1.70 COMPONENTS system thread REQUIRED )
endif()
include_directories(${Boost_INCLUDE_DIR})
set(PROJECT_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
include_directories("${PROJECT_INCLUDE_DIR}")
```
!!!note
The last 2 lines are unnecessary if you're using `crow_all.h`.
##Building Crow tests and examples
Out-of-source build with CMake is recommended.
```
mkdir build
cd build
cmake ..
make
```
Running Cmake will create `crow_all.h` file and place it in the build directory.<br>
You can run tests with following command:
```
ctest -V
```
##Installing Crow
if you wish to use Crow globally without copying `crow_all.h` in your projects, you can install Crow on your machine with the procedure below.
```
mkdir build
cd build
cmake ..
make install
```
`make install` will copy `crow_all.h` automatically in your `/usr/local/include` thus making it available globally for use.<br>

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>
@ -32,14 +33,24 @@ You can also download the `crow_all.h` file and simply include that into your pr
<br><br>
### Installing from source
#### Using CMake
1. Download Crow's source code (Either through github's UI or by using<br> `git clone https://github.com/CrowCpp/Crow.git`).
1. Download Crow's source code (Either through Github's UI or by using<br> `git clone https://github.com/CrowCpp/Crow.git`).
2. Run `mkdir build` inside of crow's source directory.
3. Navigate to the new "build" directory and run the following:<br>
`cmake .. -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF`
4. Run `make install`.
!!!note
!!! note
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`.
<br>
#### Manually
Crow can be installed manually on your Linux computer.
@ -53,13 +64,15 @@ Crow can be installed manually on your Linux computer.
Copy Crow's `include` directory to the `/usr/local/include` directory.
##### Single header (crow_all.h)
!!!warning
!!! warning
`crow_all.h` is recommended only for small, possibly single source file projects, and ideally should not be installed on your system.
navigate to the `scripts` directory and run `./merge_all.py ../include crow_all.h`. This will generate a `crow_all.h` file that you can use in your projects.
!!!note
!!! note
You can also include or exclude middlewares from your `crow_all.h` by using `-i` or `-e` followed by the middleware header file names separated by a comma (e.g. `merge_all.py ../include crow_all.h -e cookie_parser` to exclude the cookie parser middleware).
## Compiling your project
### Using CMake
In order to get your CMake project to work with Crow, all you need are the following lines in your CMakeLists.txt:
@ -68,12 +81,16 @@ 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:
```
g++ main.cpp -lpthread
```
You can use arguments like `-DCROW_ENABLE_DEBUG`, `-DCROW_ENABLE_COMPRESSION -lz` for HTTP Compression, or `-DCROW_ENABLE_SSL -lssl` for HTTPS support, or even replace g++ with clang++.
!!!warning
!!! warning
If you're using a version of boost prior to 1.69, you'll need to add the argument `-lboost_system` in order for you Crow application to compile correctly.

View File

@ -16,7 +16,7 @@ You can generate your own single header file by navigating to the `scripts` fold
python3 merge_all.py ../include crow_all.h
```
This will generate a `crow_all.h` file which you can use in the following steps
!!!warning
!!! warning
`crow_all.h` is recommended only for small, possibly single source file projects. For larger projects, it is advised to use the multi-header version.
@ -44,6 +44,10 @@ This will generate a `crow_all.h` file which you can use in the following steps
## Building Crow's tests/examples
!!! note
This tutorial can be used for Crow projects built with CMake as well
1. Download and install [Homebrew](https://brew.sh).
2. Run `brew install cmake boost` in your terminal.
3. Get Crow's source code (the entire source code).
@ -52,6 +56,20 @@ This will generate a `crow_all.h` file which you can use in the following steps
2. `cd build`
3. `cmake ..`
4. `make -j12`
!!!note
!!! note
You can add options like `-DCROW_ENABLE_SSL`, `-DCROW_ENABLE_COMPRESSION`, or `-DCROW_AMALGAMATE` to `3.c` 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:
```
g++ main.cpp -lpthread
```
!!! note
You'll need to install GCC via `brew install gcc`. the Clang compiler should be part of XCode or XCode command line tools.
You can use arguments like `-DCROW_ENABLE_DEBUG`, `-DCROW_ENABLE_COMPRESSION -lz` for HTTP Compression, or `-DCROW_ENABLE_SSL -lssl` for HTTPS support, or even replace g++ with clang++.
!!! warning
If you're using a version of boost prior to 1.69, you'll need to add the argument `-lboost_system` in order for you Crow application to compile correctly.

View File

@ -5,7 +5,7 @@ Here's how you can install Crow on your Windows machine.
Crow can be simply installed through VCPKG using the command `vcpkg install crow`
### Manually (source or release)
#### Microsoft Visual Studio 2019 and VCPKG
#### Microsoft Visual Studio and VCPKG
The following guide will use `example_with_all.cpp` as the Crow application for demonstration purposes. VCPKG will be used only to install Crow's dependencies.
1. Generate `crow_all.h` by navigating to the `scripts` folder and running `python3 merge_all.py ..\include crow_all.h`.

View File

@ -1,11 +1,11 @@
This page shows how you can get started with a simple hello world application.
## 1. Include
Starting with an empty `main.cpp` file, first add `#!cpp #define CROW_MAIN` then `#!cpp #include "crow.h"` or `#!cpp #include "crow_all.h"` if you're using the single header file.
Starting with an empty `main.cpp` file, first add `#!cpp #include "crow.h"` or `#!cpp #include "crow_all.h"` if you're using the single header file.
!!! note
If you're using multiple C++ source files make sure to have `CROW_MAIN` defined only in your main source file.
If you are using version v0.3, then you have to put `#!cpp #define CROW_MAIN` at the top of one and only one source file.
## 2. App declaration
Next Create a `main()` and declare a `#!cpp crow::SimpleApp` inside, your code should look like this
@ -39,7 +39,6 @@ Please note that the `port()` and `multithreaded()` methods aren't needed, Thoug
Once you've followed all the steps above, your code should look similar to this
``` cpp linenums="1"
#define CROW_MAIN //let the compiler know this is your main cpp file
#include "crow.h"
//#include "crow_all.h"

View File

@ -1,5 +1,5 @@
A Crow app defines an interface to allow the developer access to all the different parts of the framework, without having to manually deal with each one.<br><br>
An app allows access to the http server (for handling connections), router (for handling URLs and requests), Middlewares (for extending Crow), amoung many others.<br><br>
An app allows access to the HTTP server (for handling connections), router (for handling URLs and requests), Middlewares (for extending Crow), among many others.<br><br>
Crow has 2 different app types:
@ -23,6 +23,11 @@ app.bindaddr(192.168.1.2)
.multithreaded()
.run();
```
!!! note
The `run()` method is blocking. To run a Crow app asynchronously `run_async()` should be used instead.
<br><br>
For more info on middlewares, check out [this page](../middleware).<br><br>

71
docs/guides/auth.md Normal file
View File

@ -0,0 +1,71 @@
While Crow doesn't directly support HTTP authentication, it does provide all the tools you need to build your own. This tutorial will show you how to setup basic and token authentication using Crow.
## Shared information
Every way boils down to the same basic flow:
- The handler calls a verification function.
- The handler provides a `request` and \<optionally\> a `response`.
- The function returns a `bool` or `enum` status.
- Handler either continues or stops executing based on the returned status.
- Either the function or handler modify and `end()` the `response` in case of failure.
For the purposes of this tutorial, we will assume that the verification function is defined as `#!cpp bool verify(crow::request req, crow::response res)`
## Basic Auth
Basic HTTP authentication requires the client to send the Username and Password as a single string, separated by a colon (':') and then encoded as Base64. This data needs to be placed in the `Authorization` header of the request. A sample header using the credentials "Username" and "Password" would look like this: `Authorization: Basic VXNlcm5hbWU6UGFzc3dvcmQ=`.<br><br>
We don't need to worry about creating the request, we only need to extract the credentials from the `Authorization` header and verify them.
!!! note
There are multiple ways to verify the credentials. Most involve checking the username in a database, then checking a hash of the password against the stored password hash for that username. This tutorial will not go over them
<br>
To do this we first need to get the `Authorization` header as a string by using the following code:
```cpp
std::string myauth = req.get_header_value("Authorization");
```
<br>
Next we need to isolate our encoded credentials and decode them as follows:
```cpp
std::string mycreds = myauth.substr(6);
std::string d_mycreds = crow::utility::base64decode(mycreds, mycreds.size());
```
<br>
Now that we have our `username:password` string, we only need to separate it into 2 different strings and verify their validity:
```cpp
size_t found = d_mycreds.find(':');
std::string username = d_mycreds.substr(0, found);
std::string password = d_mycreds.substr(found+1);
/*Verify validity of username and password here*/
return true; //or false if the username/password are invalid
```
## Token Auth
Tokens are some form of unique data that a server can provide to a client in order to verify the client's identity later. While on the surface level they don't provide more security than a strong password, they are designed to be less valuable by being *temporary* and providing *limited access*. Variables like expiration time and access scopes are heavily reliant on the implementation however.<br><br>
### Access Tokens
The kind of the token itself can vary depending on the implementation and project requirements: Many services use randomly generated strings as tokens. Then compare them against a database to retrieve the associated user data. Some services however prefer using data bearing tokens. One example of the latter kind is JWT, which uses JSON strings encoded in Base64 and signed using a private key or an agreed upon secret. While this has the added hassle of signing the token to ensure that it's not been tampered with. It does allow for the client to issue tokens without ever needing to present a password or contact a server. The server would simply be able to verify the signature using the client's public key or secret.<br><br>
### Using an Access Token
Authenticating with an access token usually involves 2 stages: The first being acquiring the access token from an authority (either by providing credentials such as a username and a password to a server or generating a signed token). The scope of the token (what kind of information it can read or change) is usually defined in this step.<br><br>
The second stage is simply presenting the Token to the server when requesting a resource. This is even simpler than using basic authentication. All the client needs to do is provide the `Authorization` header with a keyword (usually `Bearer`) followed by the token itself (for example: `Authorization: Bearer ABC123`). Once the client has done that the server will need to acquire this token, which can easily be done as follows:<br>
```cpp
std::string myauth = req.get_header_value("Authorization");
std::string mycreds = myauth.substr(7); // The length can change based on the keyword used
/*Verify validity of the token here*/
return true; //or false if the token is invalid
```
<br>
The way of verifying the token is largely up to the implementation, and involves either Bearer token decoding and verification, or database access, neither of which is in this tutorial's scope.<br><br>
### Refresh Tokens
Some services may choose to provide a refresh token alongside the access token. This token can be used to request a new access token if the existing one has expired. It provides convenience and security in that it makes it possible to acquire new access tokens without the need to expose a password. The downside however is that it can allow a malicious entity to keep its access to a compromised account. As such refresh tokens need to be handled with care, kept secure, and always invalidated as soon as a client logs out or requests a new access token.
## Sessions
While Crow does not provide built in support for user sessions, a community member was kind enough to provide their own implementation on one of the related issue, their comment along with the code is available [here](https://github.com/CrowCpp/Crow/issues/144#issuecomment-860384771) (Please keep in mind that while we appreciate all efforts to push Crow forward, we cannot provide support for this implementation unless it becomes part of the core project).

9
docs/guides/base64.md Normal file
View File

@ -0,0 +1,9 @@
## Encoding
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
<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,7 +1,6 @@
!!!Warning
<span class="tag">[:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0)</span>
This feature is currently only available on the "master" branch.
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>
@ -14,9 +13,9 @@ Blueprints let you do the following:<br><br>
You can define routes in a blueprint, similarly to how `#!cpp CROW_ROUTE(app, "/xyz")` works, you can use `#!cpp CROW_BP_ROUTE(blueprint, "/xyz")` to define a blueprint route.
### Define a Prefix
Blueprints can have a prefix assigned to them. This can be done when creating a new blueprint as in `#!cpp crow::blueprint bp("prefix");`. This prefix will be applied to all routes belonging to the blueprint. truning a route such as `/crow/rocks` into `/prefix/crow/rocks`.
Blueprints can have a prefix assigned to them. This can be done when creating a new blueprint as in `#!cpp crow::blueprint bp("prefix");`. This prefix will be applied to all routes belonging to the blueprint, turning a route such as `/crow/rocks` into `/prefix/crow/rocks`.
!!!Warning
!!! Warning
Unlike routes, blueprint prefixes should contain no slashes.
@ -24,7 +23,7 @@ Blueprints can have a prefix assigned to them. This can be done when creating a
### Use a custom Static directory
Blueprints let you define a custom static directory (relative to your working directory). This can be done by initializing a blueprint as `#!cpp crow::blueprint bp("prefix", "custom_static");`. This does not have an effect on `#!cpp set_static_file_info()`, it's only for when you want direct access to a file.
!!!note
!!! note
Currently changing which endpoint the blueprint uses isn't possible, so whatever you've set in `CROW_STATIC_ENDPOINT` (default is "static") will be used. Making your final route `/prefix/static/filename`.
@ -32,12 +31,12 @@ Blueprints let you define a custom static directory (relative to your working di
### Use a custom Templates directory
Similar to static directories, You can set a custom templates directory (relative to your working directory). To do this you initialize the blueprint as `#!cpp crow::blueprint bp("prefix", "custom_static", "custom_templates");`. Any routes defined for the blueprint will use that directory when calling `#!cpp crow::mustache::load("filename.html")`.
!!!note
!!! note
If you want to define a custom templates directory without defining a custom static directory, you can pass the static directory as an empty string. Making your constructor `#!cpp crow::blueprint bp("prefix", "", "custom_templates");`.
### Define a custom Catchall route
You can define a custom catchall route for a blueprint by calling `#!cpp CROW_BP_CATCHALL_ROUTE(blueprint)`. This causes any requests with a URL starting with `/prefix` and no route found to call the blueprint's catchall route. if no catchall route is defined, Crow will default to either the parent blueprint or the app's catchall route.
You can define a custom catchall route for a blueprint by calling `#!cpp CROW_BP_CATCHALL_ROUTE(blueprint)`. This causes any requests with a URL starting with `/prefix` and no route found to call the blueprint's catchall route. If no catchall route is defined, Crow will default to either the parent blueprint or the app's catchall route.
### Register other Blueprints
Blueprints can also register other blueprints. This is done through `#!cpp blueprint.register_blueprint(blueprint_2);`. The child blueprint's routes become `/prefix/prefix_2/abc/xyz`.

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.
@ -11,9 +13,9 @@ HTTP compression is by default disabled in crow. Do the following to enable it:
3<sup>rd</sup> point is not needed for MSVC or CMake projects using `Crow::Crow` since `vcpckg.json` and Crow's target already include zlib as a dependency.
For the compression algorim you can use `crow::compression::algorithm::DEFLATE` or `crow::compression::algorithm::GZIP`.<br>
For the compression algorithm you can use `crow::compression::algorithm::DEFLATE` or `crow::compression::algorithm::GZIP`.<br>
And now your HTTP responses will be compressed.
## Websocket Compression
Crow currently does not support Websocket compression.<br>
Feel free to discuss the subject with us on Github if you're feeling adventurous and want to try to implement it. We appreciate all the help.
Feel free to discuss the subject with us on GitHub if you're feeling adventurous and want to try to implement it. We appreciate all the help.

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

@ -25,13 +25,19 @@ For more info on read values go [here](/reference/classcrow_1_1json_1_1rvalue.ht
## wvalue
JSON write value, used for creating, editing and converting JSON to a string.<br><br>
!!!note
!!! note
setting a `wvalue` to object type can be done by simply assigning a value to whatever string key you like, something like `#!cpp wval["key1"] = val1;`. Keep in mind that val1 can be any of the above types.
A `wvalue` can be treated as an object or even a list (setting a value by using `json[3] = 32` for example). Please note that this will remove the data in the value if it isn't of List type.<br><br>
A `wvalue` can be treated as an object or even a list (setting a value by using `json[3] = 32` for example). Please note that this will remove the data in the value if it isn't of List type.
Additionally, a `wvalue` can be initialized as an object using an initializer list, an example object would be `wvalue x = {{"a", 1}, {"b", 2}}`. Or as a list using `wvalue x = json::wvalue::list({1, 2, 3})`, lists can include any type that `wvalue` supports.
!!! warning
JSON does not allow floating point values like `NaN` or `INF`, Crow will output `null` instead of `NaN` or `INF` when converting `wvalue` to a string. (`{"Key": NaN}` becomes `{"Key": null}`)
<br><br>
Additionally, a `wvalue` can be initialized as an object using an initializer list, an example object would be `wvalue x = {{"a", 1}, {"b", 2}}`. Or as a list using `wvalue x = json::wvalue::list({1, 2, 3})`, lists can include any type that `wvalue` supports.<br><br>
An object type `wvalue` uses `std::unordered_map` by default, if you want to have your returned `wvalue` key value pairs be sorted (using `std::map`) you can add `#!cpp #define CROW_JSON_USE_MAP` to the top of your program.<br><br>
@ -39,6 +45,6 @@ A JSON `wvalue` can be returned directly inside a route handler, this will cause
For more info on write values go [here](../../reference/classcrow_1_1json_1_1wvalue.html).
!!!note
!!! note
Crow's json exceptions can be disabled by using the `#!cpp #define CROW_JSON_NO_ERROR_CHECK` macro. This should increase the program speed with the drawback of having unexpected behavious when used incorrectly (e.g. by attempting to parse an invalid json object).

View File

@ -1,5 +1,9 @@
Crow comes with a simple and easy to use logging system.<br><br>
!!! note
Currently Crow's Logger is not linked to the Crow application, meaning if an executable has more than one Crow application they'll be sharing any variables or classes relating to Logging.
## Setting up logging level
You can set up the level at which crow displays logs by using the app's `loglevel(crow::LogLevel)` method.<br><br>
@ -14,7 +18,43 @@ The available log levels are as follows (please not that setting a level will al
To set a logLevel, just use `#!cpp app.loglevel(crow::LogLevel::Warning)`, This will not show any debug or info logs. It will however still show error and critical logs.<br><br>
Please note that setting the Macro `CROW_ENABLE_DEBUG` during compilation will also set the log level to `Debug`.
!!! note
Setting the Macro `CROW_ENABLE_DEBUG` during compilation will also set the log level to `Debug` (unless otherwise set using `loglevel()`).
## Writing a log
Writing a log is as simple as `#!cpp CROW_LOG_<LOG LEVEL> << "Hello";` (replace&lt;LOG LEVEL&gt; with the actual level in all caps, so you have `CROW_LOG_WARNING`).
!!! note
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
<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>
Once you have your custom logger, you need to set it via `#!cpp crow::logger::setHandler(&MyLogger);`. Here's a full example:<br>
```cpp
class CustomLogger : public crow::ILogHandler {
public:
CustomLogger() {}
void log(std::string message, crow::LogLevel /*level*/) {
// "message" doesn't contain the timestamp and loglevel
// prefix the default logger does and it doesn't end
// in a newline.
std::cerr << message << std::endl;
}
};
int main(int argc, char** argv) {
CustomLogger logger;
crow::logger::setHandler(&logger);
crow::SimpleApp app;
CROW_ROUTE(app, "/")([]() { return "Hello"; });
app.run();
}
```

View File

@ -1,29 +1,96 @@
Any middleware requires following 3 members:
## struct context
Storing data for the middleware; can be read from another middleware or handlers
Middleware is used for altering and inspecting requests before and after the handler call.
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 `after_handle` method, which is called after the handler.
## before_handle
Called before handling the request.<br>
If `res.end()` is called, the operation is halted. (`after_handle` will still be called)<br>
2 signatures:<br>
`#!cpp void before_handle(request& req, response& res, context& ctx)`
if you only need to access this middleware's context.
``` cpp
template <typename AllContext>
void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
```
You can access other middlewares' context by calling `#!cpp all_ctx.template get<MW>()`<br>
`#!cpp ctx == all_ctx.template get<CurrentMiddleware>()`
There are two possible signatures for before_handle
1. if you only need to access this middleware's context.
```cpp
void before_handle(request& req, response& res, context& ctx)
```
2. To get access to other middlewares context
``` cpp
template <typename AllContext>
void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
{
auto other_ctx = all_ctx.template get<OtherMiddleware>();
}
```
## after_handle
Called after handling the request.<br>
There are two possible signatures for after_handle
`#!cpp void after_handle(request& req, response& res, context& ctx)`
``` cpp
template <typename AllContext>
void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
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`.
```cpp
struct LocalMiddleware : crow::ILocalMiddleware
{
...
```
After this, you can enable it for specific handlers.
```cpp
CROW_ROUTE(app, "/with_middleware")
.CROW_MIDDLEWARES(app, LocalMiddleware)
([]() {
return "Hello world!";
});
```
## Examples
A local middleware that can be used to guard admin handlers
```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)
{}
};
```
<br><br>
This was pulled from `cookie_parser.h`. Further Editing required, possibly use parts of [@ipkn's wiki page](https://github.com/ipkn/crow/wiki/Middleware).

View File

@ -1,9 +1,12 @@
**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>
Such an approach allows a request to contain multiple different pieces of data with potentially conflicting data types in a single response payload.<br>
It is typically used either in html forms, or when uploading multiple files.<br><br>
It is typically used either in HTML forms, or when uploading multiple files.<br><br>
## How multipart messages work
The structure of a multipart request is typically consistent of:<br>
- A Header: Typically `multipart/form-data;boundary=<boundary>`, This defines the HTTP message as being multipart, as well as defining the separator used to distinguish the different parts.<br>
@ -13,10 +16,17 @@ The structure of a multipart request is typically consistent of:<br>
- Value
- `--<boundary>--`<br><br>
## Multipart messages in Crow
Crow supports multipart requests and responses though `crow::multipart::message`.<br>
A message can be created either by defining the headers, boundary, and individual parts and using them to create the message. or simply by reading a `crow::request`.<br><br>
Once a multipart message has been made, the individual parts can be accessed throught `mpmes.parts`, `parts` is an `std::vector`, so accessing the individual parts should be straightforward.<br>
In order to access the individual part's name or filename, something like `#!cpp mpmes.parts[0].headers[0].params["name"]` sould do the trick.<br><br>
Once a multipart message has been made, the individual parts can be accessed throughout `msg.parts`, `parts` is an `std::vector`.<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>
The message's individual body parts can be accessed by name using `msg.get_part_by_name("part-name")`.<br><br>
For more info on Multipart messages, go [here](../../reference/namespacecrow_1_1multipart.html)

View File

@ -10,7 +10,7 @@ We advise that you set crow up behind some form of reverse proxy if you plan on
## Apache2
Assuming you have both Apache2 and the modules [proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html), [proxy_http](https://httpd.apache.org/docs/2.4/mod/mod_proxy_http.html), [proxy_html](https://httpd.apache.org/docs/2.4/mod/mod_proxy_html.html) (if you plan on serving html pages), and [proxy_wstunnel](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) (if you plan on using websockets). You will need to enable those modules, which you can do using the following commands:
Assuming you have both Apache2 and the modules [proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html), [proxy_http](https://httpd.apache.org/docs/2.4/mod/mod_proxy_http.html), [proxy_html](https://httpd.apache.org/docs/2.4/mod/mod_proxy_html.html) (if you plan on serving HTML pages), and [proxy_wstunnel](https://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html) (if you plan on using websockets). You will need to enable those modules, which you can do using the following commands:
```sh
a2enmod proxy

View File

@ -1,30 +1,36 @@
A query string is the part of the url that comes after a `?` character, it is usually formatted as `key=value&otherkey=othervalue`.
A query string is the part of the URL that comes after a `?` character, it is usually formatted as `key=value&otherkey=othervalue`.
<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
`crow::request::url_params` is a const value, therefore for pop (also pop_list and pop_dict) to work, a copy needs to be made.
## get_list(name)
A url can be `http://example.com?key[]=value1&key[]=value2&key[]=value3`. Using `get_list("key")` on such a url returns an `std::vector<std::string>` containing `[value1, value2, value3]`.<br><br>
A URL can be `http://example.com?key[]=value1&key[]=value2&key[]=value3`. Using `get_list("key")` on such a URL returns an `std::vector<std::string>` containing `[value1, value2, value3]`.<br><br>
`#!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

@ -2,7 +2,7 @@ Routes define what happens when your client connects to a certain URL.<br>
## Macro
`CROW_ROUTE(app, url)`<br>
Can be replaced with `#!cpp app.route<crow::black_magick::get_parameter_tag(url)>(url)` or `#!cpp app.route_dynamic(url)` if you're using VS2013 or want runtime url evaluation. Although this usage is **NOT** recommended.
Can be replaced with `#!cpp app.route<crow::black_magick::get_parameter_tag(url)>(url)` or `#!cpp app.route_dynamic(url)` if you're using VS2013 or want runtime URL evaluation. Although this usage is **NOT** recommended.
## App
Which app class to assign the route to.
## Path (URL)
@ -10,7 +10,7 @@ Which relative path is assigned to the route.<br>
Using `/hello` means the client will need to access `http://example.com/hello` in order to access the route.<br>
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 nothing 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:
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
CROW_ROUTE(app, "/add/<int>/<int>")
([](int a, int b)
@ -33,7 +33,7 @@ Basically a piece of code that gets executed whenever the client calls the assoc
### Request
Handlers can also use information from the request by adding it as a parameter `#!cpp ([](const crow::request& req){...})`.<br><br>
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>
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>
For more information on `crow::request` go [here](../../reference/structcrow_1_1request.html).<br><br>
@ -48,24 +48,26 @@ For more information on `crow::response` go [here](../../reference/structcrow_1_
### 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>
The main return type is `std::string`. although you could also return a `crow::json::wvalue` or `crow::multipart::message` directly.<br><br>
The main return type is `std::string`, although you could also return a `crow::json::wvalue` or `crow::multipart::message` directly.<br><br>
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:
Your class should look like the following:
```cpp
class a : public crow::returnable
{
a() : returnable("text/plain"){};
...
...
...
std::string dump() override
{
return this.as_string();
@ -75,14 +77,16 @@ class a : public crow::returnable
<br><br>
## Response codes
**Introduced in: `master`**<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)`
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
!!! note
For versions higher than 0.3 (excluding patches), Catchall routes handle 404 and 405 responses. The default response will contain the code 404 or 405.

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.
@ -6,10 +8,19 @@ Crow supports returning Static files as responses in 2 ways.
## Implicit
Crow implicitly returns any static files placed in a `static` directory and any subdirectories, as long as the user calls the endpoint `/static/path/to/file`.<br><br>
The static folder or endpoint can be changed by defining the macros `CROW_STATIC_DIRECTORY "alternative_directory/"` and `CROW_STATIC_ENDPOINT "/alternative_endpoint/<path>"`.<br>
static directory changes the directory in the server's filesystem, while the endpoint changes the URL that the client needs to access.
static directory changes the directory in the server's file system, while the endpoint changes the URL that the client needs to access.
## Explicit
You can directly return a static file by using the `crow::response` method `#!cpp response.set_static_file_info("path/to/file");`. The path is relative to the executable unless preceded by `/`, then it is an absolute path.<br>
Please keep in mind that using the `set_static_file_info` method does invalidate any data already in your response body.<br><br>
You can directly return a static file by using the `crow::response` method `#!cpp response.set_static_file_info("path/to/file");`. The path is relative to the working directory.
**Note**: Crow sets the `content-type` header automatically based on the file's extension, if an extension is unavailable or undefined, Crow uses `text/plain`, if you'd like to explicitly set a `content-type`, use `#!cpp response.set_header("content-type", "mime/type");` **AFTER** calling `set_static_file_info`.
!!! Warning
The path to the file is sanitized by default. it should be fine for most circumstances but if you know what you're doing and need the sanitizer off you can use `#!cpp response.set_static_file_info_unsafe("path/to/file")` instead.
!!! note
Crow sets the `content-type` header automatically based on the file's extension, if an extension is unavailable or undefined, Crow uses `text/plain`, if you'd like to explicitly set a `content-type`, use `#!cpp response.set_header("content-type", "mime/type");` **AFTER** calling `set_static_file_info`.
!!! note
Please keep in mind that using the `set_static_file_info` method means any data already in your response body is ignored and not sent to the client.

View File

@ -1,7 +1,11 @@
Templating is when you return an html page with custom data. You can probably tell why that's useful.<br><br>
Templating is when you return an HTML page with custom data. You can probably tell why that's useful.<br><br>
Crow supports [mustache](http://mustache.github.io) for templates through its own implementation `crow::mustache`.<br><br>
!!! note
Currently Crow's Mustache implementation is not linked to the Crow application, meaning if an executable has more than one Crow application they'll be sharing any variables or classes relating to template loading and compiling.
## Components of mustache
There are 2 components of a mustache template implementation:
@ -12,13 +16,37 @@ There are 2 components of a mustache template implementation:
### Page
The HTML page (including the mustache tags). It is usually loaded into `crow::mustache::template_t`. It needs to be placed in the *templates directory* which should be directly inside the current working directory of the crow executable.<br><br>
The templates directory is usually called `templates`, but can be adjusted per Route (via `crow::mustache::set_base("new_templates_directory")`), per [Blueprint](../blueprints), or globally (via `crow::mustache::set_global_base("new_templates_directory"")`).<br><br>
For more information on how to formulate a template, see [this mustache manual](http://mustache.github.io/mustache.5.html).
### Context
A JSON object containing the tags as keys and their values. `crow::mustache::context` is actually a [crow::json::wvalue](../json#wvalue).
A JSON object containing the tags as keys and their values. `crow::mustache::context` is actually a [crow::json::wvalue](../json#wvalue).<br><br>
!!! note
`crow::mustache::context` can take a C++ lambda as a value. The lambda needs to take a string as an argument and return a string, such as `#!cpp ctx[lmd] = [&](std::string){return "Hello World";};`.
!!! note
The string returned by the lamdba can contain mustache tags, Crow will parse it as any normal template string.
## Returning a template
To return a mustache template, you need to load a page using `#!cpp auto page = crow::mustache::load("path/to/template.html");`, keep in mind that the path is relative to the templates directory.<br>
You also need to set up the context by using `#!cpp crow::mustache::context ctx;`. Then you need to assign the keys and values, this can be done the same way you assign values to a json write value (`ctx["key"] = value;`).<br>
To return a mustache template, you need to load a page using `#!cpp auto page = crow::mustache::load("path/to/template.html");`. Or just simply load a string using `#!cpp auto page = crow::mustache::compile("my mustache {{value}}");`. Keep in mind that the path is relative to the templates directory.
!!! note
You can also use `#!cpp auto page = crow::mustache::load_text("path/to/template.html");` if you want to load a template without mustache processing.
!!! Warning
The path to the template is sanitized by default. it should be fine for most circumstances but if you know what you're doing and need the sanitizer off you can use `#!cpp crow::mustache::load_unsafe()` instead.
<br>
You also need to set up the context by using `#!cpp crow::mustache::context ctx;`. Then you need to assign the keys and values, this can be done the same way you assign values to a JSON write value (`ctx["key"] = value;`).<br>
With your context and page ready, just `#!cpp return page.render(ctx);`. This will use the context data to return a filled template.<br>
Alternatively you could just render the page without a context using `#!cpp return page.render();`.
!!! note
`#!cpp page.render();` returns a crow::returnable class in order to set the `Content-Type` header. to get a simple string, use `#!cpp page.render_string()` instead.

View File

@ -1,7 +1,7 @@
Unit tests can be written in 2 ways for a Crow application.<br><br>
## The handler method
Crow Allows users to handle requests that may not come from the network. This is done by calling the `handle(req, res)` method and providing a request and response objects. Which causes crow to identify and run the appropriate handler, returning the resulting response.
Crow allows users to handle requests that may not come from the network. This is done by calling the `handle(req, res)` method and providing a request and response objects. Which causes crow to identify and run the appropriate handler, returning the resulting response.
```cpp linenums="1"
CROW_ROUTE(app, "/place")
@ -74,4 +74,4 @@ Finally check the result against the expected one.
!!! warning
Be absolutely sure that the line `app.stop()` runs, whether the test fails or succeedes. Not running it WILL CAUSE OTHER TESTS TO FAIL AND THE TEST TO HANG UNTIL THE PROCESS IS TERMINATED.
Be absolutely sure that the line `app.stop()` runs, whether the test fails or succeeds. Not running it WILL CAUSE OTHER TESTS TO FAIL AND THE TEST TO HANG UNTIL THE PROCESS IS TERMINATED.

View File

@ -4,7 +4,12 @@ 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):
- `#!cpp onaccept([&](const crow::request&){handler code goes here})` (This handler has to return bool)
!!! 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.
- `#!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})`

View File

@ -7,10 +7,10 @@
<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 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,8 +135,16 @@ 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>
@ -144,19 +152,23 @@ code{
<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>
@ -169,8 +181,7 @@ code{
<h2 style="text-align: center;">Easy to get started</h3>
</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">#define CROW_MAIN</span>
<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>
@ -221,15 +232,7 @@ code{
<h3 style="text-align:center;">Crow is provided free of charge courtesy of everyone who is donating their money, time, and expertise to keep it going.<h3>
<h3 style="text-align:center;">Help us make something great!</h3>
<dev class="sbuttons">
<h2 style="text-align:center;">Sponsors</h2>
<object type="image/svg+xml" data="https://opencollective.com/crow/tiers/sponsor.svg?avatarHeight=128&button=false"></object><br>
<h2 style="text-align:center;">Boosters</h2>
<object type="image/svg+xml" data="https://opencollective.com/crow/tiers/booster.svg?avatarHeight=64&button=false"></object><br>
<h2 style="text-align:center;">Backers</h2>
<object type="image/svg+xml" data="https://opencollective.com/crow/tiers/backer.svg?avatarHeight=32&button=false"></object><br>
<h2 style="text-align:center;">Donators</h2>
<object type="image/svg+xml" data="https://opencollective.com/crow/tiers/donation.svg?avatarHeight=32&button=false"></object><br>
<dev class="sbuttons" id="contributors">
</dev>
<dev class="sbuttons">
<a href="https://opencollective.com/crow" title="Crow - OpenCollective" class="md-button crow-button">Fund Crow</a>
@ -245,7 +248,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>
@ -253,7 +256,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>
@ -261,7 +264,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>
@ -269,7 +272,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>
@ -277,7 +280,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>
@ -290,13 +293,181 @@ 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>
<script>
let text = "";
function convertRemToPixels(rem) {
return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
}
function makeCard(name, img_url, ref, size = 6)
{
let finalName = fixLong(name);
if (img_url == null)
{
img_url = AvatarImage(name);
}
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)
{
if (name.length > 12)
{
name = name.slice(0,9);
name += "...";
}
return name;
}
function GetColor(name)
{
var r = 0;
var g = 0;
var b = 0;
if (name.length >= 3)
{
var i = 0;
for (i; i < name.length/3; i++)
{
r += name[i].charCodeAt();
}
r %= 256;
for (i; i < name.length*2/3; i++)
{
g += name[i].charCodeAt();
}
g %= 256;
for (i; i < name.length; i++)
{
b += name[i].charCodeAt();
}
b %= 256;
console.log(`rgb(${r},${g},${b})`);
return `rgb(${r},${g},${b})`;
}
return "rgb(0,0,0)";
}
function AvatarImage(name)
{
var letters = name.includes(' ') ? name[0] + name.split(' ').slice(-1)[0][0] : name.slice(0,2);
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
var size = convertRemToPixels(3);
var color = GetColor(name);
canvas.width = size;
canvas.height = size;
context.font = Math.round(canvas.width / 2) + "px Arial";
context.textAlign = "center";
// Setup background and front color
context.fillStyle = color;
context.fillRect(0, 0, canvas.width, canvas.height);
// Check the color brightness (0-255)
color = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/);
r = color[1];
g = color[2];
b = color[3];
hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
context.fillStyle = hsp > 150 ? "#333" : "#EEE"; // 150 was reached by trial and error
context.fillText(letters, size / 2, size / 1.5);
// Set image representation in default format (png)
dataURI = canvas.toDataURL();
// Dispose canvas element
canvas = null;
return dataURI;
}
async function getGHData()
{
let x = await fetch("https://api.github.com/repos/crowcpp/crow/contributors?per_page=100");
let y = await x.json();
text += "<a href=\"https://github.com/CrowCpp/Crow\"><h2>Code Contributors</h2></a><section> ";
if (y.length != null)
{
for(var i = 0; i < y.length; i++){let item = y[i]; text += makeCard(item.login, item.avatar_url, item.html_url, 3.5);}
}
else
{
text += "<h3>GitHub won't let us show our awesome Code Contributors at the moment.</h3>"
}
text += "</section><br><br>";
}
async function getData()
{
let backers = [];
let boosters = [];
let sponsors = [];
let donations = [];
let x = await fetch("https://opencollective.com/crow/members/all.json");
let y = await x.json();
for (var i = 0; i < y.length; i++)
{
let item = y[i];
if (item.role === "BACKER") {
if(item.tier === "Backer"){
backers.push(item);
}
else if(item.tier === "Booster"){
boosters.push(item);
}
else if(item.tier === "Sponsor"){
sponsors.push(item);
}
else if(item.tier === "Donation"){
donations.push(item);
}
}
}
let ghx = await fetch("https://gh-sponsors-dsdpxxx9d-filiptronicek.vercel.app/sponsors/CrowCpp");
let ghy = await fetch("https://gh-sponsors-dsdpxxx9d-filiptronicek.vercel.app/count/CrowCpp");
let ghsponsors = await ghx.json();
ghsponsors = ghsponsors.sponsors;
let ghx_count = await ghy.json();
ghx_count = ghx_count.sponsors.count;
if (ghx_count > ghsponsors.length)
{
let priv_count = ghx_count - ghsponsors.length;
ghsponsors.push({handle: priv_count = 1 ? "Private sponsor" : `${priv_count} Private sponsors`, avatar:null, profile:"https://github.com/CrowCpp"});
}
text += "<section style=\"text-align: center;\"><a href=\"https://opencollective.com/crow/contribute/sponsor-30717/checkout\"><h2>Sponsors</h2></a>";
for(var i = 0; i < sponsors.length; i++){let item = sponsors[i]; text += makeCard(item.name, item.image, item.profile, 10);}
text += "</section><br><section style=\"text-align: center;\"><a href=\"https://opencollective.com/crow/contribute/booster-30767/checkout\"><h2>Boosters</h2></a>";
for(var i = 0; i < boosters.length; i++){let item = boosters[i]; text += makeCard(item.name, item.image, item.profile, 5);}
text += "</section><br><section style=\"text-align: center;\"><a href=\"https://opencollective.com/crow/contribute/backer-30716/checkout\"><h2>Backers</h2></a>";
for(var i = 0; i < backers.length; i++){let item = backers[i]; text += makeCard(item.name, item.image, item.profile, 3.5);}
text += "</section><br><section style=\"text-align: center;\"><a href=\"https://opencollective.com/crow/contribute/donation-30769/checkout\"><h2>Donations</h2></a>";
for(var i = 0; i < donations.length; i++){let item = donations[i]; text += makeCard(item.name, item.image, item.profile, 3.5);}
text += "</section><br><section style=\"text-align: center;\"><a href=\"https://github.com/sponsors/CrowCpp\"><h2>GitHub Sponsors</h2></a>";
for(var i = 0; i < ghsponsors.length; i++){let item = ghsponsors[i]; text += makeCard(item.handle, item.avatar, item.profile, 5);}
text += "</section><section style=\"text-align: center;\">";
await getGHData();
text += "</section>";
document.getElementById("contributors").innerHTML = text;
}
getData();
</script>
{% endblock %}
@ -344,7 +515,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 }}
@ -353,7 +524,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

@ -8,7 +8,7 @@ add_warnings_optimizations(helloworld)
target_link_libraries(helloworld PUBLIC Crow::Crow)
# If compression is enabled, the example will be built
if(CROW_ENABLE_COMPRESSION)
if("compression" IN_LIST CROW_FEATURES)
add_executable(example_compression example_compression.cpp)
add_warnings_optimizations(example_compression)
target_link_libraries(example_compression Crow::Crow)
@ -17,7 +17,7 @@ else()
endif()
# If SSL is enabled, the example will be built
if(CROW_ENABLE_SSL)
if("ssl" IN_LIST CROW_FEATURES)
add_executable(example_ssl ssl/example_ssl.cpp)
add_warnings_optimizations(example_ssl)
target_link_libraries(example_ssl PUBLIC Crow::Crow)
@ -79,6 +79,18 @@ add_executable(example_blueprint example_blueprint.cpp)
add_warnings_optimizations(example_blueprint)
target_link_libraries(example_blueprint PUBLIC Crow::Crow)
add_executable(example_middleware example_middleware.cpp)
add_warnings_optimizations(example_middleware)
target_link_libraries(example_middleware PUBLIC Crow::Crow)
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

@ -1,31 +1,32 @@
#define CROW_MAIN
#include "crow.h"
#include <sstream>
class ExampleLogHandler : public crow::ILogHandler {
public:
void log(std::string /*message*/, crow::LogLevel /*level*/) override {
// cerr << "ExampleLogHandler -> " << message;
}
class ExampleLogHandler : public crow::ILogHandler
{
public:
void log(std::string /*message*/, crow::LogLevel /*level*/) override
{
// cerr << "ExampleLogHandler -> " << message;
}
};
struct ExampleMiddleware
{
std::string message;
ExampleMiddleware() : message("foo")
ExampleMiddleware():
message("foo")
{
}
void setMessage(const std::string &newMsg)
void setMessage(const std::string& newMsg)
{
message = newMsg;
}
struct context
{
};
{};
void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{
@ -45,26 +46,25 @@ int main()
app.get_middleware<ExampleMiddleware>().setMessage("hello");
CROW_ROUTE(app, "/")
.name("hello")
([]{
return "Hello World!";
});
.name("hello")([] {
return "Hello World!";
});
CROW_ROUTE(app, "/about")
([](){
([]() {
return "About Crow example.";
});
// a request to /path should be forwarded to /path/
CROW_ROUTE(app, "/path/")
([](){
([]() {
return "Trailing slash test case..";
});
// simple json response
CROW_ROUTE(app, "/json")
([]{
([] {
crow::json::wvalue x({{"message", "Hello, World!"}});
x["message2"] = "Hello, World.. Again!";
return x;
@ -72,31 +72,31 @@ int main()
CROW_ROUTE(app, "/json-initializer-list-constructor")
([] {
return crow::json::wvalue({
{"first", "Hello world!"}, /* stores a char const* hence a json::type::String */
{"second", std::string("How are you today?")}, /* stores a std::string hence a json::type::String. */
{"third", std::int64_t(54)}, /* stores a 64-bit int hence a std::int64_t. */
{"fourth", std::uint64_t(54)}, /* stores a 64-bit unsigned int hence a std::uint64_t. */
{"fifth", 54}, /* stores an int (as 54 is an int literal) hence a std::int64_t. */
{"sixth", 54u}, /* stores an unsigned int (as 54u is a unsigned int literal) hence a std::uint64_t. */
{"seventh", 2.f}, /* stores a float (as 2.f is a float literal) hence a double. */
{"eighth", 2.}, /* stores a double (as 2. is a double literal) hence a double. */
{"ninth", nullptr}, /* stores a std::nullptr hence json::type::Null . */
{"tenth", true} /* stores a bool hence json::type::True . */
});
return crow::json::wvalue({
{"first", "Hello world!"}, /* stores a char const* hence a json::type::String */
{"second", std::string("How are you today?")}, /* stores a std::string hence a json::type::String. */
{"third", std::int64_t(54)}, /* stores a 64-bit int hence a std::int64_t. */
{"fourth", std::uint64_t(54)}, /* stores a 64-bit unsigned int hence a std::uint64_t. */
{"fifth", 54}, /* stores an int (as 54 is an int literal) hence a std::int64_t. */
{"sixth", 54u}, /* stores an unsigned int (as 54u is a unsigned int literal) hence a std::uint64_t. */
{"seventh", 2.f}, /* stores a float (as 2.f is a float literal) hence a double. */
{"eighth", 2.}, /* stores a double (as 2. is a double literal) hence a double. */
{"ninth", nullptr}, /* stores a std::nullptr hence json::type::Null . */
{"tenth", true} /* stores a bool hence json::type::True . */
});
});
// json list response
CROW_ROUTE(app, "/json_list")
([]{
crow::json::wvalue x(crow::json::wvalue::list({1,2,3}));
([] {
crow::json::wvalue x(crow::json::wvalue::list({1, 2, 3}));
return x;
});
// To see it in action enter {ip}:18080/hello/{integer_between -2^32 and 100} and you should receive
// {integer_between -2^31 and 100} bottles of beer!
CROW_ROUTE(app,"/hello/<int>")
([](int count){
CROW_ROUTE(app, "/hello/<int>")
([](int count) {
if (count > 100)
return crow::response(400);
std::ostringstream os;
@ -105,8 +105,8 @@ int main()
});
// Same as above, but using crow::status
CROW_ROUTE(app,"/hello/<int>")
([](int count){
CROW_ROUTE(app, "/hello_status/<int>")
([](int count) {
if (count > 100)
return crow::response(crow::status::BAD_REQUEST);
std::ostringstream os;
@ -115,18 +115,30 @@ int main()
});
// To see it in action submit {ip}:18080/add/1/2 and you should receive 3 (exciting, isn't it)
CROW_ROUTE(app,"/add/<int>/<int>")
([](crow::response& res, int a, int b){
CROW_ROUTE(app, "/add/<int>/<int>")
([](crow::response& res, int a, int b) {
std::ostringstream os;
os << a+b;
os << a + b;
res.write(os.str());
res.end();
});
//same as the example above but uses mustache instead of stringstream
CROW_ROUTE(app, "/add_mustache/<int>/<int>")
([](int a, int b) {
auto t = crow::mustache::compile("Value is {{func}}");
crow::mustache::context ctx;
ctx["func"] = [&](std::string) {
return std::to_string(a + b);
};
auto result = t.render(ctx);
return result;
});
// Compile error with message "Handler type is mismatched with URL paramters"
//CROW_ROUTE(app,"/another/<int>")
//([](int a, int b){
//return crow::response(500);
//return crow::response(500);
//});
// more json example
@ -141,22 +153,21 @@ int main()
// A simpler way for json example:
// * curl -d '{"a":1,"b":2}' {ip}:18080/add_json
CROW_ROUTE(app, "/add_json")
.methods("POST"_method)
([](const crow::request& req){
auto x = crow::json::load(req.body);
if (!x)
return crow::response(400);
int sum = x["a"].i()+x["b"].i();
std::ostringstream os;
os << sum;
return crow::response{os.str()};
});
.methods("POST"_method)([](const crow::request& req) {
auto x = crow::json::load(req.body);
if (!x)
return crow::response(400);
int sum = x["a"].i() + x["b"].i();
std::ostringstream os;
os << sum;
return crow::response{os.str()};
});
// Example of a request taking URL parameters
// If you want to activate all the functions just query
// {ip}:18080/params?foo='blabla'&pew=32&count[]=a&count[]=b
CROW_ROUTE(app, "/params")
([](const crow::request& req){
([](const crow::request& req) {
std::ostringstream os;
// To get a simple string from the url params
@ -166,16 +177,18 @@ int main()
// To get a double from the request
// To see in action submit something like '/params?pew=42'
if(req.url_params.get("pew") != nullptr) {
if (req.url_params.get("pew") != nullptr)
{
double countD = boost::lexical_cast<double>(req.url_params.get("pew"));
os << "The value of 'pew' is " << countD << '\n';
os << "The value of 'pew' is " << countD << '\n';
}
// To get a list from the request
// You have to submit something like '/params?count[]=a&count[]=b' to have a list with two values (a and b)
auto count = req.url_params.get_list("count");
os << "The key 'count' contains " << count.size() << " value(s).\n";
for(const auto& countVal : count) {
for (const auto& countVal : count)
{
os << " - " << countVal << '\n';
}
@ -183,7 +196,8 @@ int main()
// You have to submit something like '/params?mydict[a]=b&mydict[abcd]=42' to have a list of pairs ((a, b) and (abcd, 42))
auto mydict = req.url_params.get_dict("mydict");
os << "The key 'dict' contains " << mydict.size() << " value(s).\n";
for(const auto& mydictVal : mydict) {
for (const auto& mydictVal : mydict)
{
os << " - " << mydictVal.first << " -> " << mydictVal.second << '\n';
}
@ -191,13 +205,13 @@ int main()
});
CROW_ROUTE(app, "/large")
([]{
return std::string(512*1024, ' ');
([] {
return std::string(512 * 1024, ' ');
});
// Take a multipart/form-data request and print out its body
CROW_ROUTE(app,"/multipart")
([](const crow::request& req){
CROW_ROUTE(app, "/multipart")
([](const crow::request& req) {
crow::multipart::message msg(req);
CROW_LOG_INFO << "body of the first part " << msg.parts[0].body;
return "it works!";
@ -208,6 +222,6 @@ int main()
//crow::logger::setHandler(std::make_shared<ExampleLogHandler>());
app.port(18080)
.multithreaded()
.run();
.multithreaded()
.run();
}

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include "crow.h"
int main()
@ -15,7 +14,7 @@ int main()
return "Hello world!";
});
/* CROW_BP_ROUTE(bp, "/templatt")
/* CROW_BP_ROUTE(bp, "/templatt")
([]() {
crow::mustache::context ctxdat;
ctxdat["messg"] = "fifty five!!";
@ -25,7 +24,10 @@ int main()
return page.render(ctxdat);
});
*/
CROW_BP_CATCHALL_ROUTE(sub_bp)([](){return "WRONG!!";});
CROW_BP_CATCHALL_ROUTE(sub_bp)
([]() {
return "WRONG!!";
});
bp.register_blueprint(sub_bp);

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include <crow.h>
@ -7,7 +6,10 @@ int main()
{
crow::SimpleApp app;
CROW_ROUTE(app, "/")([](){return "Hello";});
CROW_ROUTE(app, "/")
([]() {
return "Hello";
});
//Setting a custom route for any URL that isn't defined, instead of a simple 404.
CROW_CATCHALL_ROUTE(app)

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include "crow.h"
#include <string>
#include <vector>
@ -16,7 +15,7 @@ void broadcast(const string& msg)
x["msgs"][0] = msgs.back();
x["last"] = msgs.size();
string body = x.dump();
for(auto p : ress)
for (auto p : ress)
{
auto* res = p.first;
CROW_LOG_DEBUG << res << " replied: " << body;
@ -31,31 +30,31 @@ int main()
crow::mustache::set_base(".");
CROW_ROUTE(app, "/")
([]{
([] {
crow::mustache::context ctx;
return crow::mustache::load("example_chat.html").render();
});
CROW_ROUTE(app, "/logs")
([]{
([] {
CROW_LOG_INFO << "logs requested";
crow::json::wvalue x;
int start = max(0, (int)msgs.size()-100);
for(int i = start; i < (int)msgs.size(); i++)
x["msgs"][i-start] = msgs[i];
int start = max(0, (int)msgs.size() - 100);
for (int i = start; i < (int)msgs.size(); i++)
x["msgs"][i - start] = msgs[i];
x["last"] = msgs.size();
CROW_LOG_INFO << "logs completed";
return x;
});
CROW_ROUTE(app, "/logs/<int>")
([](const crow::request& /*req*/, crow::response& res, int after){
([](const crow::request& /*req*/, crow::response& res, int after) {
CROW_LOG_INFO << "logs with last " << after;
if (after < (int)msgs.size())
{
crow::json::wvalue x;
for(int i = after; i < (int)msgs.size(); i ++)
x["msgs"][i-after] = msgs[i];
for (int i = after; i < (int)msgs.size(); i++)
x["msgs"][i - after] = msgs[i];
x["last"] = msgs.size();
res.write(x.dump());
@ -64,7 +63,7 @@ int main()
else
{
vector<pair<crow::response*, decltype(chrono::steady_clock::now())>> filtered;
for(auto p : ress)
for (auto p : ress)
{
if (p.first->is_alive() && chrono::steady_clock::now() - p.second < chrono::seconds(30))
filtered.push_back(p);
@ -78,15 +77,13 @@ int main()
});
CROW_ROUTE(app, "/send")
.methods("GET"_method, "POST"_method)
([](const crow::request& req)
{
CROW_LOG_INFO << "msg from client: " << req.body;
broadcast(req.body);
return "";
});
.methods("GET"_method, "POST"_method)([](const crow::request& req) {
CROW_LOG_INFO << "msg from client: " << req.body;
broadcast(req.body);
return "";
});
app.port(40080)
//.multithreaded()
.run();
//.multithreaded()
.run();
}

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include "crow.h"
#include "crow/compression.h"
@ -8,7 +7,7 @@ int main()
//crow::App<crow::CompressionGzip> app;
CROW_ROUTE(app, "/hello")
([&](const crow::request&, crow::response& res){
([&](const crow::request&, crow::response& res) {
res.compressed = false;
res.body = "Hello World! This is uncompressed!";
@ -16,15 +15,15 @@ int main()
});
CROW_ROUTE(app, "/hello_compressed")
([](){
([]() {
return "Hello World! This is compressed by default!";
});
app.port(18080)
.use_compression(crow::compression::algorithm::DEFLATE)
.use_compression(crow::compression::algorithm::DEFLATE)
//.use_compression(crow::compression::algorithm::GZIP)
.loglevel(crow::LogLevel::Debug)
.multithreaded()
.run();
.loglevel(crow::LogLevel::Debug)
.multithreaded()
.run();
}

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#define CROW_JSON_USE_MAP
#include "crow.h"
@ -6,17 +5,17 @@ int main()
{
crow::SimpleApp app;
// simple json response using a map
// To see it in action enter {ip}:18080/json
// it shoud show amessage before zmessage despite adding zmessage first.
CROW_ROUTE(app, "/json")
([]{
crow::json::wvalue x({{"zmessage", "Hello, World!"},
{"amessage", "Hello, World2!"}});
return x;
});
// simple json response using a map
// To see it in action enter {ip}:18080/json
// it shoud show amessage before zmessage despite adding zmessage first.
CROW_ROUTE(app, "/json")
([] {
crow::json::wvalue x({{"zmessage", "Hello, World!"},
{"amessage", "Hello, World2!"}});
return x;
});
app.port(18080)
.multithreaded()
.run();
app.port(18080)
.multithreaded()
.run();
}

View File

@ -0,0 +1,54 @@
#include "crow.h"
struct RequestLogger
{
struct context
{};
void before_handle(crow::request& req, crow::response& /*res*/, context& /*ctx*/)
{
CROW_LOG_INFO << "Request to:" + req.url;
}
void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
};
// Per handler middleware has to extend ILocalMiddleware
// It is called only if enabled
struct SecretContentGuard : crow::ILocalMiddleware
{
struct context
{};
void before_handle(crow::request& /*req*/, crow::response& res, context& /*ctx*/)
{
res.write("SECRET!");
res.code = 403;
res.end();
}
void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
};
int main()
{
// ALL middleware (including per handler) is listed
crow::App<RequestLogger, SecretContentGuard> app;
CROW_ROUTE(app, "/")
([]() {
return "Hello, world!";
});
CROW_ROUTE(app, "/secret")
// Enable SecretContentGuard for this handler
.CROW_MIDDLEWARES(app, SecretContentGuard)([]() {
return "";
});
app.port(18080).run();
return 0;
}

View File

@ -1,28 +1,27 @@
//#define CROW_STATIC_DRIECTORY "alternative_directory/"
//#define CROW_STATIC_DIRECTORY "alternative_directory/"
//#define CROW_STATIC_ENDPOINT "/alternative_endpoint/<path>"
//#define CROW_DISABLE_STATIC_DIR
#define CROW_MAIN
#include "crow.h"
int main()
{
//Crow app initialization
crow::SimpleApp app;
//Crow app initialization
crow::SimpleApp app;
//
CROW_ROUTE(app, "/")
([](const crow::request&, crow::response& res) {
//replace cat.jpg with your file path
res.set_static_file_info("cat.jpg");
res.end();
});
CROW_ROUTE(app, "/")
([](const crow::request&, crow::response& res) {
//replace cat.jpg with your file path
res.set_static_file_info("cat.jpg");
res.end();
});
app.port(18080).run();
return 0;
app.port(18080).run();
return 0;
}
/// You can also use the `/static` directory and endpoint (the directory needs to have the same path as your executable).

View File

@ -1,31 +1,32 @@
#define CROW_MAIN
#include "crow.h"
#include <sstream>
class ExampleLogHandler : public crow::ILogHandler {
public:
void log(std::string message, crow::LogLevel level) override {
// cerr << "ExampleLogHandler -> " << message;
}
class ExampleLogHandler : public crow::ILogHandler
{
public:
void log(std::string message, crow::LogLevel level) override
{
// cerr << "ExampleLogHandler -> " << message;
}
};
struct ExampleMiddleware
struct ExampleMiddleware
{
std::string message;
ExampleMiddleware() : message("foo")
ExampleMiddleware():
message("foo")
{
}
void setMessage(const std::string &newMsg)
void setMessage(const std::string& newMsg)
{
message = newMsg;
}
struct context
{
};
{};
void before_handle(crow::request& req, crow::response& res, context& ctx)
{
@ -45,32 +46,31 @@ int main()
app.get_middleware<ExampleMiddleware>().setMessage("hello");
CROW_ROUTE(app, "/")
.name("hello")
([]{
return "Hello World!";
});
.name("hello")([] {
return "Hello World!";
});
CROW_ROUTE(app, "/about")
([](){
([]() {
return "About Crow example.";
});
// a request to /path should be forwarded to /path/
CROW_ROUTE(app, "/path/")
([](){
([]() {
return "Trailing slash test case..";
});
// simple json response
CROW_ROUTE(app, "/json")
([]{
([] {
crow::json::wvalue x;
x["message"] = "Hello, World!";
return x;
});
CROW_ROUTE(app, "/hello/<int>")
([](int count){
([](int count) {
if (count > 100)
return crow::response(400);
std::ostringstream os;
@ -79,9 +79,9 @@ int main()
});
CROW_ROUTE(app, "/add/<int>/<int>")
([](crow::response& res, int a, int b){
([](crow::response& res, int a, int b) {
std::ostringstream os;
os << a+b;
os << a + b;
res.write(os.str());
res.end();
});
@ -89,44 +89,44 @@ int main()
// Compile error with message "Handler type is mismatched with URL paramters"
//CROW_ROUTE(app,"/another/<int>")
//([](int a, int b){
//return crow::response(500);
//return crow::response(500);
//});
// more json example
CROW_ROUTE(app, "/add_json")
.methods(crow::HTTPMethod::Post)
([](const crow::request& req){
auto x = crow::json::load(req.body);
if (!x)
return crow::response(400);
auto sum = x["a"].i()+x["b"].i();
std::ostringstream os;
os << sum;
return crow::response{os.str()};
});
.methods(crow::HTTPMethod::Post)([](const crow::request& req) {
auto x = crow::json::load(req.body);
if (!x)
return crow::response(400);
auto sum = x["a"].i() + x["b"].i();
std::ostringstream os;
os << sum;
return crow::response{os.str()};
});
app.route_dynamic("/params")
([](const crow::request& req){
app.route_dynamic("/params")([](const crow::request& req) {
std::ostringstream os;
os << "Params: " << req.url_params << "\n\n";
os << "Params: " << req.url_params << "\n\n";
os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n";
if(req.url_params.get("pew") != nullptr) {
if (req.url_params.get("pew") != nullptr)
{
double countD = boost::lexical_cast<double>(req.url_params.get("pew"));
os << "The value of 'pew' is " << countD << '\n';
os << "The value of 'pew' is " << countD << '\n';
}
auto count = req.url_params.get_list("count");
os << "The key 'count' contains " << count.size() << " value(s).\n";
for(const auto& countVal : count) {
for (const auto& countVal : count)
{
os << " - " << countVal << '\n';
}
return crow::response{os.str()};
});
});
// ignore all log
crow::logger::setLogLevel(crow::LogLevel::Debug);
//crow::logger::setHandler(std::make_shared<ExampleLogHandler>());
app.port(18080)
.multithreaded()
.run();
.multithreaded()
.run();
}

View File

@ -1,13 +1,14 @@
#define CROW_MAIN
#include "crow_all.h"
#include <sstream>
class ExampleLogHandler : public crow::ILogHandler {
public:
void log(std::string /*message*/, crow::LogLevel /*level*/) override {
// cerr << "ExampleLogHandler -> " << message;
}
class ExampleLogHandler : public crow::ILogHandler
{
public:
void log(std::string /*message*/, crow::LogLevel /*level*/) override
{
// cerr << "ExampleLogHandler -> " << message;
}
};
int main()
@ -15,19 +16,18 @@ int main()
crow::SimpleApp app;
CROW_ROUTE(app, "/")
.name("hello")
([]{
return "Hello World!";
});
.name("hello")([] {
return "Hello World!";
});
CROW_ROUTE(app, "/about")
([](){
([]() {
return "About Crow example.";
});
// simple json response
CROW_ROUTE(app, "/json")
([]{
([] {
crow::json::wvalue x({{"message", "Hello, World!"}});
x["message2"] = "Hello, World.. Again!";
return x;
@ -35,29 +35,29 @@ int main()
CROW_ROUTE(app, "/json-initializer-list-constructor")
([] {
return crow::json::wvalue({
{"first", "Hello world!"}, /* stores a char const* hence a json::type::String */
{"second", std::string("How are you today?")}, /* stores a std::string hence a json::type::String. */
{"third", 54}, /* stores an int (as 54 is an int literal) hence a std::int64_t. */
{"fourth", 54l}, /* stores a long (as 54l is a long literal) hence a std::int64_t. */
{"fifth", 54u}, /* stores an unsigned int (as 54u is a unsigned int literal) hence a std::uint64_t. */
{"sixth", 54ul}, /* stores an unsigned long (as 54ul is an unsigned long literal) hence a std::uint64_t. */
{"seventh", 2.f}, /* stores a float (as 2.f is a float literal) hence a double. */
{"eighth", 2.}, /* stores a double (as 2. is a double literal) hence a double. */
{"ninth", nullptr}, /* stores a std::nullptr hence json::type::Null . */
{"tenth", true} /* stores a bool hence json::type::True . */
});
return crow::json::wvalue({
{"first", "Hello world!"}, /* stores a char const* hence a json::type::String */
{"second", std::string("How are you today?")}, /* stores a std::string hence a json::type::String. */
{"third", 54}, /* stores an int (as 54 is an int literal) hence a std::int64_t. */
{"fourth", 54l}, /* stores a long (as 54l is a long literal) hence a std::int64_t. */
{"fifth", 54u}, /* stores an unsigned int (as 54u is a unsigned int literal) hence a std::uint64_t. */
{"sixth", 54ul}, /* stores an unsigned long (as 54ul is an unsigned long literal) hence a std::uint64_t. */
{"seventh", 2.f}, /* stores a float (as 2.f is a float literal) hence a double. */
{"eighth", 2.}, /* stores a double (as 2. is a double literal) hence a double. */
{"ninth", nullptr}, /* stores a std::nullptr hence json::type::Null . */
{"tenth", true} /* stores a bool hence json::type::True . */
});
});
// json list response
CROW_ROUTE(app, "/json_list")
([]{
crow::json::wvalue x(crow::json::wvalue::list({1,2,3}));
([] {
crow::json::wvalue x(crow::json::wvalue::list({1, 2, 3}));
return x;
});
CROW_ROUTE(app,"/hello/<int>")
([](int count){
CROW_ROUTE(app, "/hello/<int>")
([](int count) {
if (count > 100)
return crow::response(400);
std::ostringstream os;
@ -67,10 +67,10 @@ int main()
// example which uses only response as a paramter without
// request being a parameter.
CROW_ROUTE(app,"/add/<int>/<int>")
([](crow::response& res, int a, int b){
CROW_ROUTE(app, "/add/<int>/<int>")
([](crow::response& res, int a, int b) {
std::ostringstream os;
os << a+b;
os << a + b;
res.write(os.str());
res.end();
});
@ -78,44 +78,46 @@ int main()
// Compile error with message "Handler type is mismatched with URL paramters"
//CROW_ROUTE(app,"/another/<int>")
//([](int a, int b){
//return crow::response(500);
//return crow::response(500);
//});
// more json example
CROW_ROUTE(app, "/add_json")
([](const crow::request& req){
([](const crow::request& req) {
auto x = crow::json::load(req.body);
if (!x)
return crow::response(400);
int sum = x["a"].i()+x["b"].i();
int sum = x["a"].i() + x["b"].i();
std::ostringstream os;
os << sum;
return crow::response{os.str()};
});
CROW_ROUTE(app, "/params")
([](const crow::request& req){
([](const crow::request& req) {
std::ostringstream os;
os << "Params: " << req.url_params << "\n\n";
os << "Params: " << req.url_params << "\n\n";
os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n";
if(req.url_params.get("pew") != nullptr) {
if (req.url_params.get("pew") != nullptr)
{
double countD = boost::lexical_cast<double>(req.url_params.get("pew"));
os << "The value of 'pew' is " << countD << '\n';
os << "The value of 'pew' is " << countD << '\n';
}
auto count = req.url_params.get_list("count");
os << "The key 'count' contains " << count.size() << " value(s).\n";
for(const auto& countVal : count) {
for (const auto& countVal : count)
{
os << " - " << countVal << '\n';
}
return crow::response{os.str()};
});
});
// ignore all log
crow::logger::setLogLevel(crow::LogLevel::Debug);
//crow::logger::setHandler(std::make_shared<ExampleLogHandler>());
app.port(18080)
.server_name("CrowCpp")
.multithreaded()
.run();
.server_name("CrowCpp")
.multithreaded()
.run();
}

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include "crow.h"
int main()

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

@ -0,0 +1,36 @@
#include "crow.h"
#include "crow/middlewares/cors.h"
int main()
{
// Enable CORS
crow::App<crow::CORSHandler> app;
// Customize CORS
auto& cors = app.get_middleware<crow::CORSHandler>();
// clang-format off
cors
.global()
.headers("X-Custom-Header", "Upgrade-Insecure-Requests")
.methods("POST"_method, "GET"_method)
.prefix("/cors")
.origin("example.com")
.prefix("/nocors")
.ignore();
// clang-format on
CROW_ROUTE(app, "/")
([]() {
return "Check Access-Control-Allow-Methods header";
});
CROW_ROUTE(app, "/cors")
([]() {
return "Check Access-Control-Allow-Origin header";
});
app.port(18080).run();
return 0;
}

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include "crow.h"
int main()
@ -14,7 +13,7 @@ int main()
// Use .pem file
//app.port(18080).ssl_file("test.pem").run();
// Use custom context; see boost::asio::ssl::context
/*
* crow::ssl_context_t ctx;

View File

@ -1,4 +1,3 @@
#define CROW_MAIN
#include "crow.h"
#include <unordered_set>
#include <mutex>
@ -11,39 +10,38 @@ int main()
std::mutex mtx;
std::unordered_set<crow::websocket::connection*> users;
CROW_ROUTE(app, "/ws")
.websocket()
.onopen([&](crow::websocket::connection& conn){
CROW_LOG_INFO << "new websocket connection from " << conn.get_remote_ip();
std::lock_guard<std::mutex> _(mtx);
users.insert(&conn);
})
.onclose([&](crow::websocket::connection& conn, const std::string& reason){
CROW_LOG_INFO << "websocket connection closed: " << reason;
std::lock_guard<std::mutex> _(mtx);
users.erase(&conn);
})
.onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary){
std::lock_guard<std::mutex> _(mtx);
for(auto u:users)
if (is_binary)
u->send_binary(data);
else
u->send_text(data);
});
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);
users.insert(&conn);
})
.onclose([&](crow::websocket::connection& conn, const std::string& reason) {
CROW_LOG_INFO << "websocket connection closed: " << reason;
std::lock_guard<std::mutex> _(mtx);
users.erase(&conn);
})
.onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary) {
std::lock_guard<std::mutex> _(mtx);
for (auto u : users)
if (is_binary)
u->send_binary(data);
else
u->send_text(data);
});
CROW_ROUTE(app, "/")
([]{
([] {
char name[256];
gethostname(name, 256);
crow::mustache::context x;
x["servername"] = name;
auto page = crow::mustache::load("ws.html");
return page.render(x);
});
});
app.port(40080)
.multithreaded()
.run();
.multithreaded()
.run();
}

View File

@ -8,7 +8,7 @@
#include "crow/json.h"
#include "crow/mustache.h"
#include "crow/logging.h"
#include "crow/dumb_timer_queue.h"
#include "crow/task_timer.h"
#include "crow/utility.h"
#include "crow/common.h"
#include "crow/http_request.h"
@ -17,6 +17,7 @@
#include "crow/http_response.h"
#include "crow/multipart.h"
#include "crow/routing.h"
#include "crow/middleware.h"
#include "crow/middleware_context.h"
#include "crow/compression.h"
#include "crow/http_connection.h"

View File

@ -18,7 +18,7 @@
#include "crow/middleware_context.h"
#include "crow/http_request.h"
#include "crow/http_server.h"
#include "crow/dumb_timer_queue.h"
#include "crow/task_timer.h"
#ifdef CROW_ENABLE_COMPRESSION
#include "crow/compression.h"
#endif
@ -29,79 +29,78 @@
#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_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()
namespace crow
{
#ifdef CROW_MAIN
int detail::dumb_timer_queue::tick = 5;
#endif
#ifdef CROW_ENABLE_SSL
using ssl_context_t = boost::asio::ssl::context;
#endif
///The main server application
/// The main server application
///
/// Use `SimpleApp` or `App<Middleware1, Middleware2, etc...>`
template <typename ... Middlewares>
template<typename... Middlewares>
class Crow
{
public:
///This crow application
/// This crow application
using self_t = Crow;
///The HTTP server
/// The HTTP server
using server_t = Server<Crow, SocketAdaptor, Middlewares...>;
#ifdef CROW_ENABLE_SSL
///An HTTP server that runs on SSL with an SSLAdaptor
/// An HTTP server that runs on SSL with an SSLAdaptor
using ssl_server_t = Server<Crow, SSLAdaptor, Middlewares...>;
#endif
Crow()
{
}
{}
std::atomic<int> websocket_count{0};
///Process an Upgrade request
/// Process an Upgrade request
///
///Currently used to upgrrade an HTTP connection to a WebSocket connection
template <typename Adaptor>
/// Currently used to upgrade an HTTP connection to a WebSocket connection
template<typename Adaptor>
void handle_upgrade(const request& req, response& res, Adaptor&& adaptor)
{
router_.handle_upgrade(req, res, adaptor, websocket_count);
}
///Process the request and generate a response for it
void handle(const request& req, response& res)
/// Process the request and generate a response for it
void handle(request& req, response& res)
{
router_.handle(req, res);
}
///Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
/// Create a dynamic route using a rule (**Use CROW_ROUTE instead**)
DynamicRule& route_dynamic(std::string&& rule)
{
return router_.new_rule_dynamic(std::move(rule));
}
///Create a route using a rule (**Use CROW_ROUTE instead**)
template <uint64_t Tag>
auto route(std::string&& rule)
#ifdef CROW_CAN_USE_CPP17
-> typename std::invoke_result<decltype(&Router::new_rule_tagged<Tag>),
Router, std::string&&>::type
template<uint64_t Tag>
#ifdef CROW_GCC83_WORKAROUND
auto& route(std::string&& rule)
#else
-> typename std::result_of<decltype (&Router::new_rule_tagged<Tag>)(
Router, std::string&&)>::type
auto route(std::string&& rule)
#endif
#if defined CROW_CAN_USE_CPP17 && !defined CROW_GCC83_WORKAROUND
-> typename std::invoke_result<decltype(&Router::new_rule_tagged<Tag>), Router, std::string&&>::type
#elif !defined CROW_GCC83_WORKAROUND
-> typename std::result_of<decltype (&Router::new_rule_tagged<Tag>)(Router, std::string&&)>::type
#endif
{
return router_.new_rule_tagged<Tag>(std::move(rule));
}
///Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
/// Create a route for any requests without a proper route (**Use CROW_CATCHALL_ROUTE instead**)
CatchallRule& catchall_route()
{
return router_.catchall_rule();
@ -119,50 +118,57 @@ namespace crow
return *this;
}
///Set the port that Crow will handle requests on
/// Set the port that Crow will handle requests on
self_t& port(std::uint16_t port)
{
port_ = port;
return *this;
}
///Set the connection timeout in seconds (default is 5)
/// Get the port that Crow will handle requests on
std::uint16_t port()
{
return port_;
}
/// Set the connection timeout in seconds (default is 5)
self_t& timeout(std::uint8_t timeout)
{
detail::dumb_timer_queue::tick = timeout;
timeout_ = timeout;
return *this;
}
///Set the server name
/// Set the server name
self_t& server_name(std::string server_name)
{
server_name_ = server_name;
return *this;
}
///The IP address that Crow will handle requests on (default is 0.0.0.0)
/// The IP address that Crow will handle requests on (default is 0.0.0.0)
self_t& bindaddr(std::string bindaddr)
{
bindaddr_ = bindaddr;
return *this;
}
///Run the server on multiple threads using all available threads
/// Run the server on multiple threads using all available threads
self_t& multithreaded()
{
return concurrency(std::thread::hardware_concurrency());
}
///Run the server on multiple threads using a specific number
/// Run the server on multiple threads using a specific number
self_t& concurrency(std::uint16_t concurrency)
{
if (concurrency < 1)
concurrency = 1;
if (concurrency < 2) // Crow can have a minimum of 2 threads running
concurrency = 2;
concurrency_ = concurrency;
return *this;
}
///Set the server's log level
/// Set the server's log level
///
/// Possible values are:<br>
@ -177,7 +183,7 @@ namespace crow
return *this;
}
/// Set a response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB)
/// Set the response body size (in bytes) beyond which Crow automatically streams responses (Default is 1MiB)
///
/// Any streamed response is unaffected by Crow's timer, and therefore won't timeout before a response is fully sent.
@ -187,6 +193,7 @@ namespace crow
return *this;
}
/// Get the response body size (in bytes) beyond which Crow automatically streams responses
size_t& stream_threshold()
{
return res_stream_threshold_;
@ -198,9 +205,10 @@ namespace crow
return *this;
}
///Set a custom duration and function to run on every tick
template <typename Duration, typename Func>
self_t& tick(Duration d, Func f) {
/// Set a custom duration and function to run on every tick
template<typename Duration, typename Func>
self_t& tick(Duration d, Func f)
{
tick_interval_ = std::chrono::duration_cast<std::chrono::milliseconds>(d);
tick_function_ = f;
return *this;
@ -224,21 +232,20 @@ namespace crow
return compression_used_;
}
#endif
///A wrapper for `validate()` in the router
/// A wrapper for `validate()` in the router
///
///Go through the rules, upgrade them if possible, and add them to the list of rules
/// Go through the rules, upgrade them if possible, and add them to the list of rules
void validate()
{
if (!validated_)
{
#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);
res.end();
route<crow::black_magic::get_parameter_tag(CROW_STATIC_ENDPOINT)>(CROW_STATIC_ENDPOINT)([](crow::response& res, std::string file_path_partial) {
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(CROW_STATIC_DIRECTORY + file_path_partial);
res.end();
});
#if defined(__APPLE__) || defined(__MACH__)
@ -249,11 +256,10 @@ 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);
res.end();
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) {
utility::sanitize_filename(file_path_partial);
res.set_static_file_info_unsafe(bp->static_dir() + '/' + file_path_partial);
res.end();
});
}
}
@ -265,7 +271,7 @@ namespace crow
}
}
///Notify anything using `wait_for_server_start()` to proceed
/// Notify anything using `wait_for_server_start()` to proceed
void notify_server_start()
{
std::unique_lock<std::mutex> lock(start_mutex_);
@ -273,24 +279,29 @@ namespace crow
cv_started_.notify_all();
}
///Run the server
/// Run the server
void run()
{
validate();
#ifdef CROW_ENABLE_SSL
if (use_ssl_)
if (ssl_used_)
{
ssl_server_ = std::move(std::unique_ptr<ssl_server_t>(new ssl_server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, &ssl_context_)));
ssl_server_ = std::move(std::unique_ptr<ssl_server_t>(new ssl_server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, timeout_, &ssl_context_)));
ssl_server_->set_tick_function(tick_interval_, tick_function_);
ssl_server_->signal_clear();
for (auto snum : signals_)
{
ssl_server_->signal_add(snum);
}
notify_server_start();
ssl_server_->run();
}
else
#endif
{
server_ = std::move(std::unique_ptr<server_t>(new server_t(this, bindaddr_, port_, server_name_, &middlewares_, concurrency_, nullptr)));
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_);
for (auto snum : signals_)
{
@ -301,25 +312,30 @@ namespace crow
}
}
///Stop the server
/// Non-blocking version of \ref run()
std::future<void> run_async()
{
return std::async(std::launch::async, [&] {
this->run();
});
}
/// Stop the server
void stop()
{
#ifdef CROW_ENABLE_SSL
if (use_ssl_)
if (ssl_used_)
{
if (ssl_server_) {
ssl_server_->stop();
}
if (ssl_server_) { ssl_server_->stop(); }
}
else
#endif
{
if (server_) {
server_->stop();
}
if (server_) { server_->stop(); }
}
}
/// Print the routing paths defined for each HTTP method
void debug_print()
{
CROW_LOG_DEBUG << "Routing:";
@ -329,75 +345,99 @@ 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)
{
use_ssl_ = true;
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_file(crt_filename, ssl_context_t::pem);
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
);
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3);
return *this;
}
///use .pem file for SSL
/// Use .pem file for SSL
self_t& ssl_file(const std::string& pem_filename)
{
use_ssl_ = true;
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_.load_verify_file(pem_filename);
ssl_context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
);
boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3);
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)
{
use_ssl_ = true;
ssl_used_ = true;
ssl_context_ = std::move(ctx);
return *this;
}
bool use_ssl_{false};
ssl_context_t ssl_context_{boost::asio::ssl::context::sslv23};
bool ssl_used() const
{
return ssl_used_;
}
#else
template <typename T, typename ... Remain>
template<typename T, typename... Remain>
self_t& ssl_file(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.");
// 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>
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&&)
{
// 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.");
// 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;
}
bool ssl_used() const
{
return false;
}
#endif
// middleware
using context_t = detail::context<Middlewares...>;
template <typename T>
using mw_container_t = std::tuple<Middlewares...>;
template<typename T>
typename T::context& get_context(const request& req)
{
static_assert(black_magic::contains<T, Middlewares...>::value, "App doesn't have the specified middleware type.");
@ -405,13 +445,13 @@ namespace crow
return ctx.template get<T>();
}
template <typename T>
template<typename T>
T& get_middleware()
{
return utility::get_element_by_type<T, Middlewares...>(middlewares_);
}
///Wait until the server has properly started
/// Wait until the server has properly started
void wait_for_server_start()
{
std::unique_lock<std::mutex> lock(start_mutex_);
@ -421,8 +461,9 @@ namespace crow
}
private:
std::uint8_t timeout_{5};
uint16_t port_ = 80;
uint16_t concurrency_ = 1;
uint16_t concurrency_ = 2;
bool validated_ = false;
std::string server_name_ = std::string("Crow/") + VERSION;
std::string bindaddr_ = "0.0.0.0";
@ -441,7 +482,10 @@ namespace crow
#ifdef CROW_ENABLE_SSL
std::unique_ptr<ssl_server_t> ssl_server_;
bool ssl_used_{false};
ssl_context_t ssl_context_{boost::asio::ssl::context::sslv23};
#endif
std::unique_ptr<server_t> server_;
std::vector<int> signals_{SIGINT, SIGTERM};
@ -450,7 +494,7 @@ namespace crow
std::condition_variable cv_started_;
std::mutex start_mutex_;
};
template <typename ... Middlewares>
template<typename... Middlewares>
using App = Crow<Middlewares...>;
using SimpleApp = Crow<>;
}
} // namespace crow

View File

@ -14,7 +14,7 @@ namespace crow
std::size_t seed = 0;
std::locale locale;
for(auto c : key)
for (auto c : key)
{
boost::hash_combine(seed, std::toupper(c, locale));
}
@ -33,4 +33,4 @@ namespace crow
};
using ci_map = std::unordered_multimap<std::string, std::string, ci_hash, ci_key_eq>;
}
} // namespace crow

View File

@ -8,6 +8,10 @@
namespace crow
{
const char cr = '\r';
const char lf = '\n';
const std::string crlf("\r\n");
enum class HTTPMethod : char
{
#ifndef DELETE
@ -16,11 +20,43 @@ namespace crow
HEAD,
POST,
PUT,
CONNECT,
OPTIONS,
TRACE,
PATCH,
PURGE,
COPY,
LOCK,
MKCOL,
MOVE,
PROPFIND,
PROPPATCH,
SEARCH,
UNLOCK,
BIND,
REBIND,
UNBIND,
ACL,
REPORT,
MKACTIVITY,
CHECKOUT,
MERGE,
MSEARCH,
NOTIFY,
SUBSCRIBE,
UNSUBSCRIBE,
MKCALENDAR,
LINK,
UNLINK,
SOURCE,
#endif
Delete = 0,
@ -28,17 +64,106 @@ namespace crow
Head,
Post,
Put,
Connect,
Options,
Trace,
Patch,
Purge,
Copy,
Lock,
MkCol,
Move,
Propfind,
Proppatch,
Search,
Unlock,
Bind,
Rebind,
Unbind,
Acl,
Report,
MkActivity,
Checkout,
Merge,
MSearch,
Notify,
Subscribe,
Unsubscribe,
MkCalendar,
Link,
Unlink,
Source,
InternalMethodCount,
// should not add an item below this line: used for array count
};
constexpr const char* method_strings[] =
{
"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",
"M-SEARCH",
"NOTIFY",
"SUBSCRIBE",
"UNSUBSCRIBE",
"MKCALENDAR",
"LINK",
"UNLINK",
"SOURCE"};
inline std::string method_name(HTTPMethod method)
{
if (CROW_LIKELY(method < HTTPMethod::InternalMethodCount))
{
return method_strings[(unsigned char)method];
}
return "invalid";
}
// clang-format off
enum status
{
CONTINUE = 100,
@ -80,38 +205,11 @@ namespace crow
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
VARIANT_ALSO_NEGOTIATES = 506
};
inline std::string method_name(HTTPMethod method)
{
switch(method)
{
case HTTPMethod::Delete:
return "DELETE";
case HTTPMethod::Get:
return "GET";
case HTTPMethod::Head:
return "HEAD";
case HTTPMethod::Post:
return "POST";
case HTTPMethod::Put:
return "PUT";
case HTTPMethod::Connect:
return "CONNECT";
case HTTPMethod::Options:
return "OPTIONS";
case HTTPMethod::Trace:
return "TRACE";
case HTTPMethod::Patch:
return "PATCH";
case HTTPMethod::Purge:
return "PURGE";
default:
return "invalid";
}
return "invalid";
}
// clang-format on
enum class ParamType : char
{
@ -124,6 +222,7 @@ namespace crow
MAX
};
/// @cond SKIP
struct routing_params
{
std::vector<int64_t> int_params;
@ -134,23 +233,22 @@ namespace crow
void debug_print() const
{
std::cerr << "routing_params" << std::endl;
for(auto i:int_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for(auto i:uint_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for(auto i:double_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for(auto& i:string_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for (auto i : int_params)
std::cerr << i << ", ";
std::cerr << std::endl;
for (auto i : uint_params)
std::cerr << i << ", ";
std::cerr << std::endl;
for (auto i : double_params)
std::cerr << i << ", ";
std::cerr << std::endl;
for (auto& i : string_params)
std::cerr << i << ", ";
std::cerr << std::endl;
}
template <typename T>
template<typename T>
T get(unsigned) const;
};
template<>
@ -176,22 +274,60 @@ namespace crow
{
return string_params[index];
}
/// @endcond
} // namespace crow
// clang-format off
#ifndef CROW_MSVC_WORKAROUND
constexpr crow::HTTPMethod method_from_string(const char* str)
{
return crow::black_magic::is_equ_p(str, "GET", 3) ? crow::HTTPMethod::Get :
crow::black_magic::is_equ_p(str, "DELETE", 6) ? crow::HTTPMethod::Delete :
crow::black_magic::is_equ_p(str, "HEAD", 4) ? crow::HTTPMethod::Head :
crow::black_magic::is_equ_p(str, "POST", 4) ? crow::HTTPMethod::Post :
crow::black_magic::is_equ_p(str, "PUT", 3) ? crow::HTTPMethod::Put :
crow::black_magic::is_equ_p(str, "OPTIONS", 7) ? crow::HTTPMethod::Options :
crow::black_magic::is_equ_p(str, "CONNECT", 7) ? crow::HTTPMethod::Connect :
crow::black_magic::is_equ_p(str, "TRACE", 5) ? crow::HTTPMethod::Trace :
crow::black_magic::is_equ_p(str, "PATCH", 5) ? crow::HTTPMethod::Patch :
crow::black_magic::is_equ_p(str, "PURGE", 5) ? crow::HTTPMethod::Purge :
crow::black_magic::is_equ_p(str, "COPY", 4) ? crow::HTTPMethod::Copy :
crow::black_magic::is_equ_p(str, "LOCK", 4) ? crow::HTTPMethod::Lock :
crow::black_magic::is_equ_p(str, "MKCOL", 5) ? crow::HTTPMethod::MkCol :
crow::black_magic::is_equ_p(str, "MOVE", 4) ? crow::HTTPMethod::Move :
crow::black_magic::is_equ_p(str, "PROPFIND", 8) ? crow::HTTPMethod::Propfind :
crow::black_magic::is_equ_p(str, "PROPPATCH", 9) ? crow::HTTPMethod::Proppatch :
crow::black_magic::is_equ_p(str, "SEARCH", 6) ? crow::HTTPMethod::Search :
crow::black_magic::is_equ_p(str, "UNLOCK", 6) ? crow::HTTPMethod::Unlock :
crow::black_magic::is_equ_p(str, "BIND", 4) ? crow::HTTPMethod::Bind :
crow::black_magic::is_equ_p(str, "REBIND", 6) ? crow::HTTPMethod::Rebind :
crow::black_magic::is_equ_p(str, "UNBIND", 6) ? crow::HTTPMethod::Unbind :
crow::black_magic::is_equ_p(str, "ACL", 3) ? crow::HTTPMethod::Acl :
crow::black_magic::is_equ_p(str, "REPORT", 6) ? crow::HTTPMethod::Report :
crow::black_magic::is_equ_p(str, "MKACTIVITY", 10) ? crow::HTTPMethod::MkActivity :
crow::black_magic::is_equ_p(str, "CHECKOUT", 8) ? crow::HTTPMethod::Checkout :
crow::black_magic::is_equ_p(str, "MERGE", 5) ? crow::HTTPMethod::Merge :
crow::black_magic::is_equ_p(str, "MSEARCH", 7) ? crow::HTTPMethod::MSearch :
crow::black_magic::is_equ_p(str, "NOTIFY", 6) ? crow::HTTPMethod::Notify :
crow::black_magic::is_equ_p(str, "SUBSCRIBE", 9) ? crow::HTTPMethod::Subscribe :
crow::black_magic::is_equ_p(str, "UNSUBSCRIBE", 11) ? crow::HTTPMethod::Unsubscribe :
crow::black_magic::is_equ_p(str, "MKCALENDAR", 10) ? crow::HTTPMethod::MkCalendar :
crow::black_magic::is_equ_p(str, "LINK", 4) ? crow::HTTPMethod::Link :
crow::black_magic::is_equ_p(str, "UNLINK", 6) ? crow::HTTPMethod::Unlink :
crow::black_magic::is_equ_p(str, "SOURCE", 6) ? crow::HTTPMethod::Source :
throw std::runtime_error("invalid http method");
}
#ifndef CROW_MSVC_WORKAROUND
constexpr crow::HTTPMethod operator "" _method(const char* str, size_t /*len*/)
constexpr crow::HTTPMethod operator"" _method(const char* str, size_t /*len*/)
{
return
crow::black_magic::is_equ_p(str, "GET", 3) ? crow::HTTPMethod::Get :
crow::black_magic::is_equ_p(str, "DELETE", 6) ? crow::HTTPMethod::Delete :
crow::black_magic::is_equ_p(str, "HEAD", 4) ? crow::HTTPMethod::Head :
crow::black_magic::is_equ_p(str, "POST", 4) ? crow::HTTPMethod::Post :
crow::black_magic::is_equ_p(str, "PUT", 3) ? crow::HTTPMethod::Put :
crow::black_magic::is_equ_p(str, "OPTIONS", 7) ? crow::HTTPMethod::Options :
crow::black_magic::is_equ_p(str, "CONNECT", 7) ? crow::HTTPMethod::Connect :
crow::black_magic::is_equ_p(str, "TRACE", 5) ? crow::HTTPMethod::Trace :
crow::black_magic::is_equ_p(str, "PATCH", 5) ? crow::HTTPMethod::Patch :
crow::black_magic::is_equ_p(str, "PURGE", 5) ? crow::HTTPMethod::Purge :
throw std::runtime_error("invalid http method");
return method_from_string( str );
}
#endif
// clang-format on

View File

@ -16,10 +16,10 @@ namespace crow
DEFLATE = 15,
// windowBits can also be greater than 15 for optional gzip encoding.
// Add 16 to windowBits to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper.
GZIP = 15|16,
GZIP = 15 | 16,
};
inline std::string compress_string(std::string const & str, algorithm algo)
inline std::string compress_string(std::string const& str, algorithm algo)
{
std::string compressed_str;
z_stream stream{};
@ -30,13 +30,13 @@ namespace crow
stream.avail_in = str.size();
// zlib does not take a const pointer. The data is not altered.
stream.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(str.c_str()));
stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(str.c_str()));
int code = Z_OK;
do
{
stream.avail_out = sizeof(buffer);
stream.next_out = reinterpret_cast<Bytef *>(&buffer[0]);
stream.next_out = reinterpret_cast<Bytef*>(&buffer[0]);
code = ::deflate(&stream, Z_FINISH);
// Successful and non-fatal error code returned by deflate when used with Z_FINISH flush
@ -56,7 +56,7 @@ namespace crow
return compressed_str;
}
inline std::string decompress_string(std::string const & deflated_string)
inline std::string decompress_string(std::string const& deflated_string)
{
std::string inflated_string;
Bytef tmp[8192];
@ -64,7 +64,7 @@ namespace crow
z_stream zstream{};
zstream.avail_in = deflated_string.size();
// Nasty const_cast but zlib won't alter its contents
zstream.next_in = const_cast<Bytef *>(reinterpret_cast<Bytef const *>(deflated_string.c_str()));
zstream.next_in = const_cast<Bytef*>(reinterpret_cast<Bytef const*>(deflated_string.c_str()));
// Initialize with automatic header detection, for gzip support
if (::inflateInit2(&zstream, MAX_WBITS | 32) == Z_OK)
{
@ -93,7 +93,7 @@ namespace crow
return inflated_string;
}
}
}
} // namespace compression
} // namespace crow
#endif

View File

@ -1,83 +0,0 @@
#pragma once
#include <boost/asio.hpp>
#include <deque>
#include <functional>
#include <chrono>
#include <thread>
#include "crow/logging.h"
namespace crow
{
namespace detail
{
/// Fast timer queue for fixed tick value.
class dumb_timer_queue
{
public:
static int tick;
using key = std::pair<dumb_timer_queue*, int>;
void cancel(key& k)
{
auto self = k.first;
k.first = nullptr;
if (!self)
return;
unsigned int index = static_cast<unsigned>(k.second - self->step_);
if (index < self->dq_.size())
self->dq_[index].second = nullptr;
}
/// Add a function to the queue.
key add(std::function<void()> f)
{
dq_.emplace_back(std::chrono::steady_clock::now(), std::move(f));
int ret = step_+dq_.size()-1;
CROW_LOG_DEBUG << "timer add inside: " << this << ' ' << ret ;
return {this, ret};
}
/// Process the queue: take functions out in time intervals and execute them.
void process()
{
if (!io_service_)
return;
auto now = std::chrono::steady_clock::now();
while(!dq_.empty())
{
auto& x = dq_.front();
if (now - x.first < std::chrono::seconds(tick))
break;
if (x.second)
{
CROW_LOG_DEBUG << "timer call: " << this << ' ' << step_;
// we know that timer handlers are very simple currenty; call here
x.second();
}
dq_.pop_front();
step_++;
}
}
void set_io_service(boost::asio::io_service& io_service)
{
io_service_ = &io_service;
}
dumb_timer_queue() noexcept
{
}
private:
boost::asio::io_service* io_service_{};
std::deque<std::pair<decltype(std::chrono::steady_clock::now()), std::function<void()>>> dq_;
int step_{};
};
}
}

View File

@ -8,14 +8,14 @@
#include <vector>
#include "crow/http_parser_merged.h"
#include "crow/common.h"
#include "crow/parser.h"
#include "crow/http_response.h"
#include "crow/logging.h"
#include "crow/settings.h"
#include "crow/dumb_timer_queue.h"
#include "crow/task_timer.h"
#include "crow/middleware_context.h"
#include "crow/middleware.h"
#include "crow/socket_adaptors.h"
#include "crow/compression.h"
@ -24,200 +24,50 @@ namespace crow
using namespace boost;
using tcp = asio::ip::tcp;
namespace detail
{
template <typename MW>
struct check_before_handle_arity_3_const
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) const = &T::before_handle
>
struct get
{ };
};
template <typename MW>
struct check_before_handle_arity_3
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) = &T::before_handle
>
struct get
{ };
};
template <typename MW>
struct check_after_handle_arity_3_const
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) const = &T::after_handle
>
struct get
{ };
};
template <typename MW>
struct check_after_handle_arity_3
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) = &T::after_handle
>
struct get
{ };
};
template <typename T>
struct is_before_handle_arity_3_impl
{
template <typename C>
static std::true_type f(typename check_before_handle_arity_3_const<T>::template get<C>*);
template <typename C>
static std::true_type f(typename check_before_handle_arity_3<T>::template get<C>*);
template <typename C>
static std::false_type f(...);
public:
static const bool value = decltype(f<T>(nullptr))::value;
};
template <typename T>
struct is_after_handle_arity_3_impl
{
template <typename C>
static std::true_type f(typename check_after_handle_arity_3_const<T>::template get<C>*);
template <typename C>
static std::true_type f(typename check_after_handle_arity_3<T>::template get<C>*);
template <typename C>
static std::false_type f(...);
public:
static const bool value = decltype(f<T>(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*/)
{
mw.before_handle(req, res, ctx.template get<MW>(), ctx);
}
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*/)
{
mw.before_handle(req, res, ctx.template get<MW>());
}
template <typename MW, typename Context, typename ParentContext>
typename std::enable_if<!is_after_handle_arity_3_impl<MW>::value>::type
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
{
mw.after_handle(req, res, ctx.template get<MW>(), ctx);
}
template <typename MW, typename Context, typename ParentContext>
typename std::enable_if<is_after_handle_arity_3_impl<MW>::value>::type
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
{
mw.after_handle(req, res, ctx.template get<MW>());
}
template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
{
using parent_context_t = typename Context::template partial<N-1>;
before_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
if (res.is_completed())
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
return true;
}
if (middleware_call_helper<N+1, Context, Container, Middlewares...>(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;
}
return false;
}
template <int N, typename Context, typename Container>
bool middleware_call_helper(Container& /*middlewares*/, request& /*req*/, response& /*res*/, Context& /*ctx*/)
{
return false;
}
template <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*/)
{
}
template <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)
{
using parent_context_t = typename Context::template partial<N-1>;
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
}
template <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)
{
using parent_context_t = typename Context::template partial<N-1>;
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
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<N-1, Context, Container>(middlewares, ctx, req, res);
}
}
#ifdef CROW_ENABLE_DEBUG
static std::atomic<int> connectionCount;
#endif
/// An HTTP connection.
template <typename Adaptor, typename Handler, typename ... Middlewares>
template<typename Adaptor, typename Handler, typename... Middlewares>
class Connection
{
friend struct crow::response;
public:
Connection(
boost::asio::io_service& io_service,
Handler* handler,
const std::string& server_name,
std::tuple<Middlewares...>* middlewares,
std::function<std::string()>& get_cached_date_str_f,
detail::dumb_timer_queue& timer_queue,
typename Adaptor::context* adaptor_ctx_
)
: adaptor_(io_service, adaptor_ctx_),
handler_(handler),
parser_(this),
server_name_(server_name),
middlewares_(middlewares),
get_cached_date_str(get_cached_date_str_f),
timer_queue(timer_queue),
res_stream_threshold_(handler->stream_threshold())
boost::asio::io_service& io_service,
Handler* handler,
const std::string& server_name,
std::tuple<Middlewares...>* middlewares,
std::function<std::string()>& get_cached_date_str_f,
detail::task_timer& task_timer,
typename Adaptor::context* adaptor_ctx_,
std::atomic<unsigned int>& queue_length):
adaptor_(io_service, adaptor_ctx_),
handler_(handler),
parser_(this),
server_name_(server_name),
middlewares_(middlewares),
get_cached_date_str(get_cached_date_str_f),
task_timer_(task_timer),
res_stream_threshold_(handler->stream_threshold()),
queue_length_(queue_length)
{
#ifdef CROW_ENABLE_DEBUG
connectionCount ++;
CROW_LOG_DEBUG << "Connection open, total " << connectionCount << ", " << this;
connectionCount++;
CROW_LOG_DEBUG << "Connection (" << this << ") allocated, total: " << connectionCount;
#endif
}
~Connection()
{
res.complete_request_handler_ = nullptr;
cancel_deadline_timer();
#ifdef CROW_ENABLE_DEBUG
connectionCount --;
CROW_LOG_DEBUG << "Connection closed, total " << connectionCount << ", " << this;
connectionCount--;
CROW_LOG_DEBUG << "Connection (" << this << ") freed, total: " << connectionCount;
#endif
}
@ -247,7 +97,7 @@ namespace crow
void handle_header()
{
// HTTP 1.1 Expect: 100-continue
if (parser_.check_version(1, 1) && parser_.headers.count("expect") && get_header_value(parser_.headers, "expect") == "100-continue")
if (parser_.http_major == 1 && parser_.http_minor == 1 && get_header_value(parser_.headers, "expect") == "100-continue") // Using the parser because the request isn't made yet.
{
buffers_.clear();
static std::string expect_100_continue = "HTTP/1.1 100 Continue\r\n\r\n";
@ -267,66 +117,57 @@ namespace crow
req.remote_ip_address = adaptor_.remote_endpoint().address().to_string();
if (parser_.check_version(1, 0))
add_keep_alive_ = req.keep_alive;
close_connection_ = req.close_connection;
if (req.check_version(1, 1)) // HTTP/1.1
{
// HTTP/1.0
if (req.headers.count("connection"))
{
if (boost::iequals(req.get_header_value("connection"),"Keep-Alive"))
add_keep_alive_ = true;
}
else
close_connection_ = true;
}
else if (parser_.check_version(1, 1))
{
// HTTP/1.1
if (req.headers.count("connection"))
{
if (req.get_header_value("connection") == "close")
close_connection_ = true;
else if (boost::iequals(req.get_header_value("connection"),"Keep-Alive"))
add_keep_alive_ = true;
}
if (!req.headers.count("host"))
{
is_invalid_request = true;
res = response(400);
}
if (parser_.is_upgrade())
{
if (req.get_header_value("upgrade") == "h2c")
{
// TODO HTTP/2
if (req.upgrade)
{
// h2 or h2c headers
if (req.get_header_value("upgrade").substr(0, 2) == "h2")
{
// TODO(ipkn): HTTP/2
// currently, ignore upgrade header
}
}
else
{
close_connection_ = true;
handler_->handle_upgrade(req, res, std::move(adaptor_));
return;
}
}
}
}
CROW_LOG_INFO << "Request: " << boost::lexical_cast<std::string>(adaptor_.remote_endpoint()) << " " << this << " HTTP/" << parser_.http_major << "." << parser_.http_minor << ' '
<< method_name(req.method) << " " << req.url;
CROW_LOG_INFO << "Request: " << boost::lexical_cast<std::string>(adaptor_.remote_endpoint()) << " " << this << " HTTP/" << (char)(req.http_ver_major + '0') << "." << (char)(req.http_ver_minor + '0') << ' ' << method_name(req.method) << " " << req.url;
need_to_call_after_handlers_ = false;
if (!is_invalid_request)
{
res.complete_request_handler_ = []{};
res.is_alive_helper_ = [this]()->bool{ return adaptor_.is_open(); };
res.complete_request_handler_ = [] {};
res.is_alive_helper_ = [this]() -> bool {
return adaptor_.is_open();
};
ctx_ = detail::context<Middlewares...>();
req.middleware_context = static_cast<void*>(&ctx_);
req.middleware_container = static_cast<void*>(middlewares_);
req.io_service = &adaptor_.get_io_service();
detail::middleware_call_helper<0, decltype(ctx_), decltype(*middlewares_), Middlewares...>(*middlewares_, req, res, ctx_);
detail::middleware_call_helper<detail::middleware_call_criteria_only_global,
0, decltype(ctx_), decltype(*middlewares_)>(*middlewares_, req, res, ctx_);
if (!res.completed_)
{
res.complete_request_handler_ = [this]{ this->complete_request(); };
res.complete_request_handler_ = [this] {
this->complete_request();
};
need_to_call_after_handlers_ = true;
handler_->handle(req, res);
if (add_keep_alive_)
@ -354,13 +195,14 @@ namespace crow
// call all after_handler of middlewares
detail::after_handlers_call_helper<
(static_cast<int>(sizeof...(Middlewares))-1),
decltype(ctx_),
decltype(*middlewares_)>
(*middlewares_, ctx_, req_, res);
detail::middleware_call_criteria_only_global,
(static_cast<int>(sizeof...(Middlewares)) - 1),
decltype(ctx_),
decltype(*middlewares_)>(*middlewares_, ctx_, req_, res);
}
#ifdef CROW_ENABLE_COMPRESSION
if (handler_->compression_used()) {
if (handler_->compression_used())
{
std::string accept_encoding = req_.get_header_value("Accept-Encoding");
if (!accept_encoding.empty() && res.compressed)
{
@ -390,27 +232,28 @@ namespace crow
std::string location = res.get_header_value("Location");
if (!location.empty() && location.find("://", 0) == std::string::npos)
{
#ifdef CROW_ENABLE_SSL
location.insert(0, "https://" + req_.get_header_value("Host"));
#else
location.insert(0, "http://" + req_.get_header_value("Host"));
#endif
#ifdef CROW_ENABLE_SSL
if (handler_->ssl_used())
location.insert(0, "https://" + req_.get_header_value("Host"));
else
#endif
location.insert(0, "http://" + req_.get_header_value("Host"));
res.set_header("location", location);
}
prepare_buffers();
prepare_buffers();
if (res.is_static_type())
{
do_write_static();
}else {
}
else
{
do_write_general();
}
}
private:
void prepare_buffers()
{
//auto self = this->shared_from_this();
@ -422,56 +265,56 @@ namespace crow
//delete this;
return;
}
// TODO(EDev): HTTP version in status codes should be dynamic
// Keep in sync with common.h/status
static std::unordered_map<int, std::string> statusCodes = {
{status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
{status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
{status::CONTINUE, "HTTP/1.1 100 Continue\r\n"},
{status::SWITCHING_PROTOCOLS, "HTTP/1.1 101 Switching Protocols\r\n"},
{status::OK, "HTTP/1.1 200 OK\r\n"},
{status::CREATED, "HTTP/1.1 201 Created\r\n"},
{status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
{status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
{status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
{status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
{status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
{status::OK, "HTTP/1.1 200 OK\r\n"},
{status::CREATED, "HTTP/1.1 201 Created\r\n"},
{status::ACCEPTED, "HTTP/1.1 202 Accepted\r\n"},
{status::NON_AUTHORITATIVE_INFORMATION, "HTTP/1.1 203 Non-Authoritative Information\r\n"},
{status::NO_CONTENT, "HTTP/1.1 204 No Content\r\n"},
{status::RESET_CONTENT, "HTTP/1.1 205 Reset Content\r\n"},
{status::PARTIAL_CONTENT, "HTTP/1.1 206 Partial Content\r\n"},
{status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
{status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
{status::FOUND, "HTTP/1.1 302 Found\r\n"},
{status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
{status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
{status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
{status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
{status::MULTIPLE_CHOICES, "HTTP/1.1 300 Multiple Choices\r\n"},
{status::MOVED_PERMANENTLY, "HTTP/1.1 301 Moved Permanently\r\n"},
{status::FOUND, "HTTP/1.1 302 Found\r\n"},
{status::SEE_OTHER, "HTTP/1.1 303 See Other\r\n"},
{status::NOT_MODIFIED, "HTTP/1.1 304 Not Modified\r\n"},
{status::TEMPORARY_REDIRECT, "HTTP/1.1 307 Temporary Redirect\r\n"},
{status::PERMANENT_REDIRECT, "HTTP/1.1 308 Permanent Redirect\r\n"},
{status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
{status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
{status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
{status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
{status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
{status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
{status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
{status::GONE, "HTTP/1.1 410 Gone\r\n"},
{status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
{status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
{status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
{status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
{status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
{status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
{status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
{status::BAD_REQUEST, "HTTP/1.1 400 Bad Request\r\n"},
{status::UNAUTHORIZED, "HTTP/1.1 401 Unauthorized\r\n"},
{status::FORBIDDEN, "HTTP/1.1 403 Forbidden\r\n"},
{status::NOT_FOUND, "HTTP/1.1 404 Not Found\r\n"},
{status::METHOD_NOT_ALLOWED, "HTTP/1.1 405 Method Not Allowed\r\n"},
{status::PROXY_AUTHENTICATION_REQUIRED, "HTTP/1.1 407 Proxy Authentication Required\r\n"},
{status::CONFLICT, "HTTP/1.1 409 Conflict\r\n"},
{status::GONE, "HTTP/1.1 410 Gone\r\n"},
{status::PAYLOAD_TOO_LARGE, "HTTP/1.1 413 Payload Too Large\r\n"},
{status::UNSUPPORTED_MEDIA_TYPE, "HTTP/1.1 415 Unsupported Media Type\r\n"},
{status::RANGE_NOT_SATISFIABLE, "HTTP/1.1 416 Range Not Satisfiable\r\n"},
{status::EXPECTATION_FAILED, "HTTP/1.1 417 Expectation Failed\r\n"},
{status::PRECONDITION_REQUIRED, "HTTP/1.1 428 Precondition Required\r\n"},
{status::TOO_MANY_REQUESTS, "HTTP/1.1 429 Too Many Requests\r\n"},
{status::UNAVAILABLE_FOR_LEGAL_REASONS, "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"},
{status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
{status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
{status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
{status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
{status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
{status::INTERNAL_SERVER_ERROR, "HTTP/1.1 500 Internal Server Error\r\n"},
{status::NOT_IMPLEMENTED, "HTTP/1.1 501 Not Implemented\r\n"},
{status::BAD_GATEWAY, "HTTP/1.1 502 Bad Gateway\r\n"},
{status::SERVICE_UNAVAILABLE, "HTTP/1.1 503 Service Unavailable\r\n"},
{status::GATEWAY_TIMEOUT, "HTTP/1.1 504 Gateway Timeout\r\n"},
{status::VARIANT_ALSO_NEGOTIATES, "HTTP/1.1 506 Variant Also Negotiates\r\n"},
};
static std::string seperator = ": ";
static std::string crlf = "\r\n";
static const std::string seperator = ": ";
buffers_.clear();
buffers_.reserve(4*(res.headers.size()+5)+3);
buffers_.reserve(4 * (res.headers.size() + 5) + 3);
if (!statusCodes.count(res.code))
res.code = 500;
@ -483,13 +326,12 @@ namespace crow
if (res.code >= 400 && res.body.empty())
res.body = statusCodes[res.code].substr(9);
for(auto& kv : res.headers)
for (auto& kv : res.headers)
{
buffers_.emplace_back(kv.first.data(), kv.first.size());
buffers_.emplace_back(seperator.data(), seperator.size());
buffers_.emplace_back(kv.second.data(), kv.second.size());
buffers_.emplace_back(crlf.data(), crlf.size());
}
if (!res.manual_length_header && !res.headers.count("content-length"))
@ -523,14 +365,32 @@ namespace crow
}
buffers_.emplace_back(crlf.data(), crlf.size());
}
void do_write_static()
{
is_writing = true;
boost::asio::write(adaptor_.socket(), buffers_);
res.do_stream_file(adaptor_);
if (res.file_info.statResult == 0)
{
std::ifstream is(res.file_info.path.c_str(), std::ios::in | std::ios::binary);
char buf[16384];
while (is.read(buf, sizeof(buf)).gcount() > 0)
{
std::vector<asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(buf));
do_write_sync(buffers);
}
}
is_writing = false;
if (close_connection_)
{
adaptor_.shutdown_readwrite();
adaptor_.close();
CROW_LOG_DEBUG << this << " from write (static)";
check_destroy();
}
res.end();
res.clear();
@ -556,8 +416,39 @@ namespace crow
else
{
is_writing = true;
boost::asio::write(adaptor_.socket(), buffers_);
res.do_stream_body(adaptor_);
boost::asio::write(adaptor_.socket(), buffers_); // Write the response start / headers
cancel_deadline_timer();
if (res.body.length() > 0)
{
std::string buf;
std::vector<asio::const_buffer> buffers;
while (res.body.length() > 16384)
{
//buf.reserve(16385);
buf = res.body.substr(0, 16384);
res.body = res.body.substr(16384);
buffers.clear();
buffers.push_back(boost::asio::buffer(buf));
do_write_sync(buffers);
}
// Collect whatever is left (less than 16KB) and send it down the socket
// buf.reserve(is.length());
buf = res.body;
res.body.clear();
buffers.clear();
buffers.push_back(boost::asio::buffer(buf));
do_write_sync(buffers);
}
is_writing = false;
if (close_connection_)
{
adaptor_.shutdown_readwrite();
adaptor_.close();
CROW_LOG_DEBUG << this << " from write (res_stream)";
check_destroy();
}
res.end();
res.clear();
@ -569,76 +460,94 @@ namespace crow
{
//auto self = this->shared_from_this();
is_reading = true;
adaptor_.socket().async_read_some(boost::asio::buffer(buffer_),
[this](const boost::system::error_code& ec, std::size_t bytes_transferred)
{
bool error_while_reading = true;
if (!ec)
{
bool ret = parser_.feed(buffer_.data(), bytes_transferred);
if (ret && adaptor_.is_open())
{
error_while_reading = false;
}
}
adaptor_.socket().async_read_some(
boost::asio::buffer(buffer_),
[this](const boost::system::error_code& ec, std::size_t bytes_transferred) {
bool error_while_reading = true;
if (!ec)
{
bool ret = parser_.feed(buffer_.data(), bytes_transferred);
if (ret && adaptor_.is_open())
{
error_while_reading = false;
}
}
if (error_while_reading)
{
cancel_deadline_timer();
parser_.done();
adaptor_.shutdown_read();
adaptor_.close();
is_reading = false;
CROW_LOG_DEBUG << this << " from read(1)";
check_destroy();
}
else if (close_connection_)
{
cancel_deadline_timer();
parser_.done();
is_reading = false;
check_destroy();
// adaptor will close after write
}
else if (!need_to_call_after_handlers_)
{
start_deadline();
do_read();
}
else
{
// res will be completed later by user
need_to_start_read_after_complete_ = true;
}
});
if (error_while_reading)
{
cancel_deadline_timer();
parser_.done();
adaptor_.shutdown_read();
adaptor_.close();
is_reading = false;
CROW_LOG_DEBUG << this << " from read(1) with description: \"" << http_errno_description(static_cast<http_errno>(parser_.http_errno)) << '\"';
check_destroy();
}
else if (close_connection_)
{
cancel_deadline_timer();
parser_.done();
is_reading = false;
check_destroy();
// adaptor will close after write
}
else if (!need_to_call_after_handlers_)
{
start_deadline();
do_read();
}
else
{
// res will be completed later by user
need_to_start_read_after_complete_ = true;
}
});
}
void do_write()
{
//auto self = this->shared_from_this();
is_writing = true;
boost::asio::async_write(adaptor_.socket(), buffers_,
[&](const boost::system::error_code& ec, std::size_t /*bytes_transferred*/)
boost::asio::async_write(
adaptor_.socket(), buffers_,
[&](const boost::system::error_code& ec, std::size_t /*bytes_transferred*/) {
is_writing = false;
res.clear();
res_body_copy_.clear();
if (!ec)
{
if (close_connection_)
{
adaptor_.shutdown_write();
adaptor_.close();
CROW_LOG_DEBUG << this << " from write(1)";
check_destroy();
}
}
else
{
CROW_LOG_DEBUG << this << " from write(2)";
check_destroy();
}
});
}
inline void do_write_sync(std::vector<asio::const_buffer>& buffers)
{
boost::asio::write(adaptor_.socket(), buffers, [&](std::error_code ec, std::size_t) {
if (!ec)
{
is_writing = false;
res.clear();
res_body_copy_.clear();
if (!ec)
{
if (close_connection_)
{
adaptor_.shutdown_write();
adaptor_.close();
CROW_LOG_DEBUG << this << " from write(1)";
check_destroy();
}
}
else
{
CROW_LOG_DEBUG << this << " from write(2)";
check_destroy();
}
});
return false;
}
else
{
CROW_LOG_ERROR << ec << " - happened while sending buffers";
CROW_LOG_DEBUG << this << " from write (sync)(2)";
check_destroy();
return true;
}
});
}
void check_destroy()
@ -646,23 +555,23 @@ namespace crow
CROW_LOG_DEBUG << this << " is_reading " << is_reading << " is_writing " << is_writing;
if (!is_reading && !is_writing)
{
CROW_LOG_DEBUG << this << " delete (idle) ";
queue_length_--;
CROW_LOG_DEBUG << this << " delete (idle) (queue length: " << queue_length_ << ')';
delete this;
}
}
void cancel_deadline_timer()
{
CROW_LOG_DEBUG << this << " timer cancelled: " << timer_cancel_key_.first << ' ' << timer_cancel_key_.second;
timer_queue.cancel(timer_cancel_key_);
CROW_LOG_DEBUG << this << " timer cancelled: " << &task_timer_ << ' ' << task_id_;
task_timer_.cancel(task_id_);
}
void start_deadline(/*int timeout = 5*/)
{
cancel_deadline_timer();
timer_cancel_key_ = timer_queue.add([this]
{
task_id_ = task_timer_.schedule([this] {
if (!adaptor_.is_open())
{
return;
@ -670,7 +579,7 @@ namespace crow
adaptor_.shutdown_readwrite();
adaptor_.close();
});
CROW_LOG_DEBUG << this << " timer added: " << timer_cancel_key_.first << ' ' << timer_cancel_key_.second;
CROW_LOG_DEBUG << this << " timer added: " << &task_timer_ << ' ' << task_id_;
}
private:
@ -692,8 +601,7 @@ namespace crow
std::string date_str_;
std::string res_body_copy_;
//boost::asio::deadline_timer deadline_;
detail::dumb_timer_queue::key timer_cancel_key_;
detail::task_timer::identifier_type task_id_;
bool is_reading{};
bool is_writing{};
@ -705,9 +613,11 @@ namespace crow
detail::context<Middlewares...> ctx_;
std::function<std::string()>& get_cached_date_str;
detail::dumb_timer_queue& timer_queue;
detail::task_timer& task_timer_;
size_t res_stream_threshold_;
std::atomic<unsigned int>& queue_length_;
};
}
} // namespace crow

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
namespace crow
{
/// Find and return the value associated with the key. (returns an empty string if nothing is found)
template <typename T>
template<typename T>
inline const std::string& get_header_value(const T& headers, const std::string& key)
{
if (headers.count(key))
@ -20,33 +20,32 @@ namespace crow
return empty;
}
struct DetachHelper;
/// An HTTP request.
struct request
{
HTTPMethod method;
std::string raw_url; ///< The full URL containing the `?` and URL parameters.
std::string url; ///< The endpoint without any parameters.
std::string raw_url; ///< The full URL containing the `?` and URL parameters.
std::string url; ///< The endpoint without any parameters.
query_string url_params; ///< The parameters associated with the request. (everything after the `?`)
ci_map headers;
std::string body;
std::string remote_ip_address; ///< The IP address from which the request was sent.
unsigned char http_ver_major, http_ver_minor;
bool keep_alive, close_connection, upgrade;
void* middleware_context{};
void* middleware_container{};
boost::asio::io_service* io_service{};
/// Construct an empty request. (sets the method to `GET`)
request()
: method(HTTPMethod::Get)
{
}
request():
method(HTTPMethod::Get)
{}
/// Construct a request with all values assigned.
request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body)
: method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body))
{
}
request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body, unsigned char http_major, unsigned char http_minor, bool has_keep_alive, bool has_close_connection, bool is_upgrade):
method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body)), http_ver_major(http_major), http_ver_minor(http_minor), keep_alive(has_keep_alive), close_connection(has_close_connection), upgrade(is_upgrade)
{}
void add_header(std::string key, std::string value)
{
@ -58,19 +57,23 @@ namespace crow
return crow::get_header_value(headers, key);
}
/// Send the request with a completion handler and return immediately.
bool check_version(unsigned char major, unsigned char minor) const
{
return http_ver_major == major && http_ver_minor == minor;
}
/// Send data to whoever made this request with a completion handler and return immediately.
template<typename CompletionHandler>
void post(CompletionHandler handler)
{
io_service->post(handler);
}
/// Send the request with a completion handler.
/// Send data to whoever made this request with a completion handler.
template<typename CompletionHandler>
void dispatch(CompletionHandler handler)
{
io_service->dispatch(handler);
}
};
}
} // namespace crow

View File

@ -16,23 +16,32 @@
namespace crow
{
template <typename Adaptor, typename Handler, typename ... Middlewares>
template<typename Adaptor, typename Handler, typename... Middlewares>
class Connection;
namespace detail
{
template<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper;
} // namespace detail
/// HTTP response
struct response
{
template <typename Adaptor, typename Handler, typename ... Middlewares>
template<typename Adaptor, typename Handler, typename... Middlewares>
friend class crow::Connection;
int code{200}; ///< The Status code for the response.
template<typename F, typename App, typename... Middlewares>
friend struct crow::detail::handler_middleware_wrapper;
int code{200}; ///< The Status code for the response.
std::string body; ///< The actual payload containing the response data.
ci_map headers; ///< HTTP headers.
ci_map headers; ///< HTTP headers.
#ifdef CROW_ENABLE_COMPRESSION
bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
#endif
bool is_head_response = false; ///< Whether this is a response to a HEAD request.
bool skip_body = false; ///< Whether this is a response to a HEAD request.
bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
/// Set the value of an existing header in the response.
@ -53,45 +62,49 @@ namespace crow
return crow::get_header_value(headers, key);
}
// clang-format off
response() {}
explicit response(int code) : code(code) {}
response(std::string body) : body(std::move(body)) {}
response(int code, std::string body) : code(code), body(std::move(body)) {}
response (returnable&& value)
// clang-format on
response(returnable&& value)
{
body = value.dump();
set_header("Content-Type",value.content_type);
set_header("Content-Type", value.content_type);
}
response (returnable& value)
response(returnable& value)
{
body = value.dump();
set_header("Content-Type",value.content_type);
set_header("Content-Type", value.content_type);
}
response (int code, returnable& value) : code(code)
response(int code, returnable& value):
code(code)
{
body = value.dump();
set_header("Content-Type",value.content_type);
set_header("Content-Type", value.content_type);
}
response(response&& r)
{
*this = std::move(r);
}
response(std::string contentType, std::string body) : body(std::move(body))
response(std::string contentType, std::string body):
body(std::move(body))
{
set_header("Content-Type",mime_types[contentType]);
set_header("Content-Type", mime_types.at(contentType));
}
response(int code, std::string contentType, std::string body): code(code),body(std::move(body))
response(int code, std::string contentType, std::string body):
code(code), body(std::move(body))
{
set_header("Content-Type",mime_types[contentType]);
set_header("Content-Type", mime_types.at(contentType));
}
response& operator = (const response& r) = delete;
response& operator=(const response& r) = delete;
response& operator = (response&& r) noexcept
response& operator=(response&& r) noexcept
{
body = std::move(r.body);
code = r.code;
@ -117,6 +130,7 @@ namespace crow
}
/// Return a "Temporary Redirect" response.
///
/// Location can either be a route or a full URL.
void redirect(const std::string& location)
@ -126,6 +140,7 @@ namespace crow
}
/// Return a "Permanent Redirect" response.
///
/// Location can either be a route or a full URL.
void redirect_perm(const std::string& location)
@ -135,6 +150,7 @@ namespace crow
}
/// Return a "Found (Moved Temporarily)" response.
///
/// Location can either be a route or a full URL.
void moved(const std::string& location)
@ -144,6 +160,7 @@ namespace crow
}
/// Return a "Moved Permanently" response.
///
/// Location can either be a route or a full URL.
void moved_perm(const std::string& location)
@ -163,7 +180,7 @@ namespace crow
if (!completed_)
{
completed_ = true;
if (is_head_response)
if (skip_body)
{
set_header("Content-Length", std::to_string(body.size()));
body = "";
@ -197,16 +214,25 @@ namespace crow
/// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
/// Either a static file or a string body can be returned as 1 response.
///
struct static_file_info{
/// Either a static file or a string body can be returned as 1 response.
struct static_file_info
{
std::string path = "";
struct stat statbuf;
int statResult;
};
///Return a static file as the response body
void set_static_file_info(std::string path){
/// Return a static file as the response body
void set_static_file_info(std::string path)
{
utility::sanitize_filename(path);
set_static_file_info_unsafe(path);
}
/// Return a static file as the response body without sanitizing the path (use set_static_file_info instead)
void set_static_file_info_unsafe(std::string path)
{
file_info.path = path;
file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
#ifdef CROW_ENABLE_COMPRESSION
@ -215,111 +241,35 @@ namespace crow
if (file_info.statResult == 0)
{
std::size_t last_dot = path.find_last_of(".");
std::string extension = path.substr(last_dot+1);
std::string mimeType = "";
std::string extension = path.substr(last_dot + 1);
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 != ""){
mimeType = mime_types[extension];
if (mimeType != "")
this-> add_header("Content-Type", mimeType);
if (!extension.empty())
{
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
{
code = 404;
file_info.path.clear();
this->end();
}
}
/// Stream a static file.
template<typename Adaptor>
void do_stream_file(Adaptor& adaptor)
{
if (file_info.statResult == 0)
{
std::ifstream is(file_info.path.c_str(), std::ios::in | std::ios::binary);
write_streamed(is, adaptor);
}
}
/// Stream the response body (send the body in chunks).
template<typename Adaptor>
void do_stream_body(Adaptor& adaptor)
{
if (body.length() > 0)
{
write_streamed_string(body, adaptor);
}
}
private:
bool completed_{};
std::function<void()> complete_request_handler_;
std::function<bool()> is_alive_helper_;
static_file_info file_info;
template<typename Stream, typename Adaptor>
void write_streamed(Stream& is, Adaptor& adaptor)
{
char buf[16384];
while (is.read(buf, sizeof(buf)).gcount() > 0)
{
std::vector<asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(buf));
write_buffer_list(buffers, adaptor);
}
}
//THIS METHOD DOES MODIFY THE BODY, AS IN IT EMPTIES IT
template<typename Adaptor>
void write_streamed_string(std::string& is, Adaptor& adaptor)
{
std::string buf;
std::vector<asio::const_buffer> buffers;
while (is.length() > 16384)
{
//buf.reserve(16385);
buf = is.substr(0, 16384);
is = is.substr(16384);
push_and_write(buffers, buf, adaptor);
}
//Collect whatever is left (less than 16KB) and send it down the socket
//buf.reserve(is.length());
buf = is;
is.clear();
push_and_write(buffers, buf, adaptor);
}
template<typename Adaptor>
inline void push_and_write(std::vector<asio::const_buffer>& buffers, std::string& buf, Adaptor& adaptor)
{
buffers.clear();
buffers.push_back(boost::asio::buffer(buf));
write_buffer_list(buffers, adaptor);
}
template<typename Adaptor>
inline void write_buffer_list(std::vector<asio::const_buffer>& buffers, Adaptor& adaptor)
{
boost::asio::write(adaptor.socket(), buffers, [this](std::error_code ec, std::size_t)
{
if (!ec)
{
return false;
}
else
{
CROW_LOG_ERROR << ec << " - happened while sending buffers";
this->end();
return true;
}
});
}
private:
bool completed_{};
std::function<void()> complete_request_handler_;
std::function<bool()> is_alive_helper_;
static_file_info file_info;
};
}
} // namespace crow

View File

@ -10,36 +10,36 @@
#include <atomic>
#include <future>
#include <vector>
#include <memory>
#include "crow/version.h"
#include "crow/http_connection.h"
#include "crow/logging.h"
#include "crow/dumb_timer_queue.h"
#include "crow/task_timer.h"
namespace crow
{
using namespace boost;
using tcp = asio::ip::tcp;
template <typename Handler, typename Adaptor = SocketAdaptor, typename ... Middlewares>
template<typename Handler, typename Adaptor = SocketAdaptor, typename... Middlewares>
class Server
{
public:
Server(Handler* handler, std::string bindaddr, uint16_t port, std::string server_name = std::string("Crow/") + VERSION, std::tuple<Middlewares...>* middlewares = nullptr, uint16_t concurrency = 1, typename Adaptor::context* adaptor_ctx = nullptr)
: acceptor_(io_service_, tcp::endpoint(boost::asio::ip::address::from_string(bindaddr), port)),
signals_(io_service_),
tick_timer_(io_service_),
handler_(handler),
concurrency_(concurrency == 0 ? 1 : concurrency),
server_name_(server_name),
port_(port),
bindaddr_(bindaddr),
middlewares_(middlewares),
adaptor_ctx_(adaptor_ctx)
{
}
Server(Handler* handler, std::string bindaddr, uint16_t port, std::string server_name = std::string("Crow/") + VERSION, std::tuple<Middlewares...>* middlewares = nullptr, uint16_t concurrency = 1, uint8_t timeout = 5, typename Adaptor::context* adaptor_ctx = nullptr):
acceptor_(io_service_, tcp::endpoint(boost::asio::ip::address::from_string(bindaddr), port)),
signals_(io_service_),
tick_timer_(io_service_),
handler_(handler),
concurrency_(concurrency),
timeout_(timeout),
server_name_(server_name),
port_(port),
bindaddr_(bindaddr),
task_queue_length_pool_(concurrency_ - 1),
middlewares_(middlewares),
adaptor_ctx_(adaptor_ctx)
{}
void set_tick_function(std::chrono::milliseconds d, std::function<void()> f)
{
@ -51,120 +51,112 @@ namespace crow
{
tick_function_();
tick_timer_.expires_from_now(boost::posix_time::milliseconds(tick_interval_.count()));
tick_timer_.async_wait([this](const boost::system::error_code& ec)
{
if (ec)
return;
on_tick();
});
tick_timer_.async_wait([this](const boost::system::error_code& ec) {
if (ec)
return;
on_tick();
});
}
void run()
{
for(int i = 0; i < concurrency_; i++)
uint16_t worker_thread_count = concurrency_ - 1;
for (int i = 0; i < worker_thread_count; i++)
io_service_pool_.emplace_back(new boost::asio::io_service());
get_cached_date_str_pool_.resize(concurrency_);
timer_queue_pool_.resize(concurrency_);
get_cached_date_str_pool_.resize(worker_thread_count);
task_timer_pool_.resize(worker_thread_count);
std::vector<std::future<void>> v;
std::atomic<int> init_count(0);
for(uint16_t i = 0; i < concurrency_; i ++)
for (uint16_t i = 0; i < worker_thread_count; i++)
v.push_back(
std::async(std::launch::async, [this, i, &init_count]{
std::async(
std::launch::async, [this, i, &init_count] {
// thread local date string get function
auto last = std::chrono::steady_clock::now();
// thread local date string get function
auto last = std::chrono::steady_clock::now();
std::string date_str;
auto update_date_str = [&]
{
auto last_time_t = time(0);
tm my_tm;
std::string date_str;
auto update_date_str = [&] {
auto last_time_t = time(0);
tm my_tm;
#if defined(_MSC_VER) || defined(__MINGW32__)
gmtime_s(&my_tm, &last_time_t);
gmtime_s(&my_tm, &last_time_t);
#else
gmtime_r(&last_time_t, &my_tm);
gmtime_r(&last_time_t, &my_tm);
#endif
date_str.resize(100);
size_t date_str_sz = strftime(&date_str[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &my_tm);
date_str.resize(date_str_sz);
};
update_date_str();
get_cached_date_str_pool_[i] = [&]()->std::string
date_str.resize(100);
size_t date_str_sz = strftime(&date_str[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &my_tm);
date_str.resize(date_str_sz);
};
update_date_str();
get_cached_date_str_pool_[i] = [&]() -> std::string {
if (std::chrono::steady_clock::now() - last >= std::chrono::seconds(1))
{
if (std::chrono::steady_clock::now() - last >= std::chrono::seconds(1))
{
last = std::chrono::steady_clock::now();
update_date_str();
}
return date_str;
};
last = std::chrono::steady_clock::now();
update_date_str();
}
return date_str;
};
// initializing timer queue
detail::dumb_timer_queue timer_queue;
timer_queue_pool_[i] = &timer_queue;
// initializing task timers
detail::task_timer task_timer(*io_service_pool_[i]);
task_timer.set_default_timeout(timeout_);
task_timer_pool_[i] = &task_timer;
task_queue_length_pool_[i] = 0;
timer_queue.set_io_service(*io_service_pool_[i]);
boost::asio::deadline_timer timer(*io_service_pool_[i]);
timer.expires_from_now(boost::posix_time::seconds(1));
std::function<void(const boost::system::error_code& ec)> handler;
handler = [&](const boost::system::error_code& ec){
if (ec)
return;
timer_queue.process();
timer.expires_from_now(boost::posix_time::seconds(1));
timer.async_wait(handler);
};
timer.async_wait(handler);
init_count ++;
while(1)
init_count++;
while (1)
{
try
{
try
if (io_service_pool_[i]->run() == 0)
{
if (io_service_pool_[i]->run() == 0)
{
// when io_service.run returns 0, there are no more works to do.
break;
}
} catch(std::exception& e)
{
CROW_LOG_ERROR << "Worker Crash: An uncaught exception occurred: " << e.what();
// when io_service.run returns 0, there are no more works to do.
break;
}
}
}));
catch (std::exception& e)
{
CROW_LOG_ERROR << "Worker Crash: An uncaught exception occurred: " << e.what();
}
}
}));
if (tick_function_ && tick_interval_.count() > 0)
{
tick_timer_.expires_from_now(boost::posix_time::milliseconds(tick_interval_.count()));
tick_timer_.async_wait([this](const boost::system::error_code& ec)
{
if (ec)
return;
on_tick();
});
tick_timer_.async_wait(
[this](const boost::system::error_code& ec) {
if (ec)
return;
on_tick();
});
}
CROW_LOG_INFO << server_name_ << " server is running at " << bindaddr_ <<":" << acceptor_.local_endpoint().port()
<< " using " << concurrency_ << " threads";
port_ = acceptor_.local_endpoint().port();
handler_->port(port_);
CROW_LOG_INFO << server_name_ << " server is running at " << (handler_->ssl_used() ? "https://" : "http://") << bindaddr_ << ":" << acceptor_.local_endpoint().port() << " using " << concurrency_ << " threads";
CROW_LOG_INFO << "Call `app.loglevel(crow::LogLevel::Warning)` to hide Info level logs.";
signals_.async_wait(
[&](const boost::system::error_code& /*error*/, int /*signal_number*/){
stop();
});
[&](const boost::system::error_code& /*error*/, int /*signal_number*/) {
stop();
});
while(concurrency_ != init_count)
while (worker_thread_count != init_count)
std::this_thread::yield();
do_accept();
std::thread([this]{
io_service_.run();
CROW_LOG_INFO << "Exiting.";
}).join();
std::thread(
[this] {
io_service_.run();
CROW_LOG_INFO << "Exiting.";
})
.join();
}
void stop()
@ -173,7 +165,7 @@ namespace crow
while (handler_->websocket_count.load(std::memory_order_release) != 0) //Wait for the websockets to close properly
{
}
for(auto& io_service:io_service_pool_)
for(auto& io_service : io_service_pool_)
{
if (io_service != nullptr)
{
@ -197,47 +189,57 @@ namespace crow
}
private:
asio::io_service& pick_io_service()
uint16_t pick_io_service_idx()
{
// TODO load balancing
roundrobin_index_++;
if (roundrobin_index_ >= io_service_pool_.size())
roundrobin_index_ = 0;
return *io_service_pool_[roundrobin_index_];
uint16_t min_queue_idx = 0;
// TODO improve load balancing
// 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])
min_queue_idx = i;
}
return min_queue_idx;
}
void do_accept()
{
asio::io_service& is = pick_io_service();
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_[roundrobin_index_], *timer_queue_pool_[roundrobin_index_],
adaptor_ctx_);
if (!should_close_)
{
acceptor_.async_accept(p->socket(),
[this, p, &is](boost::system::error_code ec)
{
if (!ec)
{
is.post([p]
{
p->start();
});
}
else
{
delete p;
}
do_accept();
});
}
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();
});
}
private:
asio::io_service io_service_;
std::vector<std::unique_ptr<asio::io_service>> io_service_pool_;
std::vector<detail::dumb_timer_queue*> timer_queue_pool_;
std::vector<detail::task_timer*> task_timer_pool_;
std::vector<std::function<std::string()>> get_cached_date_str_pool_;
tcp::acceptor acceptor_;
bool should_close_ = false;
@ -245,21 +247,18 @@ namespace crow
boost::asio::deadline_timer tick_timer_;
Handler* handler_;
uint16_t concurrency_{1};
uint16_t concurrency_{2};
std::uint8_t timeout_;
std::string server_name_;
uint16_t port_;
std::string bindaddr_;
unsigned int roundrobin_index_{};
std::vector<std::atomic<unsigned int>> task_queue_length_pool_;
std::chrono::milliseconds tick_interval_;
std::function<void()> tick_function_;
std::tuple<Middlewares...>* middlewares_;
#ifdef CROW_ENABLE_SSL
bool use_ssl_{false};
boost::asio::ssl::context ssl_context_{boost::asio::ssl::context::sslv23};
#endif
typename Adaptor::context* adaptor_ctx_;
};
}
} // namespace crow

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
#pragma once
#include <string>
#include "crow/settings.h"
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>
#include "crow/settings.h"
#include <string>
namespace crow
{
@ -30,115 +30,134 @@ namespace crow
Critical,
};
class ILogHandler {
public:
virtual void log(std::string message, LogLevel level) = 0;
class ILogHandler
{
public:
virtual void log(std::string message, LogLevel level) = 0;
};
class CerrLogHandler : public ILogHandler {
public:
void log(std::string message, LogLevel /*level*/) override {
std::cerr << message;
}
};
class logger {
private:
//
static std::string timestamp()
class CerrLogHandler : public ILogHandler
{
public:
void log(std::string message, LogLevel level) override
{
std::string prefix;
switch (level)
{
char date[32];
time_t t = time(0);
case LogLevel::Debug:
prefix = "DEBUG ";
break;
case LogLevel::Info:
prefix = "INFO ";
break;
case LogLevel::Warning:
prefix = "WARNING ";
break;
case LogLevel::Error:
prefix = "ERROR ";
break;
case LogLevel::Critical:
prefix = "CRITICAL";
break;
}
std::cerr << std::string("(") + timestamp() + std::string(") [") + prefix + std::string("] ") + message << std::endl;
}
tm my_tm;
private:
static std::string timestamp()
{
char date[32];
time_t t = time(0);
tm my_tm;
#if defined(_MSC_VER) || defined(__MINGW32__)
gmtime_s(&my_tm, &t);
#ifdef CROW_USE_LOCALTIMEZONE
localtime_s(&my_tm, &t);
#else
gmtime_r(&t, &my_tm);
gmtime_s(&my_tm, &t);
#endif
#else
#ifdef CROW_USE_LOCALTIMEZONE
localtime_r(&t, &my_tm);
#else
gmtime_r(&t, &my_tm);
#endif
#endif
size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm);
return std::string(date, date+sz);
}
public:
logger(std::string prefix, LogLevel level) : level_(level) {
#ifdef CROW_ENABLE_LOGGING
stringstream_ << "(" << timestamp() << ") [" << prefix << "] ";
#endif
}
~logger() {
#ifdef CROW_ENABLE_LOGGING
if(level_ >= get_current_log_level()) {
stringstream_ << std::endl;
get_handler_ref()->log(stringstream_.str(), level_);
}
#endif
}
//
template <typename T>
logger& operator<<(T const &value) {
#ifdef CROW_ENABLE_LOGGING
if(level_ >= get_current_log_level()) {
stringstream_ << value;
}
#endif
return *this;
}
//
static void setLogLevel(LogLevel level) {
get_log_level_ref() = level;
}
static void setHandler(ILogHandler* handler) {
get_handler_ref() = handler;
}
static LogLevel get_current_log_level() {
return get_log_level_ref();
}
private:
//
static LogLevel& get_log_level_ref()
{
static LogLevel current_level = static_cast<LogLevel>(CROW_LOG_LEVEL);
return current_level;
}
static ILogHandler*& get_handler_ref()
{
static CerrLogHandler default_handler;
static ILogHandler* current_handler = &default_handler;
return current_handler;
}
//
std::ostringstream stringstream_;
LogLevel level_;
size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm);
return std::string(date, date + sz);
}
};
}
#define CROW_LOG_CRITICAL \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \
crow::logger("CRITICAL", crow::LogLevel::Critical)
#define CROW_LOG_ERROR \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \
crow::logger("ERROR ", crow::LogLevel::Error)
#define CROW_LOG_WARNING \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \
crow::logger("WARNING ", crow::LogLevel::Warning)
#define CROW_LOG_INFO \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \
crow::logger("INFO ", crow::LogLevel::Info)
#define CROW_LOG_DEBUG \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \
crow::logger("DEBUG ", crow::LogLevel::Debug)
class logger
{
public:
logger(LogLevel level):
level_(level)
{}
~logger()
{
#ifdef CROW_ENABLE_LOGGING
if (level_ >= get_current_log_level())
{
get_handler_ref()->log(stringstream_.str(), level_);
}
#endif
}
//
template<typename T>
logger& operator<<(T const& value)
{
#ifdef CROW_ENABLE_LOGGING
if (level_ >= get_current_log_level())
{
stringstream_ << value;
}
#endif
return *this;
}
//
static void setLogLevel(LogLevel level) { get_log_level_ref() = level; }
static void setHandler(ILogHandler* handler) { get_handler_ref() = handler; }
static LogLevel get_current_log_level() { return get_log_level_ref(); }
private:
//
static LogLevel& get_log_level_ref()
{
static LogLevel current_level = static_cast<LogLevel>(CROW_LOG_LEVEL);
return current_level;
}
static ILogHandler*& get_handler_ref()
{
static CerrLogHandler default_handler;
static ILogHandler* current_handler = &default_handler;
return current_handler;
}
//
std::ostringstream stringstream_;
LogLevel level_;
};
} // namespace crow
#define CROW_LOG_CRITICAL \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \
crow::logger(crow::LogLevel::Critical)
#define CROW_LOG_ERROR \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \
crow::logger(crow::LogLevel::Error)
#define CROW_LOG_WARNING \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \
crow::logger(crow::LogLevel::Warning)
#define CROW_LOG_INFO \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \
crow::logger(crow::LogLevel::Info)
#define CROW_LOG_DEBUG \
if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \
crow::logger(crow::LogLevel::Debug)

339
include/crow/middleware.h Normal file
View File

@ -0,0 +1,339 @@
#pragma once
#include "crow/http_request.h"
#include "crow/http_response.h"
#include "crow/utility.h"
#include <tuple>
#include <type_traits>
#include <iostream>
#include <utility>
namespace crow
{
/// Local middleware should extend ILocalMiddleware
struct ILocalMiddleware
{
using call_global = std::false_type;
};
namespace detail
{
template<typename MW>
struct check_before_handle_arity_3_const
{
template<typename T, void (T::*)(request&, response&, typename MW::context&) const = &T::before_handle>
struct get
{};
};
template<typename MW>
struct check_before_handle_arity_3
{
template<typename T, void (T::*)(request&, response&, typename MW::context&) = &T::before_handle>
struct get
{};
};
template<typename MW>
struct check_after_handle_arity_3_const
{
template<typename T, void (T::*)(request&, response&, typename MW::context&) const = &T::after_handle>
struct get
{};
};
template<typename MW>
struct check_after_handle_arity_3
{
template<typename T, void (T::*)(request&, response&, typename MW::context&) = &T::after_handle>
struct get
{};
};
template<typename MW>
struct check_global_call_false
{
template<typename T, typename std::enable_if<T::call_global::value == false, bool>::type = true>
struct get
{};
};
template<typename T>
struct is_before_handle_arity_3_impl
{
template<typename C>
static std::true_type f(typename check_before_handle_arity_3_const<T>::template get<C>*);
template<typename C>
static std::true_type f(typename check_before_handle_arity_3<T>::template get<C>*);
template<typename C>
static std::false_type f(...);
public:
static const bool value = decltype(f<T>(nullptr))::value;
};
template<typename T>
struct is_after_handle_arity_3_impl
{
template<typename C>
static std::true_type f(typename check_after_handle_arity_3_const<T>::template get<C>*);
template<typename C>
static std::true_type f(typename check_after_handle_arity_3<T>::template get<C>*);
template<typename C>
static std::false_type f(...);
public:
static constexpr bool value = decltype(f<T>(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*/)
{
mw.before_handle(req, res, ctx.template get<MW>(), ctx);
}
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*/)
{
mw.before_handle(req, res, ctx.template get<MW>());
}
template<typename MW, typename Context, typename ParentContext>
typename std::enable_if<!is_after_handle_arity_3_impl<MW>::value>::type
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
{
mw.after_handle(req, res, ctx.template get<MW>(), ctx);
}
template<typename MW, typename Context, typename ParentContext>
typename std::enable_if<is_after_handle_arity_3_impl<MW>::value>::type
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& /*parent_ctx*/)
{
mw.after_handle(req, res, ctx.template get<MW>());
}
template<template<typename QueryMW> class CallCriteria, // Checks if QueryMW should be called in this context
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)
{
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
if (!CallCriteria<CurrentMW>::value)
{
return middleware_call_helper<CallCriteria, N + 1, Context, Container>(middlewares, req, res, ctx);
}
using parent_context_t = typename Context::template partial<N - 1>;
before_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
if (res.is_completed())
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
return true;
}
if (middleware_call_helper<CallCriteria, N + 1, Context, Container>(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;
}
return false;
}
template<template<typename QueryMW> class 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*/)
{
return false;
}
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& /*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)
{
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)
{
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)
{
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)
{
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);
}
// 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 F, typename... Args>
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<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");
res = crow::response(f(std::forward<Args>(args)...));
res.end();
}
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,
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,
"Handler function cannot have void return type; valid return types: string, int, crow::response, crow::returnable");
res = crow::response(f(req, std::forward<Args>(args)...));
res.end();
}
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,
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");
f(res, std::forward<Args>(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)...);
}
// 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<typename F, typename App, typename... Middlewares>
struct handler_middleware_wrapper
{
// CallCriteria bound to the current Middlewares pack
template<typename MW>
struct middleware_call_criteria
{
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_)
{
glob_completion_handler();
return;
}
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)...);
}
F f;
};
template<typename Route, typename App, typename... Middlewares>
struct handler_call_bridge
{
template<typename MW>
using check_app_contains = typename black_magic::has_type<MW, typename App::mw_container_t>;
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
{
auto wrapped = handler_middleware_wrapper<F, App, Middlewares...>{std::forward<F>(f)};
tptr->operator()(std::move(wrapped));
}
Route* tptr;
};
} // namespace detail
} // namespace crow

View File

@ -10,16 +10,14 @@ namespace crow
{
template <typename ... Middlewares>
struct partial_context
: public black_magic::pop_back<Middlewares...>::template rebind<partial_context>
, public black_magic::last_element_type<Middlewares...>::type::context
template<typename... Middlewares>
struct partial_context : public black_magic::pop_back<Middlewares...>::template rebind<partial_context>, public black_magic::last_element_type<Middlewares...>::type::context
{
using parent_context = typename black_magic::pop_back<Middlewares...>::template rebind<::crow::detail::partial_context>;
template <int N>
using partial = typename std::conditional<N == sizeof...(Middlewares)-1, partial_context, typename parent_context::template partial<N>>::type;
template<int N>
using partial = typename std::conditional<N == sizeof...(Middlewares) - 1, partial_context, typename parent_context::template partial<N>>::type;
template <typename T>
template<typename T>
typename T::context& get()
{
return static_cast<typename T::context&>(*this);
@ -28,40 +26,35 @@ namespace crow
template <>
template<>
struct partial_context<>
{
template <int>
template<int>
using partial = partial_context;
};
template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
template <typename ... Middlewares>
template<typename... Middlewares>
struct context : private partial_context<Middlewares...>
//struct context : private Middlewares::context... // simple but less type-safe
{
template <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 <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<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 <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares2>
friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
template<template<typename QueryMW> class 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);
template <typename T>
template<typename T>
typename T::context& get()
{
return static_cast<typename T::context&>(*this);
}
template <int N>
template<int N>
using partial = typename partial_context<Middlewares...>::template partial<N>;
};
}
}
} // namespace detail
} // namespace crow

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"
@ -12,7 +14,7 @@ namespace crow
// before_handle
// called before handling the request.
// if res.end() is called, the operation is halted.
// if res.end() is called, the operation is halted.
// (still call after_handle of this middleware)
// 2 signatures:
// void before_handle(request& req, response& res, context& ctx)
@ -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;
@ -62,25 +201,26 @@ namespace crow
}
std::string cookies = req.get_header_value("Cookie");
size_t pos = 0;
while(pos < cookies.size())
while (pos < cookies.size())
{
size_t pos_equal = cookies.find('=', pos);
if (pos_equal == cookies.npos)
break;
std::string name = cookies.substr(pos, pos_equal-pos);
std::string name = cookies.substr(pos, pos_equal - pos);
boost::trim(name);
pos = pos_equal+1;
while(pos < cookies.size() && cookies[pos] == ' ') pos++;
pos = pos_equal + 1;
while (pos < cookies.size() && cookies[pos] == ' ')
pos++;
if (pos == cookies.size())
break;
size_t pos_semicolon = cookies.find(';', pos);
std::string value = cookies.substr(pos, pos_semicolon-pos);
std::string value = cookies.substr(pos, pos_semicolon - pos);
boost::trim(value);
if (value[0] == '"' && value[value.size()-1] == '"')
if (value[0] == '"' && value[value.size() - 1] == '"')
{
value = value.substr(1, value.size()-2);
value = value.substr(1, value.size() - 2);
}
ctx.jar.emplace(std::move(name), std::move(value));
@ -89,18 +229,16 @@ namespace crow
if (pos == cookies.npos)
break;
pos++;
while(pos < cookies.size() && cookies[pos] == ' ') pos++;
while (pos < cookies.size() && cookies[pos] == ' ')
pos++;
}
}
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());
}
}
};
@ -128,4 +266,4 @@ namespace crow
SimpleApp
*/
}
} // namespace crow

View File

@ -0,0 +1,200 @@
#pragma once
#include "crow/http_request.h"
#include "crow/http_response.h"
#include "crow/routing.h"
namespace crow
{
struct CORSHandler;
/// Used for tuning CORS policies
struct CORSRules
{
friend struct crow::CORSHandler;
/// Set Access-Control-Allow-Origin. Default is "*"
CORSRules& origin(const std::string& origin)
{
origin_ = origin;
return *this;
}
/// Set Access-Control-Allow-Methods. Default is "*"
CORSRules& methods(crow::HTTPMethod method)
{
add_list_item(methods_, crow::method_name(method));
return *this;
}
/// Set Access-Control-Allow-Methods. Default is "*"
template<typename... Methods>
CORSRules& methods(crow::HTTPMethod method, Methods... method_list)
{
add_list_item(methods_, crow::method_name(method));
methods(method_list...);
return *this;
}
/// Set Access-Control-Allow-Headers. Default is "*"
CORSRules& headers(const std::string& header)
{
add_list_item(headers_, header);
return *this;
}
/// Set Access-Control-Allow-Headers. Default is "*"
template<typename... Headers>
CORSRules& headers(const std::string& header, Headers... header_list)
{
add_list_item(headers_, header);
headers(header_list...);
return *this;
}
/// Set Access-Control-Max-Age. Default is none
CORSRules& max_age(int max_age)
{
max_age_ = std::to_string(max_age);
return *this;
}
/// Enable Access-Control-Allow-Credentials
CORSRules& allow_credentials()
{
allow_credentials_ = true;
return *this;
}
/// Ignore CORS and don't send any headers
void ignore()
{
ignore_ = true;
}
/// Handle CORS on specific prefix path
CORSRules& prefix(const std::string& prefix);
/// Handle CORS for specific blueprint
CORSRules& blueprint(const Blueprint& bp);
/// Global CORS policy
CORSRules& global();
private:
CORSRules() = delete;
CORSRules(CORSHandler* handler):
handler_(handler) {}
/// build comma separated list
void add_list_item(std::string& list, const std::string& val)
{
if (list == "*") list = "";
if (list.size() > 0) list += ", ";
list += val;
}
/// Set header `key` to `value` if it is not set
void set_header_no_override(const std::string& key, const std::string& value, crow::response& res)
{
if (value.size() == 0) return;
if (!get_header_value(res.headers, key).empty()) return;
res.add_header(key, value);
}
/// Set response headers
void apply(crow::response& res)
{
if (ignore_) return;
set_header_no_override("Access-Control-Allow-Origin", origin_, res);
set_header_no_override("Access-Control-Allow-Methods", methods_, res);
set_header_no_override("Access-Control-Allow-Headers", headers_, res);
set_header_no_override("Access-Control-Max-Age", max_age_, res);
if (allow_credentials_) set_header_no_override("Access-Control-Allow-Credentials", "true", res);
}
bool ignore_ = false;
// TODO: support multiple origins that are dynamically selected
std::string origin_ = "*";
std::string methods_ = "*";
std::string headers_ = "*";
std::string max_age_;
bool allow_credentials_ = false;
CORSHandler* handler_;
};
/// CORSHandler is a global middleware for setting CORS headers.
///
/// By default, it sets Access-Control-Allow-Origin/Methods/Headers to "*".
/// The default behaviour can be changed with the `global()` cors rule.
/// Additional rules for prexies can be added with `prefix()`.
struct CORSHandler
{
struct context
{};
void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/)
{}
void after_handle(crow::request& req, crow::response& res, context& /*ctx*/)
{
auto& rule = find_rule(req.url);
rule.apply(res);
}
/// Handle CORS on a specific prefix path
CORSRules& prefix(const std::string& prefix)
{
rules.emplace_back(prefix, CORSRules(this));
return rules.back().second;
}
/// Handle CORS for a specific blueprint
CORSRules& blueprint(const Blueprint& bp)
{
rules.emplace_back(bp.prefix(), CORSRules(this));
return rules.back().second;
}
/// Get the global CORS policy
CORSRules& global()
{
return default_;
}
private:
CORSRules& find_rule(const std::string& path)
{
// TODO: use a trie in case of many rules
for (auto& rule : rules)
{
// Check if path starts with a rules prefix
if (path.rfind(rule.first, 0) == 0)
{
return rule.second;
}
}
return default_;
}
std::vector<std::pair<std::string, CORSRules>> rules;
CORSRules default_ = CORSRules(this);
};
inline CORSRules& CORSRules::prefix(const std::string& prefix)
{
return handler_->prefix(prefix);
}
inline CORSRules& CORSRules::blueprint(const Blueprint& bp)
{
return handler_->blueprint(bp);
}
inline CORSRules& CORSRules::global()
{
return handler_->global();
}
} // namespace crow

View File

@ -8,12 +8,10 @@ namespace crow
struct UTF8
{
struct context
{
};
{};
void before_handle(request& /*req*/, response& /*res*/, context& /*ctx*/)
{
}
{}
void after_handle(request& /*req*/, response& res, context& /*ctx*/)
{
@ -24,4 +22,4 @@ namespace crow
}
};
}
} // namespace crow

View File

@ -1,121 +1,118 @@
//This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py
// This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py on 2021-12-03.
#include <unordered_map>
#include <string>
namespace crow {
#ifdef CROW_MAIN
std::unordered_map<std::string, std::string> mime_types {
{"shtml", "text/html"},
{"htm", "text/html"},
{"html", "text/html"},
{"css", "text/css"},
{"xml", "text/xml"},
{"gif", "image/gif"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"js", "application/javascript"},
{"atom", "application/atom+xml"},
{"rss", "application/rss+xml"},
{"mml", "text/mathml"},
{"txt", "text/plain"},
{"jad", "text/vnd.sun.j2me.app-descriptor"},
{"wml", "text/vnd.wap.wml"},
{"htc", "text/x-component"},
{"png", "image/png"},
{"svgz", "image/svg+xml"},
{"svg", "image/svg+xml"},
{"tiff", "image/tiff"},
{"tif", "image/tiff"},
{"wbmp", "image/vnd.wap.wbmp"},
{"webp", "image/webp"},
{"ico", "image/x-icon"},
{"jng", "image/x-jng"},
{"bmp", "image/x-ms-bmp"},
{"woff", "font/woff"},
{"woff2", "font/woff2"},
{"ear", "application/java-archive"},
{"war", "application/java-archive"},
{"jar", "application/java-archive"},
{"json", "application/json"},
{"hqx", "application/mac-binhex40"},
{"doc", "application/msword"},
{"pdf", "application/pdf"},
{"ai", "application/postscript"},
{"eps", "application/postscript"},
{"ps", "application/postscript"},
{"rtf", "application/rtf"},
{"m3u8", "application/vnd.apple.mpegurl"},
{"kml", "application/vnd.google-earth.kml+xml"},
{"kmz", "application/vnd.google-earth.kmz"},
{"xls", "application/vnd.ms-excel"},
{"eot", "application/vnd.ms-fontobject"},
{"ppt", "application/vnd.ms-powerpoint"},
{"odg", "application/vnd.oasis.opendocument.graphics"},
{"odp", "application/vnd.oasis.opendocument.presentation"},
{"ods", "application/vnd.oasis.opendocument.spreadsheet"},
{"odt", "application/vnd.oasis.opendocument.text"},
{"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{"wmlc", "application/vnd.wap.wmlc"},
{"7z", "application/x-7z-compressed"},
{"cco", "application/x-cocoa"},
{"jardiff", "application/x-java-archive-diff"},
{"jnlp", "application/x-java-jnlp-file"},
{"run", "application/x-makeself"},
{"pm", "application/x-perl"},
{"pl", "application/x-perl"},
{"pdb", "application/x-pilot"},
{"prc", "application/x-pilot"},
{"rar", "application/x-rar-compressed"},
{"rpm", "application/x-redhat-package-manager"},
{"sea", "application/x-sea"},
{"swf", "application/x-shockwave-flash"},
{"sit", "application/x-stuffit"},
{"tk", "application/x-tcl"},
{"tcl", "application/x-tcl"},
{"crt", "application/x-x509-ca-cert"},
{"pem", "application/x-x509-ca-cert"},
{"der", "application/x-x509-ca-cert"},
{"xpi", "application/x-xpinstall"},
{"xhtml", "application/xhtml+xml"},
{"xspf", "application/xspf+xml"},
{"zip", "application/zip"},
{"dll", "application/octet-stream"},
{"exe", "application/octet-stream"},
{"bin", "application/octet-stream"},
{"deb", "application/octet-stream"},
{"dmg", "application/octet-stream"},
{"img", "application/octet-stream"},
{"iso", "application/octet-stream"},
{"msm", "application/octet-stream"},
{"msp", "application/octet-stream"},
{"msi", "application/octet-stream"},
{"kar", "audio/midi"},
{"midi", "audio/midi"},
{"mid", "audio/midi"},
{"mp3", "audio/mpeg"},
{"ogg", "audio/ogg"},
{"m4a", "audio/x-m4a"},
{"ra", "audio/x-realaudio"},
{"3gp", "video/3gpp"},
{"3gpp", "video/3gpp"},
{"ts", "video/mp2t"},
{"mp4", "video/mp4"},
{"mpg", "video/mpeg"},
{"mpeg", "video/mpeg"},
{"mov", "video/quicktime"},
{"webm", "video/webm"},
{"flv", "video/x-flv"},
{"m4v", "video/x-m4v"},
{"mng", "video/x-mng"},
{"asf", "video/x-ms-asf"},
{"asx", "video/x-ms-asf"},
{"wmv", "video/x-ms-wmv"},
{"avi", "video/x-msvideo"}
};
#else
extern std::unordered_map<std::string, std::string> mime_types;
#endif
namespace crow
{
const std::unordered_map<std::string, std::string> mime_types{
{"shtml", "text/html"},
{"htm", "text/html"},
{"html", "text/html"},
{"css", "text/css"},
{"xml", "text/xml"},
{"gif", "image/gif"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"js", "application/javascript"},
{"atom", "application/atom+xml"},
{"rss", "application/rss+xml"},
{"mml", "text/mathml"},
{"txt", "text/plain"},
{"jad", "text/vnd.sun.j2me.app-descriptor"},
{"wml", "text/vnd.wap.wml"},
{"htc", "text/x-component"},
{"avif", "image/avif"},
{"png", "image/png"},
{"svgz", "image/svg+xml"},
{"svg", "image/svg+xml"},
{"tiff", "image/tiff"},
{"tif", "image/tiff"},
{"wbmp", "image/vnd.wap.wbmp"},
{"webp", "image/webp"},
{"ico", "image/x-icon"},
{"jng", "image/x-jng"},
{"bmp", "image/x-ms-bmp"},
{"woff", "font/woff"},
{"woff2", "font/woff2"},
{"ear", "application/java-archive"},
{"war", "application/java-archive"},
{"jar", "application/java-archive"},
{"json", "application/json"},
{"hqx", "application/mac-binhex40"},
{"doc", "application/msword"},
{"pdf", "application/pdf"},
{"ai", "application/postscript"},
{"eps", "application/postscript"},
{"ps", "application/postscript"},
{"rtf", "application/rtf"},
{"m3u8", "application/vnd.apple.mpegurl"},
{"kml", "application/vnd.google-earth.kml+xml"},
{"kmz", "application/vnd.google-earth.kmz"},
{"xls", "application/vnd.ms-excel"},
{"eot", "application/vnd.ms-fontobject"},
{"ppt", "application/vnd.ms-powerpoint"},
{"odg", "application/vnd.oasis.opendocument.graphics"},
{"odp", "application/vnd.oasis.opendocument.presentation"},
{"ods", "application/vnd.oasis.opendocument.spreadsheet"},
{"odt", "application/vnd.oasis.opendocument.text"},
{"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{"wmlc", "application/vnd.wap.wmlc"},
{"wasm", "application/wasm"},
{"7z", "application/x-7z-compressed"},
{"cco", "application/x-cocoa"},
{"jardiff", "application/x-java-archive-diff"},
{"jnlp", "application/x-java-jnlp-file"},
{"run", "application/x-makeself"},
{"pm", "application/x-perl"},
{"pl", "application/x-perl"},
{"pdb", "application/x-pilot"},
{"prc", "application/x-pilot"},
{"rar", "application/x-rar-compressed"},
{"rpm", "application/x-redhat-package-manager"},
{"sea", "application/x-sea"},
{"swf", "application/x-shockwave-flash"},
{"sit", "application/x-stuffit"},
{"tk", "application/x-tcl"},
{"tcl", "application/x-tcl"},
{"crt", "application/x-x509-ca-cert"},
{"pem", "application/x-x509-ca-cert"},
{"der", "application/x-x509-ca-cert"},
{"xpi", "application/x-xpinstall"},
{"xhtml", "application/xhtml+xml"},
{"xspf", "application/xspf+xml"},
{"zip", "application/zip"},
{"dll", "application/octet-stream"},
{"exe", "application/octet-stream"},
{"bin", "application/octet-stream"},
{"deb", "application/octet-stream"},
{"dmg", "application/octet-stream"},
{"img", "application/octet-stream"},
{"iso", "application/octet-stream"},
{"msm", "application/octet-stream"},
{"msp", "application/octet-stream"},
{"msi", "application/octet-stream"},
{"kar", "audio/midi"},
{"midi", "audio/midi"},
{"mid", "audio/midi"},
{"mp3", "audio/mpeg"},
{"ogg", "audio/ogg"},
{"m4a", "audio/x-m4a"},
{"ra", "audio/x-realaudio"},
{"3gp", "video/3gpp"},
{"3gpp", "video/3gpp"},
{"ts", "video/mp2t"},
{"mp4", "video/mp4"},
{"mpg", "video/mpeg"},
{"mpeg", "video/mpeg"},
{"mov", "video/quicktime"},
{"webm", "video/webm"},
{"flv", "video/x-flv"},
{"m4v", "video/x-m4v"},
{"mng", "video/x-mng"},
{"asf", "video/x-ms-asf"},
{"asx", "video/x-ms-asf"},
{"wmv", "video/x-ms-wmv"},
{"avi", "video/x-msvideo"}};
}

View File

@ -6,51 +6,95 @@
#include "crow/http_request.h"
#include "crow/returnable.h"
#include "crow/ci_map.h"
namespace crow
{
///Encapsulates anything related to processing and organizing `multipart/xyz` messages
/// Encapsulates anything related to processing and organizing `multipart/xyz` messages
namespace multipart
{
const std::string dd = "--";
const std::string crlf = "\r\n";
///The first part in a section, contains metadata about the part
const std::string dd = "--";
/// The first part in a section, contains metadata about the part
struct header
{
std::pair<std::string, std::string> value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition`
std::unordered_map<std::string, std::string> params; ///< The parameters of the header, come after the `value`
std::string value; ///< The first part of the header, usually `Content-Type` or `Content-Disposition`
std::unordered_map<std::string, std::string> params; ///< The parameters of the header, come after the `value`
operator int() const { return std::stoi(value); } ///< Returns \ref value as integer
operator double() const { return std::stod(value); } ///< Returns \ref value as double
};
/// Multipart header map (key is header key).
using mph_map = std::unordered_multimap<std::string, header, ci_hash, ci_key_eq>;
/// Find and return the value object associated with the key. (returns an empty class if nothing is found)
template<typename O, typename T>
inline const O& get_header_value_object(const T& headers, const std::string& key)
{
if (headers.count(key))
{
return headers.find(key)->second;
}
static O empty;
return empty;
}
/// 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)
{
return get_header_value_object<header>(headers, key);
}
///One part of the multipart message
///It is usually separated from other sections by a `boundary`
///
/// It is usually separated from other sections by a `boundary`
struct part
{
std::vector<header> headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding
mph_map headers; ///< (optional) The first part before the data, Contains information regarding the type of data and encoding
std::string body; ///< The actual data in the part
operator int() const { return std::stoi(body); } ///< Returns \ref body as integer
operator double() const { return std::stod(body); } ///< Returns \ref body as double
const header& get_header_object(const std::string& key) const
{
return multipart::get_header_object(headers, key);
}
};
///The parsed multipart request/response
/// Multipart map (key is the name parameter).
using mp_map = std::unordered_multimap<std::string, part, ci_hash, ci_key_eq>;
/// The parsed multipart request/response
struct message : public returnable
{
ci_map headers;
std::string boundary; ///< The text boundary that separates different `parts`
ci_map headers; ///< The request/response headers
std::string boundary; ///< The text boundary that separates different `parts`
std::vector<part> parts; ///< The individual parts of the message
mp_map part_map; ///< The individual parts of the message, organized in a map with the `name` header parameter being the key
const std::string& get_header_value(const std::string& key) const
{
return crow::get_header_value(headers, key);
}
///Represent all parts as a string (**does not include message headers**)
part get_part_by_name(const std::string& name)
{
return part_map.find(name)->second;
}
/// Represent all parts as a string (**does not include message headers**)
std::string dump() const override
{
std::stringstream str;
std::string delimiter = dd + boundary;
for (unsigned i=0 ; i<parts.size(); i++)
for (unsigned i = 0; i < parts.size(); i++)
{
str << delimiter << crlf;
str << dump(i);
@ -59,15 +103,15 @@ namespace crow
return str.str();
}
///Represent an individual part as a string
/// Represent an individual part as a string
std::string dump(int part_) const
{
std::stringstream str;
part item = parts[part_];
for (header item_h: item.headers)
for (auto& item_h : item.headers)
{
str << item_h.value.first << ": " << item_h.value.second;
for (auto& it: item_h.params)
str << item_h.first << ": " << item_h.second.value;
for (auto& it : item_h.second.params)
{
str << "; " << it.first << '=' << pad(it.second);
}
@ -78,62 +122,88 @@ namespace crow
return str.str();
}
///Default constructor using default values
message(const ci_map& headers, const std::string& boundary, const std::vector<part>& sections)
: returnable("multipart/form-data"), headers(headers), boundary(boundary), parts(sections){}
/// Default constructor using default values
message(const ci_map& headers, const std::string& boundary, const std::vector<part>& sections):
returnable("multipart/form-data; boundary=CROW-BOUNDARY"), headers(headers), boundary(boundary), parts(sections)
{
if (!boundary.empty())
content_type = "multipart/form-data; boundary=" + boundary;
for (auto& item : parts)
{
part_map.emplace(
(get_header_object(item.headers, "Content-Disposition").params.find("name")->second),
item);
}
}
///Create a multipart message from a request data
message(const request& req)
: returnable("multipart/form-data"),
headers(req.headers),
boundary(get_boundary(get_header_value("Content-Type"))),
parts(parse_body(req.body))
{}
private:
/// Create a multipart message from a request data
message(const request& req):
returnable("multipart/form-data; boundary=CROW-BOUNDARY"),
headers(req.headers),
boundary(get_boundary(get_header_value("Content-Type")))
{
if (!boundary.empty())
content_type = "multipart/form-data; boundary=" + boundary;
parse_body(req.body, parts, part_map);
}
private:
std::string get_boundary(const std::string& header) const
{
size_t found = header.find("boundary=");
if (found)
return header.substr(found+9);
constexpr char boundary_text[] = "boundary=";
size_t found = header.find(boundary_text);
if (found != std::string::npos)
{
std::string to_return(header.substr(found + strlen(boundary_text)));
if (to_return[0] == '\"')
{
to_return = to_return.substr(1, to_return.length() - 2);
}
return to_return;
}
return std::string();
}
std::vector<part> parse_body(std::string body)
void parse_body(std::string body, std::vector<part>& sections, mp_map& part_map)
{
std::vector<part> sections;
std::string delimiter = dd + boundary;
std::string delimiter = dd + boundary;
// TODO(EDev): Exit on error
while (body != (crlf))
{
size_t found = body.find(delimiter);
if (found == std::string::npos)
{
// did not find delimiter; probably an ill-formed body; ignore the rest
break;
}
std::string section = body.substr(0, found);
while(body != (crlf))
{
size_t found = body.find(delimiter);
std::string section = body.substr(0, found);
//+2 is the CRLF
//We don't check it and delete it so that the same delimiter can be used for
//the last delimiter (--delimiter--CRLF).
body.erase(0, found + delimiter.length() + 2);
if (!section.empty())
{
sections.emplace_back(parse_section(section));
}
}
return sections;
// +2 is the CRLF.
// We don't check it and delete it so that the same delimiter can be used for The last delimiter (--delimiter--CRLF).
body.erase(0, found + delimiter.length() + 2);
if (!section.empty())
{
part parsed_section(parse_section(section));
part_map.emplace(
(get_header_object(parsed_section.headers, "Content-Disposition").params.find("name")->second),
parsed_section);
sections.push_back(std::move(parsed_section));
}
}
}
part parse_section(std::string& section)
{
struct part to_return;
size_t found = section.find(crlf+crlf);
std::string head_line = section.substr(0, found+2);
size_t found = section.find(crlf + crlf);
std::string head_line = section.substr(0, found + 2);
section.erase(0, found + 4);
parse_section_head(head_line, to_return);
to_return.body = section.substr(0, section.length()-2);
to_return.body = section.substr(0, section.length() - 2);
return to_return;
}
@ -145,54 +215,55 @@ namespace crow
size_t found = lines.find(crlf);
std::string line = lines.substr(0, found);
lines.erase(0, found+2);
//add the header if available
std::string key;
lines.erase(0, found + 2);
// Add the header if available
if (!line.empty())
{
size_t found = line.find("; ");
std::string header = line.substr(0, found);
if (found != std::string::npos)
line.erase(0, found+2);
line.erase(0, found + 2);
else
line = std::string();
size_t header_split = header.find(": ");
key = header.substr(0, header_split);
to_add.value = std::pair<std::string, std::string>(header.substr(0, header_split), header.substr(header_split+2));
to_add.value = header.substr(header_split + 2);
}
//add the parameters
// Add the parameters
while (!line.empty())
{
size_t found = line.find("; ");
std::string param = line.substr(0, found);
if (found != std::string::npos)
line.erase(0, found+2);
line.erase(0, found + 2);
else
line = std::string();
size_t param_split = param.find('=');
std::string value = param.substr(param_split+1);
std::string value = param.substr(param_split + 1);
to_add.params.emplace(param.substr(0, param_split), trim(value));
}
part.headers.emplace_back(to_add);
part.headers.emplace(key, to_add);
}
}
inline std::string trim (std::string& string, const char& excess = '"') const
inline std::string trim(std::string& string, const char& excess = '"') const
{
if (string.length() > 1 && string[0] == excess && string[string.length()-1] == excess)
return string.substr(1, string.length()-2);
if (string.length() > 1 && string[0] == excess && string[string.length() - 1] == excess)
return string.substr(1, string.length() - 2);
return string;
}
inline std::string pad (std::string& string, const char& padding = '"') const
inline std::string pad(std::string& string, const char& padding = '"') const
{
return (padding + string + padding);
return (padding + string + padding);
}
};
}
}
} // namespace multipart
} // namespace crow

View File

@ -6,6 +6,9 @@
#include <functional>
#include "crow/json.h"
#include "crow/logging.h"
#include "crow/returnable.h"
#include "crow/utility.h"
namespace crow
{
namespace mustache
@ -16,11 +19,10 @@ namespace crow
class invalid_template_exception : public std::exception
{
public:
invalid_template_exception(const std::string& msg)
: msg("crow::mustache error: " + msg)
{
}
public:
invalid_template_exception(const std::string& msg):
msg("crow::mustache error: " + msg)
{}
virtual const char* what() const throw()
{
return msg.c_str();
@ -28,6 +30,22 @@ namespace crow
std::string msg;
};
struct rendered_template : returnable
{
rendered_template():
returnable("text/html") {}
rendered_template(std::string& body):
returnable("text/html"), body_(std::move(body)) {}
std::string body_;
std::string dump() const override
{
return body_;
}
};
enum class ActionType
{
Ignore,
@ -45,28 +63,29 @@ namespace crow
int end;
int pos;
ActionType t;
Action(ActionType t, size_t start, size_t end, size_t pos = 0)
: start(static_cast<int>(start)), end(static_cast<int>(end)), pos(static_cast<int>(pos)), t(t)
{}
Action(ActionType t, size_t start, size_t end, size_t pos = 0):
start(static_cast<int>(start)), end(static_cast<int>(end)), pos(static_cast<int>(pos)), t(t)
{
}
};
/// A mustache template object.
class template_t
class template_t
{
public:
template_t(std::string body)
: body_(std::move(body))
template_t(std::string body):
body_(std::move(body))
{
// {{ {{# {{/ {{^ {{! {{> {{=
parse();
}
private:
std::string tag_name(const Action& action)
std::string tag_name(const Action& action) const
{
return body_.substr(action.start, action.end - action.start);
}
auto find_context(const std::string& name, const std::vector<context*>& stack, bool shouldUseOnlyFirstStackValue = false)->std::pair<bool, context&>
auto find_context(const std::string& name, const std::vector<context*>& stack, bool shouldUseOnlyFirstStackValue = false) const -> std::pair<bool, context&>
{
if (name == ".")
{
@ -78,7 +97,7 @@ namespace crow
int dotPosition = name.find(".");
if (dotPosition == static_cast<int>(name.npos))
{
for(auto it = stack.rbegin(); it != stack.rend(); ++it)
for (auto it = stack.rbegin(); it != stack.rend(); ++it)
{
if ((*it)->t() == json::type::Object)
{
@ -91,22 +110,22 @@ namespace crow
{
std::vector<int> dotPositions;
dotPositions.push_back(-1);
while(dotPosition != static_cast<int>(name.npos))
while (dotPosition != static_cast<int>(name.npos))
{
dotPositions.push_back(dotPosition);
dotPosition = name.find(".", dotPosition+1);
dotPosition = name.find(".", dotPosition + 1);
}
dotPositions.push_back(name.size());
std::vector<std::string> names;
names.reserve(dotPositions.size()-1);
for(int i = 1; i < static_cast<int>(dotPositions.size()); i ++)
names.emplace_back(name.substr(dotPositions[i-1]+1, dotPositions[i]-dotPositions[i-1]-1));
names.reserve(dotPositions.size() - 1);
for (int i = 1; i < static_cast<int>(dotPositions.size()); i++)
names.emplace_back(name.substr(dotPositions[i - 1] + 1, dotPositions[i] - dotPositions[i - 1] - 1));
for(auto it = stack.rbegin(); it != stack.rend(); ++it)
for (auto it = stack.rbegin(); it != stack.rend(); ++it)
{
context* view = *it;
bool found = true;
for(auto jt = names.begin(); jt != names.end(); ++jt)
for (auto jt = names.begin(); jt != names.end(); ++jt)
{
if (view->t() == json::type::Object &&
view->count(*jt))
@ -115,7 +134,8 @@ namespace crow
}
else
{
if (shouldUseOnlyFirstStackValue) {
if (shouldUseOnlyFirstStackValue)
{
return {false, empty_str};
}
found = false;
@ -125,18 +145,17 @@ namespace crow
if (found)
return {true, *view};
}
}
return {false, empty_str};
}
void escape(const std::string& in, std::string& out)
void escape(const std::string& in, std::string& out) const
{
out.reserve(out.size() + in.size());
for(auto it = in.begin(); it != in.end(); ++it)
for (auto it = in.begin(); it != in.end(); ++it)
{
switch(*it)
switch (*it)
{
case '&': out += "&amp;"; break;
case '<': out += "&lt;"; break;
@ -144,25 +163,32 @@ namespace crow
case '"': out += "&quot;"; break;
case '\'': out += "&#39;"; break;
case '/': out += "&#x2F;"; break;
case '`': out += "&#x60;"; break;
case '=': out += "&#x3D;"; break;
default: out += *it; break;
}
}
}
bool isTagInsideObjectBlock(const int& current, const std::vector<context*>& stack)
bool isTagInsideObjectBlock(const int& current, const std::vector<context*>& stack) const
{
int openedBlock = 0;
int totalBlocksBefore = 0;
for (int i = current; i > 0; --i) {
for (int i = current; i > 0; --i)
{
++totalBlocksBefore;
auto& action = actions_[i - 1];
if (action.t == ActionType::OpenBlock) {
if (openedBlock == 0 && (*stack.rbegin())->t() == json::type::Object) {
if (action.t == ActionType::OpenBlock)
{
if (openedBlock == 0 && (*stack.rbegin())->t() == json::type::Object)
{
return true;
}
--openedBlock;
} else if (action.t == ActionType::CloseBlock) {
}
else if (action.t == ActionType::CloseBlock)
{
++openedBlock;
}
}
@ -170,123 +196,139 @@ namespace crow
return false;
}
void render_internal(int actionBegin, int actionEnd, std::vector<context*>& stack, std::string& out, int indent)
void render_internal(int actionBegin, int actionEnd, std::vector<context*>& stack, std::string& out, int indent) const
{
int current = actionBegin;
if (indent)
out.insert(out.size(), indent, ' ');
while(current < actionEnd)
while (current < actionEnd)
{
auto& fragment = fragments_[current];
auto& action = actions_[current];
render_fragment(fragment, indent, out);
switch(action.t)
switch (action.t)
{
case ActionType::Ignore:
// do nothing
break;
case ActionType::Partial:
{
std::string partial_name = tag_name(action);
auto partial_templ = load(partial_name);
int partial_indent = action.pos;
partial_templ.render_internal(0, partial_templ.fragments_.size()-1, stack, out, partial_indent?indent+partial_indent:0);
}
break;
{
std::string partial_name = tag_name(action);
auto partial_templ = load(partial_name);
int partial_indent = action.pos;
partial_templ.render_internal(0, partial_templ.fragments_.size() - 1, stack, out, partial_indent ? indent + partial_indent : 0);
}
break;
case ActionType::UnescapeTag:
case ActionType::Tag:
{
bool shouldUseOnlyFirstStackValue = false;
if (isTagInsideObjectBlock(current, stack))
{
bool shouldUseOnlyFirstStackValue = false;
if (isTagInsideObjectBlock(current, stack)) {
shouldUseOnlyFirstStackValue = true;
}
auto optional_ctx = find_context(tag_name(action), stack, shouldUseOnlyFirstStackValue);
auto& ctx = optional_ctx.second;
switch(ctx.t())
{
case json::type::Number:
out += ctx.dump();
break;
case json::type::String:
if (action.t == ActionType::Tag)
escape(ctx.s, out);
else
out += ctx.s;
break;
default:
throw std::runtime_error("not implemented tag type" + boost::lexical_cast<std::string>(static_cast<int>(ctx.t())));
}
shouldUseOnlyFirstStackValue = true;
}
break;
case ActionType::ElseBlock:
auto optional_ctx = find_context(tag_name(action), stack, shouldUseOnlyFirstStackValue);
auto& ctx = optional_ctx.second;
switch (ctx.t())
{
static context nullContext;
auto optional_ctx = find_context(tag_name(action), stack);
if (!optional_ctx.first)
case json::type::Number:
out += ctx.dump();
break;
case json::type::String:
if (action.t == ActionType::Tag)
escape(ctx.s, out);
else
out += ctx.s;
break;
case json::type::Function:
{
std::string execute_result = ctx.execute();
while (execute_result.find("{{") != std::string::npos)
{
template_t result_plug(execute_result);
execute_result = result_plug.render_string(*(stack[0]));
}
if (action.t == ActionType::Tag)
escape(execute_result, out);
else
out += execute_result;
}
break;
default:
throw std::runtime_error("not implemented tag type" + boost::lexical_cast<std::string>(static_cast<int>(ctx.t())));
}
}
break;
case ActionType::ElseBlock:
{
static context nullContext;
auto optional_ctx = find_context(tag_name(action), stack);
if (!optional_ctx.first)
{
stack.emplace_back(&nullContext);
break;
}
auto& ctx = optional_ctx.second;
switch (ctx.t())
{
case json::type::List:
if (ctx.l && !ctx.l->empty())
current = action.pos;
else
stack.emplace_back(&nullContext);
break;
case json::type::False:
case json::type::Null:
stack.emplace_back(&nullContext);
break;
}
auto& ctx = optional_ctx.second;
switch(ctx.t())
{
case json::type::List:
if (ctx.l && !ctx.l->empty())
current = action.pos;
else
stack.emplace_back(&nullContext);
break;
case json::type::False:
case json::type::Null:
stack.emplace_back(&nullContext);
break;
default:
current = action.pos;
break;
}
break;
}
case ActionType::OpenBlock:
{
auto optional_ctx = find_context(tag_name(action), stack);
if (!optional_ctx.first)
{
default:
current = action.pos;
break;
}
auto& ctx = optional_ctx.second;
switch(ctx.t())
{
case json::type::List:
if (ctx.l)
for(auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
{
stack.push_back(&*it);
render_internal(current+1, action.pos, stack, out, indent);
stack.pop_back();
}
current = action.pos;
break;
case json::type::Number:
case json::type::String:
case json::type::Object:
case json::type::True:
stack.push_back(&ctx);
break;
case json::type::False:
case json::type::Null:
current = action.pos;
break;
default:
throw std::runtime_error("{{#: not implemented context type: " + boost::lexical_cast<std::string>(static_cast<int>(ctx.t())));
break;
}
}
break;
}
case ActionType::OpenBlock:
{
auto optional_ctx = find_context(tag_name(action), stack);
if (!optional_ctx.first)
{
current = action.pos;
break;
}
auto& ctx = optional_ctx.second;
switch (ctx.t())
{
case json::type::List:
if (ctx.l)
for (auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
{
stack.push_back(&*it);
render_internal(current + 1, action.pos, stack, out, indent);
stack.pop_back();
}
current = action.pos;
break;
case json::type::Number:
case json::type::String:
case json::type::Object:
case json::type::True:
stack.push_back(&ctx);
break;
case json::type::False:
case json::type::Null:
current = action.pos;
break;
default:
throw std::runtime_error("{{#: not implemented context type: " + boost::lexical_cast<std::string>(static_cast<int>(ctx.t())));
break;
}
break;
}
case ActionType::CloseBlock:
stack.pop_back();
break;
@ -298,52 +340,78 @@ namespace crow
auto& fragment = fragments_[actionEnd];
render_fragment(fragment, indent, out);
}
void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out)
void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out) const
{
if (indent)
{
for(int i = fragment.first; i < fragment.second; i ++)
for (int i = fragment.first; i < fragment.second; i++)
{
out += body_[i];
if (body_[i] == '\n' && i+1 != static_cast<int>(body_.size()))
if (body_[i] == '\n' && i + 1 != static_cast<int>(body_.size()))
out.insert(out.size(), indent, ' ');
}
}
else
out.insert(out.size(), body_, fragment.first, fragment.second-fragment.first);
out.insert(out.size(), body_, fragment.first, fragment.second - fragment.first);
}
public:
std::string render()
/// Output a returnable template from this mustache template
rendered_template render() const
{
context empty_ctx;
std::vector<context*> stack;
stack.emplace_back(&empty_ctx);
std::string ret;
render_internal(0, fragments_.size()-1, stack, ret, 0);
return ret;
render_internal(0, fragments_.size() - 1, stack, ret, 0);
return rendered_template(ret);
}
std::string render(context& ctx)
/// Apply the values from the context provided and output a returnable template from this mustache template
rendered_template render(context& ctx) const
{
std::vector<context*> stack;
stack.emplace_back(&ctx);
std::string ret;
render_internal(0, fragments_.size()-1, stack, ret, 0);
render_internal(0, fragments_.size() - 1, stack, ret, 0);
return rendered_template(ret);
}
/// Output a returnable template from this mustache template
std::string render_string() const
{
context empty_ctx;
std::vector<context*> stack;
stack.emplace_back(&empty_ctx);
std::string ret;
render_internal(0, fragments_.size() - 1, stack, ret, 0);
return ret;
}
/// Apply the values from the context provided and output a returnable template from this mustache template
std::string render_string(context& ctx) const
{
std::vector<context*> stack;
stack.emplace_back(&ctx);
std::string ret;
render_internal(0, fragments_.size() - 1, stack, ret, 0);
return ret;
}
private:
void parse()
{
std::string tag_open = "{{";
std::string tag_close = "}}";
std::vector<int> blockPositions;
size_t current = 0;
while(1)
while (1)
{
size_t idx = body_.find(tag_open, current);
if (idx == body_.npos)
@ -366,27 +434,31 @@ namespace crow
throw invalid_template_exception("not matched opening tag");
}
current = endIdx + tag_close.size();
switch(body_[idx])
switch (body_[idx])
{
case '#':
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
blockPositions.emplace_back(static_cast<int>(actions_.size()));
actions_.emplace_back(ActionType::OpenBlock, idx, endIdx);
break;
case '/':
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
{
auto& matched = actions_[blockPositions.back()];
if (body_.compare(idx, endIdx-idx,
body_, matched.start, matched.end - matched.start) != 0)
if (body_.compare(idx, endIdx - idx,
body_, matched.start, matched.end - matched.start) != 0)
{
throw invalid_template_exception("not matched {{# {{/ pair: " +
body_.substr(matched.start, matched.end - matched.start) + ", " +
body_.substr(idx, endIdx-idx));
throw invalid_template_exception("not matched {{# {{/ pair: " +
body_.substr(matched.start, matched.end - matched.start) + ", " +
body_.substr(idx, endIdx - idx));
}
matched.pos = actions_.size();
}
@ -395,68 +467,79 @@ namespace crow
break;
case '^':
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
blockPositions.emplace_back(static_cast<int>(actions_.size()));
actions_.emplace_back(ActionType::ElseBlock, idx, endIdx);
break;
case '!':
// do nothing action
actions_.emplace_back(ActionType::Ignore, idx+1, endIdx);
actions_.emplace_back(ActionType::Ignore, idx + 1, endIdx);
break;
case '>': // partial
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
actions_.emplace_back(ActionType::Partial, idx, endIdx);
break;
case '{':
if (tag_open != "{{" || tag_close != "}}")
throw invalid_template_exception("cannot use triple mustache when delimiter changed");
idx ++;
if (body_[endIdx+2] != '}')
idx++;
if (body_[endIdx + 2] != '}')
{
throw invalid_template_exception("{{{: }}} not matched");
}
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
current++;
break;
case '&':
idx ++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
idx++;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
break;
case '=':
// tag itself is no-op
idx ++;
idx++;
actions_.emplace_back(ActionType::Ignore, idx, endIdx);
endIdx --;
endIdx--;
if (body_[endIdx] != '=')
throw invalid_template_exception("{{=: not matching = tag: "+body_.substr(idx, endIdx-idx));
endIdx --;
while(body_[idx] == ' ') idx++;
while(body_[endIdx] == ' ') endIdx--;
throw invalid_template_exception("{{=: not matching = tag: " + body_.substr(idx, endIdx - idx));
endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx] == ' ')
endIdx--;
endIdx++;
{
bool succeeded = false;
for(size_t i = idx; i < endIdx; i++)
for (size_t i = idx; i < endIdx; i++)
{
if (body_[i] == ' ')
{
tag_open = body_.substr(idx, i-idx);
while(body_[i] == ' ') i++;
tag_close = body_.substr(i, endIdx-i);
tag_open = body_.substr(idx, i - idx);
while (body_[i] == ' ')
i++;
tag_close = body_.substr(i, endIdx - i);
if (tag_open.empty())
throw invalid_template_exception("{{=: empty open tag");
if (tag_close.empty())
throw invalid_template_exception("{{=: empty close tag");
if (tag_close.find(" ") != tag_close.npos)
throw invalid_template_exception("{{=: invalid open/close tag: "+tag_open+" " + tag_close);
throw invalid_template_exception("{{=: invalid open/close tag: " + tag_open + " " + tag_close);
succeeded = true;
break;
}
@ -467,24 +550,26 @@ namespace crow
break;
default:
// normal tag case;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
while (body_[idx] == ' ')
idx++;
while (body_[endIdx - 1] == ' ')
endIdx--;
actions_.emplace_back(ActionType::Tag, idx, endIdx);
break;
}
}
// removing standalones
for(int i = actions_.size()-2; i >= 0; i --)
for (int i = actions_.size() - 2; i >= 0; i--)
{
if (actions_[i].t == ActionType::Tag || actions_[i].t == ActionType::UnescapeTag)
continue;
auto& fragment_before = fragments_[i];
auto& fragment_after = fragments_[i+1];
bool is_last_action = i == static_cast<int>(actions_.size())-2;
auto& fragment_after = fragments_[i + 1];
bool is_last_action = i == static_cast<int>(actions_.size()) - 2;
bool all_space_before = true;
int j, k;
for(j = fragment_before.second-1;j >= fragment_before.first;j--)
for (j = fragment_before.second - 1; j >= fragment_before.first; j--)
{
if (body_[j] != ' ')
{
@ -497,7 +582,7 @@ namespace crow
if (!all_space_before && body_[j] != '\n')
continue;
bool all_space_after = true;
for(k = fragment_after.first; k < static_cast<int>(body_.size()) && k < fragment_after.second; k ++)
for (k = fragment_after.first; k < static_cast<int>(body_.size()) && k < fragment_after.second; k++)
{
if (body_[k] != ' ')
{
@ -507,31 +592,30 @@ namespace crow
}
if (all_space_after && !is_last_action)
continue;
if (!all_space_after &&
!(
body_[k] == '\n'
||
(body_[k] == '\r' &&
k + 1 < static_cast<int>(body_.size()) &&
body_[k+1] == '\n')))
if (!all_space_after &&
!(
body_[k] == '\n' ||
(body_[k] == '\r' &&
k + 1 < static_cast<int>(body_.size()) &&
body_[k + 1] == '\n')))
continue;
if (actions_[i].t == ActionType::Partial)
{
actions_[i].pos = fragment_before.second - j - 1;
}
fragment_before.second = j+1;
fragment_before.second = j + 1;
if (!all_space_after)
{
if (body_[k] == '\n')
k++;
else
else
k += 2;
fragment_after.first = k;
}
}
}
std::vector<std::pair<int,int>> fragments_;
std::vector<std::pair<int, int>> fragments_;
std::vector<Action> actions_;
std::string body_;
};
@ -547,7 +631,14 @@ namespace crow
static std::string template_base_directory = "templates";
return template_base_directory;
}
}
/// A base directory not related to any blueprint
inline std::string& get_global_template_base_directory_ref()
{
static std::string template_base_directory = "templates";
return template_base_directory;
}
} // namespace detail
inline std::string default_loader(const std::string& filename)
{
@ -566,18 +657,29 @@ namespace crow
namespace detail
{
inline std::function<std::string (std::string)>& get_loader_ref()
inline std::function<std::string(std::string)>& get_loader_ref()
{
static std::function<std::string (std::string)> loader = default_loader;
static std::function<std::string(std::string)> loader = default_loader;
return loader;
}
}
} // namespace detail
inline void set_base(const std::string& path)
{
auto& base = detail::get_template_base_directory_ref();
base = path;
if (base.back() != '\\' &&
if (base.back() != '\\' &&
base.back() != '/')
{
base += '/';
}
}
inline void set_global_base(const std::string& path)
{
auto& base = detail::get_global_template_base_directory_ref();
base = path;
if (base.back() != '\\' &&
base.back() != '/')
{
base += '/';
@ -590,13 +692,27 @@ namespace crow
}
inline std::string load_text(const std::string& filename)
{
std::string filename_sanitized(filename);
utility::sanitize_filename(filename_sanitized);
return detail::get_loader_ref()(filename_sanitized);
}
inline std::string load_text_unsafe(const std::string& filename)
{
return detail::get_loader_ref()(filename);
}
inline template_t load(const std::string& filename)
{
std::string filename_sanitized(filename);
utility::sanitize_filename(filename_sanitized);
return compile(detail::get_loader_ref()(filename_sanitized));
}
inline template_t load_unsafe(const std::string& filename)
{
return compile(detail::get_loader_ref()(filename));
}
}
}
} // namespace mustache
} // namespace crow

View File

@ -12,9 +12,9 @@ namespace crow
{
/// A wrapper for `nodejs/http-parser`.
/// Used to generate a \ref crow.request from the TCP socket buffer.
///
template <typename Handler>
/// Used to generate a \ref crow.request from the TCP socket buffer.
template<typename Handler>
struct HTTPParser : public http_parser
{
static int on_message_begin(http_parser* self_)
@ -26,7 +26,7 @@ namespace crow
static int on_url(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
self->raw_url.insert(self->raw_url.end(), at, at+length);
self->raw_url.insert(self->raw_url.end(), at, at + length);
return 0;
}
static int on_header_field(http_parser* self_, const char* at, size_t length)
@ -39,11 +39,11 @@ namespace crow
{
self->headers.emplace(std::move(self->header_field), std::move(self->header_value));
}
self->header_field.assign(at, at+length);
self->header_field.assign(at, at + length);
self->header_building_state = 1;
break;
case 1:
self->header_field.insert(self->header_field.end(), at, at+length);
self->header_field.insert(self->header_field.end(), at, at + length);
break;
}
return 0;
@ -54,11 +54,11 @@ namespace crow
switch (self->header_building_state)
{
case 0:
self->header_value.insert(self->header_value.end(), at, at+length);
self->header_value.insert(self->header_value.end(), at, at + length);
break;
case 1:
self->header_building_state = 0;
self->header_value.assign(at, at+length);
self->header_value.assign(at, at + length);
break;
}
return 0;
@ -70,13 +70,16 @@ namespace crow
{
self->headers.emplace(std::move(self->header_field), std::move(self->header_value));
}
self->set_connection_parameters();
self->process_header();
return 0;
}
static int on_body(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
self->body.insert(self->body.end(), at, at+length);
self->body.insert(self->body.end(), at, at + length);
return 0;
}
static int on_message_complete(http_parser* self_)
@ -84,16 +87,16 @@ namespace crow
HTTPParser* self = static_cast<HTTPParser*>(self_);
// url params
self->url = self->raw_url.substr(0, self->raw_url.find("?"));
self->url = self->raw_url.substr(0, self->qs_point != 0 ? self->qs_point : std::string::npos);
self->url_params = query_string(self->raw_url);
self->process_message();
return 0;
}
HTTPParser(Handler* handler) :
handler_(handler)
HTTPParser(Handler* handler):
handler_(handler)
{
http_parser_init(this, HTTP_REQUEST);
http_parser_init(this);
}
// return false on error
@ -101,18 +104,17 @@ namespace crow
bool feed(const char* buffer, int length)
{
const static http_parser_settings settings_{
on_message_begin,
on_url,
nullptr,
on_header_field,
on_header_value,
on_headers_complete,
on_body,
on_message_complete,
on_message_begin,
on_url,
on_header_field,
on_header_value,
on_headers_complete,
on_body,
on_message_complete,
};
int nparsed = http_parser_execute(this, &settings_, buffer, length);
if (http_errno != HPE_OK)
if (http_errno != CHPE_OK)
{
return false;
}
@ -128,38 +130,48 @@ namespace crow
{
url.clear();
raw_url.clear();
header_building_state = 0;
header_field.clear();
header_value.clear();
headers.clear();
url_params.clear();
body.clear();
header_building_state = 0;
qs_point = 0;
http_major = 0;
http_minor = 0;
keep_alive = false;
close_connection = false;
}
void process_header()
inline void process_header()
{
handler_->handle_header();
}
void process_message()
inline void process_message()
{
handler_->handle();
}
inline void set_connection_parameters()
{
//NOTE(EDev): it seems that the problem is with crow's policy on closing the connection for HTTP_VERSION < 1.0, the behaviour for that in crow is "don't close the connection, but don't send a keep-alive either"
// HTTP1.1 = always send keep_alive, HTTP1.0 = only send if header exists, HTTP?.? = never send
keep_alive = (http_major == 1 && http_minor == 0) ?
((flags & F_CONNECTION_KEEP_ALIVE) ? true : false) :
((http_major == 1 && http_minor == 1) ? true : false);
// HTTP1.1 = only close if close header exists, HTTP1.0 = always close unless keep_alive header exists, HTTP?.?= never close
close_connection = (http_major == 1 && http_minor == 0) ?
((flags & F_CONNECTION_KEEP_ALIVE) ? false : true) :
((http_major == 1 && http_minor == 1) ? ((flags & F_CONNECTION_CLOSE) ? true : false) : false);
}
/// Take the parsed HTTP request data and convert it to a \ref crow.request
request to_request() const
{
return request{static_cast<HTTPMethod>(method), std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body)};
}
bool is_upgrade() const
{
return upgrade;
}
bool check_version(int major, int minor) const
{
return http_major == major && http_minor == minor;
return request{static_cast<HTTPMethod>(method), std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body), http_major, http_minor, keep_alive, close_connection, static_cast<bool>(upgrade)};
}
std::string raw_url;
@ -171,7 +183,9 @@ namespace crow
ci_map headers;
query_string url_params; ///< What comes after the `?` in the URL.
std::string body;
bool keep_alive; ///< Whether or not the server should send a `connection: Keep-Alive` header to the client.
bool close_connection; ///< Whether or not the server should shut down the TCP connection once a response is sent.
Handler* handler_; ///< This is currently an HTTP connection object (\ref crow.Connection).
};
}
} // namespace crow

View File

@ -363,6 +363,7 @@ namespace crow
}
/// 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
@ -391,6 +392,7 @@ namespace crow
}
/// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list.
///
/// 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
@ -429,6 +431,7 @@ namespace crow
}
/// Works similar to \ref get_list() except the brackets are mandatory must not be empty.
///
/// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
///

View File

@ -4,15 +4,16 @@
namespace crow
{
/// An abstract class that allows any other class to be returned by a handler.
struct returnable
{
std::string content_type;
virtual std::string dump() const = 0;
/// An abstract class that allows any other class to be returned by a handler.
struct returnable
{
std::string content_type;
virtual std::string dump() const = 0;
returnable(std::string ctype) : content_type {ctype}
{}
returnable(std::string ctype):
content_type{ctype}
{}
virtual ~returnable(){};
};
}
virtual ~returnable(){};
};
} // namespace crow

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
#pragma once
// settings for crow
// TODO - replace with runtime config. libucl?
// TODO(ipkn) replace with runtime config. libucl?
/* #ifdef - enables debug mode */
//#define CROW_ENABLE_DEBUG
@ -8,8 +8,8 @@
/* #ifdef - enables logging */
#define CROW_ENABLE_LOGGING
/* #ifdef - enables ssl */
//#define CROW_ENABLE_SSL
/* #ifdef - enforces section 5.2 and 6.1 of RFC6455 (only accepting masked messages from clients) */
//#define CROW_ENFORCE_WS_SPEC
/* #define - specifies log level */
/*
@ -54,3 +54,11 @@
#define noexcept throw()
#endif
#endif
#if defined(__GNUC__) && __GNUC__ == 8 && __GNUC_MINOR__ < 4
#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."
#endif
#endif

View File

@ -14,14 +14,13 @@ namespace crow
using namespace boost;
using tcp = asio::ip::tcp;
///A wrapper for the asio::ip::tcp::socket and asio::ssl::stream
/// A wrapper for the asio::ip::tcp::socket and asio::ssl::stream
struct SocketAdaptor
{
using context = void;
SocketAdaptor(boost::asio::io_service& io_service, context*)
: socket_(io_service)
{
}
SocketAdaptor(boost::asio::io_service& io_service, context*):
socket_(io_service)
{}
boost::asio::io_service& get_io_service()
{
@ -74,7 +73,7 @@ namespace crow
socket_.shutdown(boost::asio::socket_base::shutdown_type::shutdown_receive, ec);
}
template <typename F>
template<typename F>
void start(F f)
{
f(boost::system::error_code());
@ -88,10 +87,9 @@ namespace crow
{
using context = boost::asio::ssl::context;
using ssl_socket_t = boost::asio::ssl::stream<tcp::socket>;
SSLAdaptor(boost::asio::io_service& io_service, context* ctx)
: ssl_socket_(new ssl_socket_t(io_service, *ctx))
{
}
SSLAdaptor(boost::asio::io_service& io_service, context* ctx):
ssl_socket_(new ssl_socket_t(io_service, *ctx))
{}
boost::asio::ssl::stream<tcp::socket>& socket()
{
@ -99,7 +97,7 @@ namespace crow
}
tcp::socket::lowest_layer_type&
raw_socket()
raw_socket()
{
return ssl_socket_->lowest_layer();
}
@ -155,16 +153,16 @@ namespace crow
return GET_IO_SERVICE(raw_socket());
}
template <typename F>
template<typename F>
void start(F f)
{
ssl_socket_->async_handshake(boost::asio::ssl::stream_base::server,
[f](const boost::system::error_code& ec) {
f(ec);
});
[f](const boost::system::error_code& ec) {
f(ec);
});
}
std::unique_ptr<boost::asio::ssl::stream<tcp::socket>> ssl_socket_;
};
#endif
}
} // namespace crow

132
include/crow/task_timer.h Normal file
View File

@ -0,0 +1,132 @@
#pragma once
#include <boost/asio.hpp>
#include <chrono>
#include <functional>
#include <map>
#include <vector>
#include "crow/logging.h"
namespace crow
{
namespace detail
{
/// A class for scheduling functions to be called after a specific amount of ticks. A tick is equal to 1 second.
class task_timer
{
public:
using task_type = std::function<void()>;
using identifier_type = size_t;
private:
using clock_type = std::chrono::steady_clock;
using time_type = clock_type::time_point;
public:
task_timer(boost::asio::io_service& io_service):
io_service_(io_service), deadline_timer_(io_service_)
{
deadline_timer_.expires_from_now(boost::posix_time::seconds(1));
deadline_timer_.async_wait(
std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
}
~task_timer() { deadline_timer_.cancel(); }
void cancel(identifier_type id)
{
tasks_.erase(id);
CROW_LOG_DEBUG << "task_timer cancelled: " << this << ' ' << id;
}
/// Schedule the given task to be executed after the default amount of ticks.
///
/// \return identifier_type Used to cancel the thread.
/// It is not bound to this task_timer instance and in some cases could lead to
/// undefined behavior if used with other task_timer objects or after the task
/// has been successfully executed.
identifier_type schedule(const task_type& task)
{
tasks_.insert(
{++highest_id_,
{clock_type::now() + std::chrono::seconds(get_default_timeout()),
task}});
CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_;
return highest_id_;
}
/// Schedule the given task to be executed after the given time.
///
/// \param timeout The amount of ticks (seconds) to wait before execution.
///
/// \return identifier_type Used to cancel the thread.
/// It is not bound to this task_timer instance and in some cases could lead to
/// undefined behavior if used with other task_timer objects or after the task
/// has been successfully executed.
identifier_type schedule(const task_type& task, std::uint8_t timeout)
{
tasks_.insert({++highest_id_,
{clock_type::now() + std::chrono::seconds(timeout), task}});
CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' << highest_id_;
return highest_id_;
}
/// Set the default timeout for this task_timer instance. (Default: 5)
///
/// \param timeout The amount of ticks (seconds) to wait before execution.
void set_default_timeout(std::uint8_t timeout) { default_timeout_ = timeout; }
/// Get the default timeout. (Default: 5)
std::uint8_t get_default_timeout() const { return default_timeout_; }
private:
void process_tasks()
{
time_type current_time = clock_type::now();
std::vector<identifier_type> finished_tasks;
for (const auto& task : tasks_)
{
if (task.second.first < current_time)
{
(task.second.second)();
finished_tasks.push_back(task.first);
CROW_LOG_DEBUG << "task_timer called: " << this << ' ' << task.first;
}
}
for (const auto& task : finished_tasks)
tasks_.erase(task);
// If no task is currently scheduled, reset the issued ids back to 0.
if (tasks_.empty()) highest_id_ = 0;
}
void tick_handler(const boost::system::error_code& ec)
{
if (ec) return;
process_tasks();
deadline_timer_.expires_from_now(boost::posix_time::seconds(1));
deadline_timer_.async_wait(
std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
}
private:
std::uint8_t default_timeout_{5};
boost::asio::io_service& io_service_;
boost::asio::deadline_timer deadline_timer_;
std::map<identifier_type, std::pair<time_type, task_type>> tasks_;
// A continuosly increasing number to be issued to threads to identify them.
// If no tasks are scheduled, it will be reset to 0.
identifier_type highest_id_{0};
};
} // namespace detail
} // namespace crow

View File

@ -11,16 +11,28 @@
#include "crow/settings.h"
// TODO(EDev): Adding C++20's [[likely]] and [[unlikely]] attributes might be useful
#if defined(__GNUG__) || defined(__clang__)
#define CROW_LIKELY(X) __builtin_expect(!!(X), 1)
#define CROW_UNLIKELY(X) __builtin_expect(!!(X), 0)
#else
#define CROW_LIKELY(X) (X)
#define CROW_UNLIKELY(X) (X)
#endif
namespace crow
{
/// @cond SKIP
namespace black_magic
{
#ifndef CROW_MSVC_WORKAROUND
/// Out of Range Exception for const_str
struct OutOfRange
{
OutOfRange(unsigned /*pos*/, unsigned /*length*/) {}
};
constexpr unsigned requires_in_range( unsigned i, unsigned len )
/// Helper function to throw an exception if i is larger than len
constexpr unsigned requires_in_range(unsigned i, unsigned len)
{
return i >= len ? throw OutOfRange(i, len) : i;
}
@ -28,73 +40,65 @@ namespace crow
/// A constant string implementation.
class const_str
{
const char * const begin_;
const char* const begin_;
unsigned size_;
public:
template< unsigned N >
constexpr const_str( const char(&arr)[N] ) : begin_(arr), size_(N - 1) {
static_assert( N >= 1, "not a string literal");
}
constexpr char operator[]( unsigned i ) const {
return requires_in_range(i, size_), begin_[i];
public:
template<unsigned N>
constexpr const_str(const char (&arr)[N]):
begin_(arr), size_(N - 1)
{
static_assert(N >= 1, "not a string literal");
}
constexpr char operator[](unsigned i) const
{
return requires_in_range(i, size_), begin_[i];
}
constexpr operator const char *() const {
return begin_;
constexpr operator const char*() const
{
return begin_;
}
constexpr const char* begin() const { return begin_; }
constexpr const char* end() const { return begin_ + size_; }
constexpr unsigned size() const {
return size_;
constexpr unsigned size() const
{
return size_;
}
};
constexpr unsigned find_closing_tag(const_str s, unsigned p)
{
return s[p] == '>' ? p : find_closing_tag(s, p+1);
return s[p] == '>' ? p : find_closing_tag(s, p + 1);
}
/// Check that the CROW_ROUTE string is valid
constexpr bool is_valid(const_str s, unsigned i = 0, int f = 0)
{
return
i == s.size()
? f == 0 :
f < 0 || f >= 2
? false :
s[i] == '<'
? is_valid(s, i+1, f+1) :
s[i] == '>'
? is_valid(s, i+1, f-1) :
is_valid(s, i+1, f);
return i == s.size() ? f == 0 :
f < 0 || f >= 2 ? false :
s[i] == '<' ? is_valid(s, i + 1, f + 1) :
s[i] == '>' ? is_valid(s, i + 1, f - 1) :
is_valid(s, i + 1, f);
}
constexpr bool is_equ_p(const char* a, const char* b, unsigned n)
{
return
*a == 0 && *b == 0 && n == 0
? true :
(*a == 0 || *b == 0)
? false :
n == 0
? true :
*a != *b
? false :
is_equ_p(a+1, b+1, n-1);
return *a == 0 && *b == 0 && n == 0 ? true :
(*a == 0 || *b == 0) ? false :
n == 0 ? true :
*a != *b ? false :
is_equ_p(a + 1, b + 1, n - 1);
}
constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n)
{
return
ai + n > a.size() || bi + n > b.size()
? false :
n == 0
? true :
a[ai] != b[bi]
? false :
is_equ_n(a,ai+1,b,bi+1,n-1);
return ai + n > a.size() || bi + n > b.size() ? false :
n == 0 ? true :
a[ai] != b[bi] ? false :
is_equ_n(a, ai + 1, b, bi + 1, n - 1);
}
constexpr bool is_int(const_str s, unsigned i)
@ -110,13 +114,13 @@ namespace crow
constexpr bool is_float(const_str s, unsigned i)
{
return is_equ_n(s, i, "<float>", 0, 7) ||
is_equ_n(s, i, "<double>", 0, 8);
is_equ_n(s, i, "<double>", 0, 8);
}
constexpr bool is_str(const_str s, unsigned i)
{
return is_equ_n(s, i, "<str>", 0, 5) ||
is_equ_n(s, i, "<string>", 0, 8);
is_equ_n(s, i, "<string>", 0, 8);
}
constexpr bool is_path(const_str s, unsigned i)
@ -124,17 +128,17 @@ namespace crow
return is_equ_n(s, i, "<path>", 0, 6);
}
#endif
template <typename T>
template<typename T>
struct parameter_tag
{
static const int value = 0;
};
#define CROW_INTERNAL_PARAMETER_TAG(t, i) \
template <> \
struct parameter_tag<t> \
{ \
static const int value = i; \
}
template<> \
struct parameter_tag<t> \
{ \
static const int value = i; \
}
CROW_INTERNAL_PARAMETER_TAG(int, 1);
CROW_INTERNAL_PARAMETER_TAG(char, 1);
CROW_INTERNAL_PARAMETER_TAG(short, 1);
@ -148,24 +152,22 @@ struct parameter_tag<t> \
CROW_INTERNAL_PARAMETER_TAG(double, 3);
CROW_INTERNAL_PARAMETER_TAG(std::string, 4);
#undef CROW_INTERNAL_PARAMETER_TAG
template <typename ... Args>
template<typename... Args>
struct compute_parameter_tag_from_args_list;
template <>
template<>
struct compute_parameter_tag_from_args_list<>
{
static const int value = 0;
};
template <typename Arg, typename ... Args>
template<typename Arg, typename... Args>
struct compute_parameter_tag_from_args_list<Arg, Args...>
{
static const int sub_value =
compute_parameter_tag_from_args_list<Args...>::value;
static const int value =
parameter_tag<typename std::decay<Arg>::type>::value
? sub_value* 6 + parameter_tag<typename std::decay<Arg>::type>::value
: sub_value;
static const int sub_value =
compute_parameter_tag_from_args_list<Args...>::value;
static const int value =
parameter_tag<typename std::decay<Arg>::type>::value ? sub_value * 6 + parameter_tag<typename std::decay<Arg>::type>::value : sub_value;
};
static inline bool is_parameter_tag_compatible(uint64_t a, uint64_t b)
@ -174,237 +176,251 @@ struct parameter_tag<t> \
return b == 0;
if (b == 0)
return a == 0;
int sa = a%6;
int sb = a%6;
int sa = a % 6;
int sb = a % 6;
if (sa == 5) sa = 4;
if (sb == 5) sb = 4;
if (sa != sb)
return false;
return is_parameter_tag_compatible(a/6, b/6);
return is_parameter_tag_compatible(a / 6, b / 6);
}
static inline unsigned find_closing_tag_runtime(const char* s, unsigned p)
{
return
s[p] == 0
? throw std::runtime_error("unmatched tag <") :
s[p] == '>'
? p : find_closing_tag_runtime(s, p + 1);
return s[p] == 0 ? throw std::runtime_error("unmatched tag <") :
s[p] == '>' ? p :
find_closing_tag_runtime(s, p + 1);
}
static inline uint64_t get_parameter_tag_runtime(const char* s, unsigned p = 0)
{
return
s[p] == 0
? 0 :
s[p] == '<' ? (
std::strncmp(s+p, "<int>", 5) == 0
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 1 :
std::strncmp(s+p, "<uint>", 6) == 0
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 2 :
(std::strncmp(s+p, "<float>", 7) == 0 ||
std::strncmp(s+p, "<double>", 8) == 0)
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 3 :
(std::strncmp(s+p, "<str>", 5) == 0 ||
std::strncmp(s+p, "<string>", 8) == 0)
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 4 :
std::strncmp(s+p, "<path>", 6) == 0
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 5 :
throw std::runtime_error("invalid parameter type")
) :
get_parameter_tag_runtime(s, p+1);
return s[p] == 0 ? 0 :
s[p] == '<' ? (
std::strncmp(s + p, "<int>", 5) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 1 :
std::strncmp(s + p, "<uint>", 6) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 2 :
(std::strncmp(s + p, "<float>", 7) == 0 ||
std::strncmp(s + p, "<double>", 8) == 0) ?
get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 3 :
(std::strncmp(s + p, "<str>", 5) == 0 ||
std::strncmp(s + p, "<string>", 8) == 0) ?
get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 4 :
std::strncmp(s + p, "<path>", 6) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 5 :
throw std::runtime_error("invalid parameter type")) :
get_parameter_tag_runtime(s, p + 1);
}
#ifndef CROW_MSVC_WORKAROUND
constexpr uint64_t get_parameter_tag(const_str s, unsigned p = 0)
{
return
p == s.size()
? 0 :
s[p] == '<' ? (
is_int(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 :
is_uint(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 :
is_float(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 :
is_str(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 :
is_path(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 :
throw std::runtime_error("invalid parameter type")
) :
get_parameter_tag(s, p+1);
return p == s.size() ? 0 :
s[p] == '<' ? (
is_int(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 :
is_uint(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 :
is_float(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 :
is_str(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 :
is_path(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 :
throw std::runtime_error("invalid parameter type")) :
get_parameter_tag(s, p + 1);
}
#endif
template <typename ... T>
template<typename... T>
struct S
{
template <typename U>
template<typename U>
using push = S<U, T...>;
template <typename U>
template<typename U>
using push_back = S<T..., U>;
template <template<typename ... Args> class U>
template<template<typename... Args> class U>
using rebind = U<T...>;
};
template <typename F, typename Set>
// Check whether the template function can be called with specific arguments
template<typename F, typename Set>
struct CallHelper;
template <typename F, typename ...Args>
template<typename F, typename... Args>
struct CallHelper<F, S<Args...>>
{
template <typename F1, typename ...Args1, typename =
decltype(std::declval<F1>()(std::declval<Args1>()...))
>
template<typename F1, typename... Args1, typename = decltype(std::declval<F1>()(std::declval<Args1>()...))>
static char __test(int);
template <typename ...>
template<typename...>
static int __test(...);
static constexpr bool value = sizeof(__test<F, Args...>(0)) == sizeof(char);
};
// Check Tuple contains type T
template<typename T, typename Tuple>
struct has_type;
template <int N>
template<typename T>
struct has_type<T, std::tuple<>> : std::false_type
{};
template<typename T, typename U, typename... Ts>
struct has_type<T, std::tuple<U, Ts...>> : has_type<T, std::tuple<Ts...>>
{};
template<typename T, typename... Ts>
struct has_type<T, std::tuple<T, Ts...>> : std::true_type
{};
// Kind of fold expressions in C++11
template<bool...>
struct bool_pack;
template<bool... bs>
using all_true = std::is_same<bool_pack<bs..., true>, bool_pack<true, bs...>>;
template<int N>
struct single_tag_to_type
{
};
{};
template <>
template<>
struct single_tag_to_type<1>
{
using type = int64_t;
};
template <>
template<>
struct single_tag_to_type<2>
{
using type = uint64_t;
};
template <>
template<>
struct single_tag_to_type<3>
{
using type = double;
};
template <>
template<>
struct single_tag_to_type<4>
{
using type = std::string;
};
template <>
template<>
struct single_tag_to_type<5>
{
using type = std::string;
};
template <uint64_t Tag>
template<uint64_t Tag>
struct arguments
{
using subarguments = typename arguments<Tag/6>::type;
using type =
typename subarguments::template push<typename single_tag_to_type<Tag%6>::type>;
using subarguments = typename arguments<Tag / 6>::type;
using type =
typename subarguments::template push<typename single_tag_to_type<Tag % 6>::type>;
};
template <>
template<>
struct arguments<0>
{
using type = S<>;
};
template <typename ... T>
template<typename... T>
struct last_element_type
{
using type = typename std::tuple_element<sizeof...(T)-1, std::tuple<T...>>::type;
using type = typename std::tuple_element<sizeof...(T) - 1, std::tuple<T...>>::type;
};
template <>
template<>
struct last_element_type<>
{
};
{};
// from http://stackoverflow.com/questions/13072359/c11-compile-time-array-with-logarithmic-evaluation-depth
template<class T> using Invoke = typename T::type;
template<class T>
using Invoke = typename T::type;
template<unsigned...> struct seq{ using type = seq; };
template<unsigned...>
struct seq
{
using type = seq;
};
template<class S1, class S2> struct concat;
template<class S1, class S2>
struct concat;
template<unsigned... I1, unsigned... I2>
struct concat<seq<I1...>, seq<I2...>>
: seq<I1..., (sizeof...(I1)+I2)...>{};
struct concat<seq<I1...>, seq<I2...>> : seq<I1..., (sizeof...(I1) + I2)...>
{};
template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;
template<unsigned N> struct gen_seq;
template<unsigned N> using GenSeq = Invoke<gen_seq<N>>;
template<unsigned N>
struct gen_seq;
template<unsigned N>
using GenSeq = Invoke<gen_seq<N>>;
template<unsigned N>
struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};
struct gen_seq : Concat<GenSeq<N / 2>, GenSeq<N - N / 2>>
{};
template<> struct gen_seq<0> : seq<>{};
template<> struct gen_seq<1> : seq<0>{};
template<>
struct gen_seq<0> : seq<>
{};
template<>
struct gen_seq<1> : seq<0>
{};
template <typename Seq, typename Tuple>
template<typename Seq, typename Tuple>
struct pop_back_helper;
template <unsigned ... N, typename Tuple>
template<unsigned... N, typename Tuple>
struct pop_back_helper<seq<N...>, Tuple>
{
template <template <typename ... Args> class U>
template<template<typename... Args> class U>
using rebind = U<typename std::tuple_element<N, Tuple>::type...>;
};
template <typename ... T>
template<typename... T>
struct pop_back //: public pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>
{
template <template <typename ... Args> class U>
using rebind = typename pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>::template rebind<U>;
template<template<typename... Args> class U>
using rebind = typename pop_back_helper<typename gen_seq<sizeof...(T) - 1>::type, std::tuple<T...>>::template rebind<U>;
};
template <>
template<>
struct pop_back<>
{
template <template <typename ... Args> class U>
template<template<typename... Args> class U>
using rebind = U<>;
};
// from http://stackoverflow.com/questions/2118541/check-if-c0x-parameter-pack-contains-a-type
template < typename Tp, typename... List >
struct contains : std::true_type {};
template<typename Tp, typename... List>
struct contains : std::true_type
{};
template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...>
: std::conditional< std::is_same<Tp, Head>::value,
std::true_type,
contains<Tp, Rest...>
>::type {};
template<typename Tp, typename Head, typename... Rest>
struct contains<Tp, Head, Rest...> : std::conditional<std::is_same<Tp, Head>::value, std::true_type, contains<Tp, Rest...>>::type
{};
template < typename Tp >
struct contains<Tp> : std::false_type {};
template<typename Tp>
struct contains<Tp> : std::false_type
{};
template <typename T>
template<typename T>
struct empty_context
{
};
{};
template <typename T>
template<typename T>
struct promote
{
using type = T;
};
#define CROW_INTERNAL_PROMOTE_TYPE(t1, t2) \
template<> \
struct promote<t1> \
{ \
using type = t2; \
}
template<> \
struct promote<t1> \
{ \
using type = t2; \
}
CROW_INTERNAL_PROMOTE_TYPE(char, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(short, int64_t);
@ -419,7 +435,7 @@ template <typename F, typename Set>
CROW_INTERNAL_PROMOTE_TYPE(float, double);
#undef CROW_INTERNAL_PROMOTE_TYPE
template <typename T>
template<typename T>
using promote_t = typename promote<T>::type;
} // namespace black_magic
@ -427,19 +443,19 @@ template <typename F, typename Set>
namespace detail
{
template <class T, std::size_t N, class... Args>
template<class T, std::size_t N, class... Args>
struct get_index_of_element_from_tuple_by_type_impl
{
static constexpr auto value = N;
};
template <class T, std::size_t N, class... Args>
template<class T, std::size_t N, class... Args>
struct get_index_of_element_from_tuple_by_type_impl<T, N, T, Args...>
{
static constexpr auto value = N;
};
template <class T, std::size_t N, class U, class... Args>
template<class T, std::size_t N, class U, class... Args>
struct get_index_of_element_from_tuple_by_type_impl<T, N, U, Args...>
{
static constexpr auto value = get_index_of_element_from_tuple_by_type_impl<T, N + 1, Args...>::value;
@ -449,80 +465,80 @@ template <typename F, typename Set>
namespace utility
{
template <class T, class... Args>
template<class T, class... Args>
T& get_element_by_type(std::tuple<Args...>& t)
{
return std::get<detail::get_index_of_element_from_tuple_by_type_impl<T, 0, Args...>::value>(t);
}
template<typename T>
struct function_traits;
template<typename T>
struct function_traits;
#ifndef CROW_MSVC_WORKAROUND
template<typename T>
template<typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{
using parent_t = function_traits<decltype(&T::operator())>;
static const size_t arity = parent_t::arity;
using result_type = typename parent_t::result_type;
template <size_t i>
template<size_t i>
using arg = typename parent_t::template arg<i>;
};
};
#endif
template<typename ClassType, typename R, typename ...Args>
struct function_traits<R(ClassType::*)(Args...) const>
template<typename ClassType, typename R, typename... Args>
struct function_traits<R (ClassType::*)(Args...) const>
{
static const size_t arity = sizeof...(Args);
typedef R result_type;
template <size_t i>
template<size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
template<typename ClassType, typename R, typename ...Args>
struct function_traits<R(ClassType::*)(Args...)>
template<typename ClassType, typename R, typename... Args>
struct function_traits<R (ClassType::*)(Args...)>
{
static const size_t arity = sizeof...(Args);
typedef R result_type;
template <size_t i>
template<size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
template<typename R, typename ...Args>
template<typename R, typename... Args>
struct function_traits<std::function<R(Args...)>>
{
static const size_t arity = sizeof...(Args);
typedef R result_type;
template <size_t i>
template<size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
/// @endcond
inline static std::string base64encode(const unsigned char* data, size_t size, const char* key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
{
std::string ret;
ret.resize((size+2) / 3 * 4);
ret.resize((size + 2) / 3 * 4);
auto it = ret.begin();
while(size >= 3)
while (size >= 3)
{
*it++ = key[(static_cast<unsigned char>(*data)&0xFC)>>2];
*it++ = key[(static_cast<unsigned char>(*data) & 0xFC) >> 2];
unsigned char h = (static_cast<unsigned char>(*data++) & 0x03) << 4;
*it++ = key[h|((static_cast<unsigned char>(*data)&0xF0)>>4)];
*it++ = key[h | ((static_cast<unsigned char>(*data) & 0xF0) >> 4)];
h = (static_cast<unsigned char>(*data++) & 0x0F) << 2;
*it++ = key[h|((static_cast<unsigned char>(*data)&0xC0)>>6)];
*it++ = key[static_cast<unsigned char>(*data++)&0x3F];
*it++ = key[h | ((static_cast<unsigned char>(*data) & 0xC0) >> 6)];
*it++ = key[static_cast<unsigned char>(*data++) & 0x3F];
size -= 3;
}
if (size == 1)
{
*it++ = key[(static_cast<unsigned char>(*data)&0xFC)>>2];
*it++ = key[(static_cast<unsigned char>(*data) & 0xFC) >> 2];
unsigned char h = (static_cast<unsigned char>(*data++) & 0x03) << 4;
*it++ = key[h];
*it++ = '=';
@ -530,9 +546,9 @@ template <typename F, typename Set>
}
else if (size == 2)
{
*it++ = key[(static_cast<unsigned char>(*data)&0xFC)>>2];
*it++ = key[(static_cast<unsigned char>(*data) & 0xFC) >> 2];
unsigned char h = (static_cast<unsigned char>(*data++) & 0x03) << 4;
*it++ = key[h|((static_cast<unsigned char>(*data)&0xF0)>>4)];
*it++ = key[h | ((static_cast<unsigned char>(*data) & 0xF0) >> 4)];
h = (static_cast<unsigned char>(*data++) & 0x0F) << 2;
*it++ = key[h];
*it++ = '=';
@ -555,28 +571,32 @@ template <typename F, typename Set>
return base64encode((const unsigned char*)data.c_str(), size, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
}
inline static std::string base64decode(const char* data, size_t size, bool urlsafe = false)
inline static std::string base64decode(const char* data, size_t size)
{
std::unordered_map<char, unsigned char> key ({
{'A', 0},{'B', 1},{'C', 2},{'D', 3},{'E', 4},{'F', 5},{'G', 6},{'H', 7},{'I', 8},{'J', 9},
{'K', 10},{'L', 11},{'M', 12},{'N', 13},{'O', 14},{'P', 15},{'Q', 16},{'R', 17},{'S', 18},{'T', 19},
{'U', 20},{'V', 21},{'W', 22},{'X', 23},{'Y', 24},{'Z', 25},{'a', 26},{'b', 27},{'c', 28},{'d', 29},
{'e', 30},{'f', 31},{'g', 32},{'h', 33},{'i', 34},{'j', 35},{'k', 36},{'l', 37},{'m', 38},{'n', 39},
{'o', 40},{'p', 41},{'q', 42},{'r', 43},{'s', 44},{'t', 45},{'u', 46},{'v', 47},{'w', 48},{'x', 49},
{'y', 50},{'z', 51},{'0', 52},{'1', 53},{'2', 54},{'3', 55},{'4', 56},{'5', 57},{'6', 58},{'7', 59},
{'8', 60},{'9', 61},{urlsafe ? '-' : '+', 62},{urlsafe ? '_' : '/', 63}});
// We accept both regular and url encoding here, as there does not seem to be any downside to that.
// If we want to distinguish that we should use +/ for non-url and -_ for url.
// Mapping logic from characters to [0-63]
auto key = [](char c) -> unsigned char {
if ((c >= 'A') && (c <= 'Z')) return c - 'A';
if ((c >= 'a') && (c <= 'z')) return c - 'a' + 26;
if ((c >= '0') && (c <= '9')) return c - '0' + 52;
if ((c == '+') || (c == '-')) return 62;
if ((c == '/') || (c == '_')) return 63;
return 0;
};
// Not padded
if (size % 4 == 2) // missing last 2 characters
if (size % 4 == 2) // missing last 2 characters
size = (size / 4 * 3) + 1; // Not subtracting extra characters because they're truncated in int division
else if (size % 4 == 3) // missing last character
else if (size % 4 == 3) // missing last character
size = (size / 4 * 3) + 2; // Not subtracting extra characters because they're truncated in int division
// Padded
else if (data[size-2] == '=') // padded with '=='
size = (size / 4 * 3) - 2; // == padding means the last block only has 1 character instead of 3, hence the '-2'
else if (data[size-1] == '=') // padded with '='
size = (size / 4 * 3) - 1; // = padding means the last block only has 2 character instead of 3, hence the '-1'
else if (data[size - 2] == '=') // padded with '=='
size = (size / 4 * 3) - 2; // == padding means the last block only has 1 character instead of 3, hence the '-2'
else if (data[size - 1] == '=') // padded with '='
size = (size / 4 * 3) - 1; // = padding means the last block only has 2 character instead of 3, hence the '-1'
// Padding not needed
else
@ -593,15 +613,15 @@ template <typename F, typename Set>
// Take 4 character blocks to turn into 3
while (size >= 3)
{
// dec_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
odd = key[*data++];
even = key[*data++];
// dec_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
odd = key(*data++);
even = key(*data++);
*it++ = (odd << 2) | ((even & 0x30) >> 4);
// dec_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right))
odd = key[*data++];
// dec_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right))
odd = key(*data++);
*it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2);
// dec_char3 = ((char3 AND 00000011) shifted 6 bits left) OR (char4)
even = key[*data++];
// dec_char3 = ((char3 AND 00000011) shifted 6 bits left) OR (char4)
even = key(*data++);
*it++ = ((odd & 0x03) << 6) | (even);
size -= 3;
@ -609,27 +629,116 @@ template <typename F, typename Set>
if (size == 2)
{
// d_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
odd = key[*data++];
even = key[*data++];
*it++ = (odd << 2) | ((even & 0x30) >> 4);
odd = key(*data++);
even = key(*data++);
*it++ = (odd << 2) | ((even & 0x30) >> 4);
// d_char2 = ((char2 AND 00001111) shifted 4 bits left) OR ((char3 AND 00111100) shifted 2 bits right))
odd = key[*data++];
*it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2);
odd = key(*data++);
*it++ = ((even & 0x0F) << 4) | ((odd & 0x3C) >> 2);
}
else if (size == 1)
{
// d_char1 = (char1 shifted 2 bits to the left) OR ((char2 AND 00110000) shifted 4 bits to the right))
odd = key[*data++];
even = key[*data++];
*it++ = (odd << 2) | ((even & 0x30) >> 4);
odd = key(*data++);
even = key(*data++);
*it++ = (odd << 2) | ((even & 0x30) >> 4);
}
return ret;
}
inline static std::string base64decode(std::string data, size_t size, bool urlsafe = false)
inline static std::string base64decode(const std::string& data, size_t size)
{
return base64decode(data.c_str(), size, urlsafe);
return base64decode(data.data(), size);
}
inline static std::string base64decode(const std::string& data)
{
return base64decode(data.data(), data.length());
}
inline static void sanitize_filename(std::string& data, char replacement = '_')
{
if (data.length() > 255)
data.resize(255);
static const auto toUpper = [](char c) {
return ((c >= 'a') && (c <= 'z')) ? (c - ('a' - 'A')) : c;
};
// Check for special device names. The Windows behavior is really odd here, it will consider both AUX and AUX.txt
// a special device. Thus we search for the string (case-insensitive), and then check if the string ends or if
// is has a dangerous follow up character (.:\/)
auto sanitizeSpecialFile = [](std::string& source, unsigned ofs, const char* pattern, bool includeNumber, char replacement) {
unsigned i = ofs, len = source.length();
const char* p = pattern;
while (*p)
{
if (i >= len) return;
if (toUpper(source[i]) != *p) return;
++i;
++p;
}
if (includeNumber)
{
if ((i >= len) || (source[i] < '1') || (source[i] > '9')) return;
++i;
}
if ((i >= len) || (source[i] == '.') || (source[i] == ':') || (source[i] == '/') || (source[i] == '\\'))
{
source.erase(ofs + 1, (i - ofs) - 1);
source[ofs] = replacement;
}
};
bool checkForSpecialEntries = true;
for (unsigned i = 0; i < data.length(); ++i)
{
// Recognize directory traversals and the special devices CON/PRN/AUX/NULL/COM[1-]/LPT[1-9]
if (checkForSpecialEntries)
{
checkForSpecialEntries = false;
switch (toUpper(data[i]))
{
case 'A':
sanitizeSpecialFile(data, i, "AUX", false, replacement);
break;
case 'C':
sanitizeSpecialFile(data, i, "CON", false, replacement);
sanitizeSpecialFile(data, i, "COM", true, replacement);
break;
case 'L':
sanitizeSpecialFile(data, i, "LPT", true, replacement);
break;
case 'N':
sanitizeSpecialFile(data, i, "NUL", false, replacement);
break;
case 'P':
sanitizeSpecialFile(data, i, "PRN", false, replacement);
break;
case '.':
sanitizeSpecialFile(data, i, "..", false, replacement);
break;
}
}
// Sanitize individual characters
unsigned char c = data[i];
if ((c < ' ') || ((c >= 0x80) && (c <= 0x9F)) || (c == '?') || (c == '<') || (c == '>') || (c == ':') || (c == '*') || (c == '|') || (c == '\"'))
{
data[i] = replacement;
}
else if ((c == '/') || (c == '\\'))
{
if (CROW_UNLIKELY(i == 0)) //Prevent Unix Absolute Paths (Windows Absolute Paths are prevented with `(c == ':')`)
{
data[i] = replacement;
}
else
{
checkForSpecialEntries = true;
}
}
}
}
} // namespace utility
}
} // namespace crow

View File

@ -1,10 +1,6 @@
#pragma once
namespace crow {
#ifdef CROW_MAIN
char VERSION[] = "master";
#else
extern char VERSION[];
#endif
namespace crow
{
constexpr const char VERSION[] = "master";
}

File diff suppressed because it is too large Load Diff

View File

@ -7,78 +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
- Legacy: getting_started/setup/legacy.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
- Writing Tests: guides/testing.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-2021 CrowCpp'
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

@ -3,26 +3,33 @@
#get mime.types file from the nginx repository at nginx/conf/mime.types
#typical output filename: mime_types.h
import sys
from datetime import date
if len(sys.argv) != 3:
print("Usage: {} <NGINX_MIME_TYPE_FILE_PATH> <CROW_OUTPUT_HEADER_PATH>".format(sys.argv[0]))
if (len(sys.argv) != 3) and (len(sys.argv) != 2):
print("Usage (local file): {} <NGINX_MIME_TYPE_FILE_PATH> <CROW_OUTPUT_HEADER_PATH>".format(sys.argv[0]))
print("(downloads file) : {} <CROW_OUTPUT_HEADER_PATH>".format(sys.argv[0]))
sys.exit(1)
file_path = sys.argv[1]
output_path = sys.argv[2]
if len(sys.argv) == 3:
file_path = sys.argv[1]
output_path = sys.argv[2]
elif len(sys.argv) == 2:
import requests
open("mime.types", "wb").write(requests.get("https://hg.nginx.org/nginx/raw-file/tip/conf/mime.types").content)
file_path = "mime.types"
output_path = sys.argv[1]
tabspace = " "
tabandahalfspace = " "
def main():
outLines = []
outLines.append("//This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py")
outLines.append("// This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py on " + date.today().strftime('%Y-%m-%d') + ".")
outLines.extend([
"#include <unordered_map>",
"#include <string>",
"",
"namespace crow {",
"",
"#ifdef CROW_MAIN"
tabspace + "std::unordered_map<std::string, std::string> mime_types {"])
"namespace crow",
"{",
tabspace + "const std::unordered_map<std::string, std::string> mime_types{"])
with open(file_path, "r") as mtfile:
incomplete = ""
@ -41,14 +48,8 @@ def main():
incompleteExists = False
outLines.extend(mime_line_to_cpp(splitLine))
outLines[-1] = outLines[-1][:-1]
outLines.extend([
tabspace + "};",
"#else",
"extern std::unordered_map<std::string, std::string> mime_types;",
"#endif",
"}"
])
outLines[-1] = outLines[-1][:-1] + "};"
outLines.append("}")
with open(output_path, "w") as mtcppfile:
mtcppfile.writelines(x + '\n' for x in outLines)
@ -60,7 +61,7 @@ def mime_line_to_cpp(mlist):
if (mlist[i].endswith(";")):
mlist[i] = mlist[i][:-1]
for i in range (len(mlist)-1, 0, -1):
stringReturn.append(tabspace*2 + "{\"" + mlist[i] + "\", \"" + mlist[0] + "\"},")
stringReturn.append(tabandahalfspace + "{\"" + mlist[i] + "\", \"" + mlist[0] + "\"},")
#print("created: " + stringReturn)
return stringReturn

View File

@ -17,7 +17,8 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
endif()
add_subdirectory(template)
if (CROW_ENABLE_SSL)
add_subdirectory(multi_file)
if ("ssl" IN_LIST CROW_FEATURES)
add_subdirectory(ssl)
endif()
add_subdirectory(img)

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,6 @@
project(test_multi_file)
file (GLOB_RECURSE SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
add_executable(${PROJECT_NAME} ${SRC})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC Crow::Crow)

15
tests/multi_file/main.cpp Normal file
View File

@ -0,0 +1,15 @@
// Test of a project containing more than 1 source file which includes Crow headers.
// The test succeeds if the project is linked successfully.
#include "crow.h"
int main()
{
crow::SimpleApp app;
CROW_ROUTE(app, "/")
([]() {
return "Hello, world!";
});
app.port(18080).run();
}

View File

@ -0,0 +1 @@
#include <crow.h>

Some files were not shown because too many files have changed in this diff Show More