├── .bazelversion ├── .clang-format ├── .coveralls.yml ├── .drone.yml ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build_and_test.yml │ ├── build_bazel.yml │ ├── cifuzz.yml │ ├── codeql.yml │ ├── doxygen-gh-pages.yml │ └── submit_coverage.yml ├── .gitignore ├── .travis.yml ├── BUILD.bazel ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── MODULE.bazel ├── README.md ├── cmake ├── CPM.cmake ├── CrowConfig.cmake.in ├── Findasio.cmake ├── cmake_uninstall.cmake.in └── compiler_options.cmake ├── docs ├── assets │ ├── crowlogo.svg │ ├── crowlogo_dual_color.svg │ ├── crowlogo_inverted.svg │ ├── crowlogo_main_color.svg │ ├── crowlogo_main_light_color.svg │ ├── fast_icon.svg │ ├── fast_light_icon.svg │ ├── favicon.png │ ├── favicon.svg │ ├── header_icon.svg │ ├── header_light_icon.svg │ ├── og_img.png │ ├── pkg_logos │ │ ├── arch.png │ │ ├── conan.png │ │ ├── github.png │ │ ├── homebrew.svg │ │ ├── nix.png │ │ ├── ubuntu.png │ │ └── vcpkg.png │ ├── typesafe_icon.svg │ ├── typesafe_light_icon.svg │ ├── websocket_icon.svg │ └── websocket_light_icon.svg ├── getting_started │ ├── a_simple_webpage.md │ ├── project_templates_and_applications.md │ ├── setup │ │ ├── linux.md │ │ ├── macos.md │ │ └── windows.md │ └── your_first_application.md ├── guides │ ├── app.md │ ├── auth.md │ ├── base64.md │ ├── blueprints.md │ ├── compression.md │ ├── included-middleware.md │ ├── json.md │ ├── logging.md │ ├── middleware.md │ ├── multipart.md │ ├── proxies.md │ ├── query-string.md │ ├── routes.md │ ├── ssl.md │ ├── static.md │ ├── syste.md │ ├── templating.md │ ├── testing.md │ └── websockets.md ├── index.md ├── overrides │ ├── home.html │ ├── main.html │ ├── partials │ │ ├── footer.html │ │ ├── header.html │ │ ├── integrations │ │ │ └── analytics │ │ │ │ └── matomo.html │ │ └── social.html │ └── privacy_policy.html ├── stylesheets │ ├── Lato-Medium.ttf │ ├── colors.css │ ├── extra.css │ └── latofonts.css └── versions.json ├── examples ├── CMakeLists.txt ├── example.cpp ├── example_blueprint.cpp ├── example_catchall.cpp ├── example_chat.cpp ├── example_chat.html ├── example_compression.cpp ├── example_file_upload.cpp ├── example_json_map.cpp ├── example_middleware.cpp ├── example_static_file.cpp ├── example_vs.cpp ├── example_with_all.cpp ├── helloworld.cpp ├── middlewares │ ├── example_cookies.cpp │ ├── example_cors.cpp │ └── example_session.cpp ├── ssl │ └── example_ssl.cpp └── websocket │ ├── example_ws.cpp │ └── templates │ └── ws.html ├── include ├── crow.h └── crow │ ├── TinySHA1.hpp │ ├── app.h │ ├── ci_map.h │ ├── common.h │ ├── compression.h │ ├── exceptions.h │ ├── http_connection.h │ ├── http_parser_merged.h │ ├── http_request.h │ ├── http_response.h │ ├── http_server.h │ ├── json.h │ ├── logging.h │ ├── middleware.h │ ├── middleware_context.h │ ├── middlewares │ ├── cookie_parser.h │ ├── cors.h │ ├── session.h │ └── utf-8.h │ ├── mime_types.h │ ├── multipart.h │ ├── multipart_view.h │ ├── mustache.h │ ├── parser.h │ ├── query_string.h │ ├── returnable.h │ ├── routing.h │ ├── settings.h │ ├── socket_adaptors.h │ ├── task_timer.h │ ├── utility.h │ ├── version.h │ └── websocket.h ├── logo55.png ├── mkdocs.yml ├── scripts ├── PKGBUILD ├── generateDocumentationAndDeploy.sh ├── merge_all.py ├── nginx_mime2cpp.py └── release.sh ├── tests ├── CMakeLists.txt ├── external_definition │ ├── CMakeLists.txt │ └── main.cpp ├── fuzz │ ├── CMakeLists.txt │ ├── b64_fuzzer.cpp │ ├── build.sh │ ├── html_corpus │ │ └── get.seed │ ├── request_fuzzer.cpp │ ├── template_corpus │ │ └── template.seed │ └── template_fuzzer.cpp ├── img │ ├── CMakeLists.txt │ ├── cat.jpg │ └── filewith.badext ├── multi_file │ ├── CMakeLists.txt │ ├── main.cpp │ └── secondary.cpp ├── query_string_tests.cpp ├── ssl │ ├── BUILD.bazel │ ├── CMakeLists.txt │ └── ssltest.cpp ├── template │ ├── CMakeLists.txt │ ├── README.md │ ├── comments.json │ ├── crow_extra_mustache_tests.json │ ├── delimiters.json │ ├── interpolation.json │ ├── inverted.json │ ├── mustachetest.cpp │ ├── partials.json │ ├── sections.json │ ├── test.py │ └── ~lambdas.json └── unittest.cpp └── vcpkg.json /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.x -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Mozilla 3 | AccessModifierOffset: '-4' 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveMacros: 'false' 6 | AlignEscapedNewlines: Left 7 | AlignOperands: 'true' #AlignAfterOperator 8 | AlignTrailingComments: 'true' 9 | AllowShortBlocksOnASingleLine: 'true' 10 | AllowShortCaseLabelsOnASingleLine: 'true' 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: 'true' 13 | AllowShortLambdasOnASingleLine: Empty 14 | AllowShortLoopsOnASingleLine: 'false' 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: 'false' 18 | AlwaysBreakTemplateDeclarations: 'Yes' 19 | BinPackArguments: 'true' 20 | BinPackParameters: 'true' 21 | BreakBeforeBraces: Custom 22 | BraceWrapping: 23 | AfterCaseLabel: 'true' 24 | AfterClass: 'true' 25 | AfterControlStatement: Always 26 | AfterEnum: 'true' 27 | AfterFunction: 'true' 28 | AfterNamespace: 'true' 29 | AfterObjCDeclaration: 'true' 30 | AfterStruct: 'true' 31 | AfterUnion: 'true' 32 | AfterExternBlock: 'true' 33 | BeforeCatch: 'true' 34 | BeforeElse: 'true' 35 | # BeforeLambdaBody: 'false' 36 | # BeforeWhile: 'false' 37 | IndentBraces: 'false' 38 | SplitEmptyFunction: 'false' 39 | SplitEmptyRecord: 'false' 40 | SplitEmptyNamespace: 'false' 41 | BreakBeforeTernaryOperators: 'false' 42 | BreakBeforeBinaryOperators: 'false' 43 | BreakConstructorInitializers: AfterColon 44 | BreakInheritanceList: AfterColon 45 | ColumnLimit: '0' 46 | CompactNamespaces: 'false' 47 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 48 | ContinuationIndentWidth: '2' 49 | Cpp11BracedListStyle: 'true' 50 | FixNamespaceComments: 'true' 51 | IncludeBlocks: Regroup 52 | IncludeCategories: 53 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 54 | Priority: 2 55 | SortPriority: 2 56 | # CaseSensitive: true 57 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 58 | Priority: 3 59 | - Regex: '<[[:alnum:].]+>' 60 | Priority: 4 61 | - Regex: '.*' 62 | Priority: 1 63 | SortPriority: 0 64 | IndentCaseLabels: 'true' 65 | IndentWidth: '4' 66 | IndentWrappedFunctionNames: 'true' 67 | Language: Cpp 68 | MaxEmptyLinesToKeep: '3' 69 | NamespaceIndentation: All 70 | PointerAlignment: Left 71 | ReflowComments: 'false' 72 | SortIncludes: 'false' 73 | SortUsingDeclarations: 'false' 74 | SpaceAfterCStyleCast: 'false' 75 | SpaceAfterLogicalNot: 'false' 76 | SpaceAfterTemplateKeyword: 'false' 77 | SpaceBeforeAssignmentOperators: 'true' 78 | SpaceBeforeCpp11BracedList: 'false' 79 | SpaceBeforeCtorInitializerColon: 'false' 80 | SpaceBeforeInheritanceColon: 'true' 81 | SpaceBeforeParens: ControlStatements 82 | SpaceBeforeRangeBasedForLoopColon: 'true' 83 | SpaceInEmptyParentheses: 'false' 84 | SpacesBeforeTrailingComments: '1' 85 | SpacesInAngles: 'false' 86 | SpacesInCStyleCastParentheses: 'false' 87 | SpacesInContainerLiterals: 'false' 88 | SpacesInParentheses: 'false' 89 | SpacesInSquareBrackets: 'false' 90 | #SpacesInLineCommentPrefix: 91 | # Minimum: 1 92 | # Maximum: -1 93 | Standard: Cpp11 94 | TabWidth: '4' 95 | UseTab: Never 96 | 97 | ... 98 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: gh-actions 2 | gcov_options: \-lp 3 | include: 4 | - include 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: crow 4 | github: CrowCpp 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/build_bazel.yml: -------------------------------------------------------------------------------- 1 | name: bazel - Build and test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | COVERALLS_PULL_REQUEST: ${{ github.event.number }} 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: true 21 | matrix: 22 | os: [ ubuntu-latest, 23 | ubuntu-22.04, 24 | ubuntu-24.04 25 | ] 26 | # ubuntu-18.04 & mac-os do not work due to compile error on asio 27 | # windows-2019 not included to spare free minutes 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Prepare dependencies 31 | run: | 32 | if [ "$RUNNER_OS" == "Linux" ]; then 33 | curl --tlsv1.2 --proto =https -Lo bazel https://github.com/bazelbuild/bazelisk/releases/download/v1.25.0/bazelisk-linux-amd64 34 | chmod +x bazel 35 | 36 | sudo apt-get update && \ 37 | sudo apt-get install -yq \ 38 | g++ clang 39 | else 40 | echo "$RUNNER_OS not supported" 41 | exit 1 42 | fi 43 | shell: bash 44 | 45 | - name: Build bazel 46 | run: | 47 | ./bazel build //... 48 | ./bazel test //... 49 | 50 | 51 | -------------------------------------------------------------------------------- /.github/workflows/cifuzz.yml: -------------------------------------------------------------------------------- 1 | name: CIFuzz 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | permissions: {} 8 | jobs: 9 | Fuzzing: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | security-events: write 13 | steps: 14 | - name: Build Fuzzers 15 | id: build 16 | uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master 17 | with: 18 | oss-fuzz-project-name: 'crow' 19 | language: c++ 20 | - name: Run Fuzzers 21 | uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master 22 | with: 23 | oss-fuzz-project-name: 'crow' 24 | language: c++ 25 | fuzz-seconds: 800 26 | output-sarif: true 27 | - name: Upload Crash 28 | uses: actions/upload-artifact@v4 29 | if: failure() && steps.build.outcome == 'success' 30 | with: 31 | name: artifacts 32 | path: ./out/artifacts 33 | - name: Upload Sarif 34 | if: always() && steps.build.outcome == 'success' 35 | uses: github/codeql-action/upload-sarif@v3 36 | with: 37 | # Path to SARIF file relative to the root of the repository 38 | sarif_file: cifuzz-sarif/results.sarif 39 | checkout_path: cifuzz-sarif 40 | -------------------------------------------------------------------------------- /.github/workflows/doxygen-gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Doxygen results to Github Pages 2 | 3 | #on: push 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | permissions: 10 | contents: write 11 | jobs: 12 | build-and-deploy: 13 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 🛎️ 17 | uses: actions/checkout@v4 18 | - name: Prepare dependencies 19 | run: sudo apt-get update && sudo apt-get -yq install libasio-dev doxygen mkdocs graphviz zlib1g-dev gcc clang make cmake python3 python3-pip git openssl libssl-dev 20 | - name: prepate pip dependencies 21 | run: pip3 install mkdocs-material mkdocs-redirects pyyaml mkdocs-meta-descriptions-plugin mike --no-input 22 | - name: configure 23 | run: cmake -B build -DCROW_AMALGAMATE=ON 24 | - name: clean generated docs dir 25 | run: rm -rf site docs/reference 26 | - name: clone doxygen theme 27 | run: git clone https://github.com/CrowCpp/darxygen.git 28 | - name: run doxygen 29 | run: doxygen 30 | - name: run mkdocs 31 | run: mkdocs build 32 | - name: Setup doc deploy 33 | run: | 34 | git config --global user.name Docs deploy 35 | git config --global user.email docs@dummy.bot.com 36 | - name: run mike (Deploy 🚀) 37 | run: | 38 | git fetch origin gh-pages --depth=1 39 | mike deploy master --push 40 | -------------------------------------------------------------------------------- /.github/workflows/submit_coverage.yml: -------------------------------------------------------------------------------- 1 | name: Submit coverage 2 | 3 | # read-write repo token 4 | # access to secrets 5 | on: 6 | workflow_run: 7 | workflows: [Build and test] 8 | types: [completed] 9 | 10 | env: 11 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 12 | 13 | jobs: 14 | upload: 15 | runs-on: ubuntu-latest 16 | if: github.event.workflow_run.conclusion == 'success' 17 | steps: 18 | - name: Download artifact 19 | uses: dawidd6/action-download-artifact@v9 20 | with: 21 | workflow: ${{ github.event.workflow_run.workflow_id }} 22 | workflow_conclusion: success 23 | name: coveralls.json 24 | 25 | 26 | - name: 'Prepare dependencies' 27 | run: | 28 | sudo apt-get update && \ 29 | sudo apt-get install -yq \ 30 | jq \ 31 | curl 32 | shell: bash 33 | 34 | - name: 'Generate full report' 35 | run: | 36 | cat coveralls.json | jq --arg repo_token $COVERALLS_REPO_TOKEN '. += {repo_token: $repo_token}' > coveralls_full.json 37 | shell: bash 38 | 39 | - name: 'Upload final report' 40 | run: | 41 | curl --request POST \ 42 | --url https://coveralls.io/api/v1/jobs \ 43 | --header 'Content-Type: multipart/form-data' \ 44 | --form json_file=@./coveralls_full.json 45 | shell: bash 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | darxygen/** 2 | docs/reference/** 3 | # Compiled Object files 4 | *.slo 5 | *.lo 6 | *.o 7 | *.obj 8 | 9 | # Compiled Dynamic libraries 10 | *.so 11 | *.dylib 12 | *.dll 13 | 14 | # Compiled Static libraries 15 | *.lai 16 | *.la 17 | *.a 18 | *.lib 19 | 20 | # Executables 21 | *.exe 22 | *.out 23 | *.app 24 | 25 | example 26 | unittest 27 | Testing/* 28 | include/crow/tags 29 | 30 | *.swp 31 | *.gcov 32 | 33 | *.gcda 34 | *.gcno 35 | 36 | build 37 | 38 | .directory 39 | crow_all.h 40 | 41 | # conan.io 42 | build*/ 43 | 44 | #Doxygen 45 | html/ 46 | 47 | #QT-Creator 48 | *.config 49 | *.creator* 50 | *.cflags 51 | *.cxxflags 52 | *.files 53 | *.includes 54 | 55 | #VS-Code 56 | .vscode 57 | .idea 58 | cmake-build-debug 59 | .cache 60 | site 61 | venv 62 | 63 | #Bazel 64 | bazel-* 65 | MODULE.bazel.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | cache: ccache 4 | 5 | dist: focal 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | compiler: 12 | - gcc 13 | - clang 14 | 15 | arch: 16 | - amd64 17 | - arm64 18 | 19 | env: 20 | global: 21 | - secure: "CQRYHWlg/WDu5DBUeDwGo+rPeOofN08DhiLUNlLtZjMWaRyP0Cop1qVaFs8ESOkYiWek2MdpvjZud+7hL+yx2ogvNx4SfHpUMCDKYgcX+YQ9MmYwabvoKq8N6KVXE3lbPp549TonHdDuNCWNKRniNjYtrij5J+IiIiT8/6Txo2p9RWk6YSUTdXJ9YrfuWMtRuF5uo9SHGyujv8pOJKedrwWoSBbHT44jnwfHMVe/C8jgjwlrJ9N3iXOtsG6sj+UTS8vOpL+jpBONEbBfHgSFU57I7IFNdPQbSObpVwG9geOAHT7IQQyQ9hp2AJoFxxVURB5SzqztDDpQ0XIF76vuH9tA/fF2pwDsLRmcLR8JU1TCmQgvnlYD0+Or9S1Dq0tQME5AP+21Hk2zVcGdbgQP7XWix758F0vpOXa4PXw8TmAjP2jKyAMHlzR3icr3+OmKSK3uXMMt2HSMOJQ+JvFxr//DM493i/VGyeY25/zu3A9RstiE+1d82Fi9xKOmMf4smvSkjOgT0b727jqNbNe6CvEKQUmqHabzYRQzUVz6WPVDHBxZP7AiKmZIVQXYnDsVXywStkSoxxY5En6XKpq0GR3bIVtUMORgZPoZi7Jni+/4EckcYH8g9mpsQf9tPRcOZ2WIvt5gqp2MZuwBLBRcbxihuECfBscqdeA0oDU5AZw=" 22 | - GH_REPO_NAME: crow 23 | - DOXYFILE: $TRAVIS_BUILD_DIR/Doxyfile 24 | - GH_REPO_REF: github.com/crowcpp/crow.git 25 | - THEME_REPO_REF: github.com/crowcpp/darxygen.git 26 | 27 | 28 | addons: 29 | apt: 30 | packages: 31 | - libasio-dev 32 | - doxygen 33 | - mkdocs 34 | - graphviz 35 | - zlib1g-dev 36 | - libssl-dev 37 | 38 | before_install: 39 | - if [ "$TRAVIS_COMPILER" == "gcc" -a "$TRAVIS_CPU_ARCH" == "amd64" ]; then export PUSH_COVERAGE=ON; fi 40 | - if [ "$TRAVIS_BRANCH" == "master" -a "$TRAVIS_PULL_REQUEST" == "false" -a "$PUSH_COVERAGE" == "ON" ]; then export TRAVIS_BUILD_DOCS=ON; pip install mkdocs-material; fi 41 | 42 | install: 43 | - if [ "$PUSH_COVERAGE" == "ON" ]; then pip install --user cpp-coveralls; fi 44 | 45 | before_script: 46 | - mkdir build 47 | - cd build 48 | - cmake --version 49 | - cmake .. -DCROW_FEATURES="ssl;compression" -DCROW_AMALGAMATE=ON 50 | 51 | script: make -j4 && ctest -V -j4 52 | 53 | after_success: 54 | - cd .. 55 | - if [ "$PUSH_COVERAGE" == "ON" ]; then coveralls -i include --exclude-pattern .*/http_parser_merged.h --exclude-pattern .*/TinySHA1.hpp --gcov-options '\-lp'; fi 56 | - chmod +x scripts/generateDocumentationAndDeploy.sh 57 | - if [ "$TRAVIS_BUILD_DOCS" == "ON" ]; then ./scripts/generateDocumentationAndDeploy.sh; fi 58 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") 3 | 4 | bool_flag( 5 | name = "ssl_enabled_flag", 6 | build_setting_default = True, 7 | visibility = ["//visibility:public"], 8 | ) 9 | 10 | config_setting( 11 | name = "ssl_enabled", 12 | flag_values = { 13 | "//:ssl_enabled_flag": "True", 14 | }, 15 | ) 16 | 17 | bool_flag( 18 | name = "compression_enabled_flag", 19 | build_setting_default = True, 20 | visibility = ["//visibility:public"], 21 | ) 22 | 23 | config_setting( 24 | name = "compression_enabled", 25 | flag_values = { 26 | "//:compression_enabled_flag": "True", 27 | }, 28 | ) 29 | 30 | cc_library( 31 | name = "crow", 32 | hdrs = glob(["include/**/*"]), 33 | includes = ["include"], 34 | copts = select({ 35 | ":ssl_enabled": ["-DASIO_STANDALONE", "-DCROW_ENABLE_SSL"], 36 | "//conditions:default": [], 37 | }) + select({ 38 | ":compression_enabled": ["-DCROW_ENABLE_COMPRESSION"], 39 | "//conditions:default": [], 40 | }), 41 | deps = select({ 42 | ":ssl_enabled": ["@asio//:asio"], 43 | "//conditions:default": [], 44 | }) + select({ 45 | ":compression_enabled": ["@zlib//:zlib"], 46 | "//conditions:default": [], 47 | }), 48 | ) 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2014-2017, ipkn 4 | 2020-2022, CrowCpp 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the author nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | 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. 33 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module(name = "crowcpp", version = "1.2.1.2") 2 | 3 | bazel_dep(name = "bazel_skylib", version = "1.7.1") 4 | bazel_dep(name = "rules_cc", version = "0.1.1") 5 | bazel_dep(name = "zlib", version = "1.3.1.bcr.5") 6 | bazel_dep(name = "asio", version = "1.32.0") 7 | 8 | # testing 9 | bazel_dep(name = "catch2", version = "3.8.0") -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | set(CPM_DOWNLOAD_VERSION 0.40.2) 6 | set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") 7 | 8 | if(CPM_SOURCE_CACHE) 9 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 10 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 11 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 12 | else() 13 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 14 | endif() 15 | 16 | # Expand relative path. This is important if the provided path contains a tilde (~) 17 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 18 | 19 | file(DOWNLOAD 20 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 21 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 22 | ) 23 | 24 | include(${CPM_DOWNLOAD_LOCATION}) 25 | -------------------------------------------------------------------------------- /cmake/CrowConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | get_filename_component(CROW_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 6 | 7 | list(APPEND CMAKE_MODULE_PATH ${CROW_CMAKE_DIR}) 8 | find_dependency(asio) 9 | list(REMOVE_AT CMAKE_MODULE_PATH -1) 10 | 11 | set(CROW_INSTALLED_FEATURES "@CROW_FEATURES@") 12 | 13 | if(NOT DEFINED CROW_FEATURES) 14 | set(CROW_FEATURES ${CROW_INSTALLED_FEATURES}) 15 | endif() 16 | 17 | if(CROW_ENABLE_COMPRESSION) 18 | find_dependency(ZLIB) 19 | endif() 20 | 21 | if(CROW_ENABLE_SSL) 22 | find_dependency(OpenSSL) 23 | endif() 24 | 25 | include("${CMAKE_CURRENT_LIST_DIR}/CrowTargets.cmake") 26 | check_required_components("@PROJECT_NAME@") 27 | 28 | get_target_property(_CROW_ILL Crow::Crow INTERFACE_LINK_LIBRARIES) 29 | get_target_property(_CROW_ICD Crow::Crow INTERFACE_COMPILE_DEFINITIONS) 30 | 31 | if(_CROW_ILL STREQUAL "_CROW_ILL-NOTFOUND") 32 | set(_CROW_ILL "") 33 | endif() 34 | if(_CROW_ICD STREQUAL "_CROW_ICD-NOTFOUND") 35 | set(_CROW_ICD "") 36 | endif() 37 | 38 | list(REMOVE_ITEM _CROW_ILL "ZLIB::ZLIB" "OpenSSL::SSL") 39 | list(REMOVE_ITEM _CROW_ICD "CROW_ENABLE_SSL" "CROW_ENABLE_COMPRESSION") 40 | 41 | if(CROW_ENABLE_COMPRESSION) 42 | list(APPEND _CROW_ILL "ZLIB::ZLIB") 43 | list(APPEND _CROW_ICD "CROW_ENABLE_COMPRESSION") 44 | endif() 45 | 46 | if(CROW_ENABLE_SSL) 47 | list(APPEND _CROW_ILL "OpenSSL::SSL") 48 | list(APPEND _CROW_ICD "CROW_ENABLE_SSL") 49 | endif() 50 | 51 | if( NOT (_CROW_ICD STREQUAL "" ) ) 52 | set_target_properties(Crow::Crow PROPERTIES 53 | INTERFACE_COMPILE_DEFINITIONS "${_CROW_ICD}" 54 | ) 55 | endif() 56 | if( NOT (_CROW_ILL STREQUAL "" ) ) 57 | set_target_properties(Crow::Crow PROPERTIES 58 | INTERFACE_LINK_LIBRARIES "${_CROW_ILL}" 59 | ) 60 | endif() 61 | -------------------------------------------------------------------------------- /cmake/Findasio.cmake: -------------------------------------------------------------------------------- 1 | #Findasio.cmake 2 | # 3 | # Finds the asio library 4 | # 5 | # from https://think-async.com/Asio/ 6 | # 7 | # This will define the following variables 8 | # 9 | # ASIO_FOUND 10 | # ASIO_INCLUDE_DIR 11 | # 12 | # and the following imported targets 13 | # 14 | # asio::asio 15 | # 16 | 17 | find_package(Threads QUIET) 18 | if (Threads_FOUND) 19 | find_path(ASIO_INCLUDE_DIR asio.hpp) 20 | 21 | mark_as_advanced(ASIO_FOUND ASIO_INCLUDE_DIR) 22 | 23 | include(FindPackageHandleStandardArgs) 24 | find_package_handle_standard_args(asio 25 | FOUND_VAR ASIO_FOUND 26 | REQUIRED_VARS ASIO_INCLUDE_DIR 27 | ) 28 | 29 | if(ASIO_FOUND AND NOT TARGET asio::asio) 30 | add_library(asio::asio INTERFACE IMPORTED) 31 | target_include_directories(asio::asio 32 | INTERFACE 33 | ${ASIO_INCLUDE_DIR} 34 | ) 35 | target_compile_definitions(asio::asio 36 | INTERFACE 37 | "ASIO_STANDALONE" 38 | ) 39 | target_link_libraries(asio::asio 40 | INTERFACE 41 | Threads::Threads 42 | ) 43 | endif() 44 | else() 45 | if(asio_FIND_REQUIRED) 46 | message(FATAL_ERROR "asio requires Threads, which couldn't be found.") 47 | elseif(asio_FIND_QUIETLY) 48 | message(STATUS "asio requires Threads, which couldn't be found.") 49 | endif() 50 | endif() 51 | -------------------------------------------------------------------------------- /cmake/cmake_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") 3 | endif() 4 | 5 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif() 18 | else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif() 21 | endforeach() 22 | 23 | -------------------------------------------------------------------------------- /cmake/compiler_options.cmake: -------------------------------------------------------------------------------- 1 | # Compiler options with hardening flags 2 | 3 | function(add_warnings_optimizations target_name) 4 | if(MSVC) 5 | target_compile_options(${target_name} 6 | PRIVATE 7 | /W4 8 | /permissive- 9 | $<$:/O2 /Ob2> 10 | $<$:/O1 /Ob1> 11 | $<$:/Zi /O2 /Ob1> 12 | $<$:/Zi /Ob0 /Od /RTC1> 13 | ) 14 | elseif(NOT CMAKE_COMPILER_IS_GNU AND 15 | "${CMAKE_SYSTEM_NAME}" STREQUAL "Android") 16 | # clang on Android, no prof/gprof 17 | target_compile_options(${target_name} 18 | PRIVATE 19 | -Wall 20 | -Wextra 21 | -Wpedantic 22 | -Wsuggest-override 23 | -Wshadow 24 | $<$:-O2> 25 | $<$:-O0 -g> 26 | ) 27 | elseif(NOT CMAKE_COMPILER_IS_GNU) 28 | # clang, no prof 29 | target_compile_options(${target_name} 30 | PRIVATE 31 | -Wall 32 | -Wextra 33 | -Wpedantic 34 | -Wsuggest-override 35 | -Wshadow 36 | $<$:-O2> 37 | $<$:-O0 -g -pg> 38 | ) 39 | else() 40 | target_compile_options(${target_name} 41 | PRIVATE 42 | -Wall 43 | -Wextra 44 | -Wpedantic 45 | -Wsuggest-override 46 | -Wshadow 47 | $<$:-O2> 48 | $<$:-O0 -g -p -pg> 49 | ) 50 | endif() 51 | endfunction() 52 | -------------------------------------------------------------------------------- /docs/assets/fast_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 49 | 55 | 62 | 68 | 74 | 80 | 86 | 92 | 99 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/assets/fast_light_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 49 | 55 | 62 | 68 | 74 | 80 | 86 | 92 | 99 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/favicon.png -------------------------------------------------------------------------------- /docs/assets/header_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 50 | 51 | 56 | 61 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/assets/header_light_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 44 | 50 | 51 | 56 | 61 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/assets/og_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/og_img.png -------------------------------------------------------------------------------- /docs/assets/pkg_logos/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/pkg_logos/arch.png -------------------------------------------------------------------------------- /docs/assets/pkg_logos/conan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/pkg_logos/conan.png -------------------------------------------------------------------------------- /docs/assets/pkg_logos/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/pkg_logos/github.png -------------------------------------------------------------------------------- /docs/assets/pkg_logos/homebrew.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/pkg_logos/nix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/pkg_logos/nix.png -------------------------------------------------------------------------------- /docs/assets/pkg_logos/ubuntu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/pkg_logos/ubuntu.png -------------------------------------------------------------------------------- /docs/assets/pkg_logos/vcpkg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/assets/pkg_logos/vcpkg.png -------------------------------------------------------------------------------- /docs/assets/typesafe_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 52 | 53 | 58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/assets/typesafe_light_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 52 | 53 | 58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/assets/websocket_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/assets/websocket_light_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 38 | 43 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/getting_started/a_simple_webpage.md: -------------------------------------------------------------------------------- 1 | Hello World is a good start, but what if you want something a bit more fancy.. Something like an HTML document saying "Hello World". If that's what you want, follow along: 2 | 3 | ## Basic Webpage 4 | Let's start our webpage with.. well.. a webpage. But before we create a webpage we need to place it somewhere Crow recognizes, for now this directory is going to be called `templates`, but we can [change it later](../guides/templating.md#page). 5 | 6 | Once our `templates` folder is created, we can create our HTML document inside it, let's call it `fancypage.html`. 7 | 8 | After that we can just place something simple inside it like: 9 | ``` html title="templates/fancypage.html" 10 | 11 | 12 | 13 |

Hello World!

14 | 15 | 16 | ``` 17 |
18 | Now that we have our HTML page ready, let's take our Hello World example from earlier: 19 | ``` cpp linenums="1" 20 | #include "crow.h" 21 | //#include "crow_all.h" 22 | 23 | int main() 24 | { 25 | crow::SimpleApp app; //define your crow application 26 | 27 | //define your endpoint at the root directory 28 | CROW_ROUTE(app, "/")([](){ 29 | return "Hello world"; 30 | }); 31 | 32 | //set the port, set the app to run on multiple threads, and run the app 33 | app.port(18080).multithreaded().run(); 34 | } 35 | ``` 36 |
37 | 38 | And now let's modify it so that it returns our cool page: 39 | ``` cpp title="/main.cpp" linenums="1" hl_lines="10 11" 40 | #include "crow.h" 41 | //#include "crow_all.h" 42 | 43 | int main() 44 | { 45 | crow::SimpleApp app; 46 | 47 | //define your endpoint at the root directory 48 | CROW_ROUTE(app, "/")([](){ 49 | auto page = crow::mustache::load_text("fancypage.html"); 50 | return page; 51 | }); 52 | 53 | app.port(18080).multithreaded().run(); 54 | } 55 | ``` 56 | 57 | Your project should look something something like: 58 | ``` 59 | ./ 60 | |-templates/ 61 | | |-fancypage.html 62 | | 63 | |-main.cpp 64 | |-crow_all.h 65 | ``` 66 | or 67 | ``` 68 | ./ 69 | |-templates/ 70 | | |-fancypage.html 71 | | 72 | |-crow/ 73 | | |-include/... 74 | | |-crow.h 75 | |-main.cpp 76 | ``` 77 | 78 | 79 | Once the code is done compiling, if we call `http://localhost:18080/` we get our Hello World in an HTML document rather than just plain text. 80 | 81 | !!! note 82 | 83 | Compilation instructions are available for 84 | [Linux](setup/linux.md#compiling-your-project), 85 | [MacOS](setup/macos.md#compiling-using-a-compiler-directly), 86 | and 87 | [Windows](setup/windows.md#getting-and-compiling-crow) 88 | 89 | 90 | ## Template Webpage with a variable 91 | But we can make things even more exciting, we can greet a user by their name instead!! 92 | 93 | Let's start with our webpage, and modify it with a little bit of [mustache](../guides/templating.md) syntax: 94 | ``` html title="templates/fancypage.html" hl_lines="4" 95 | 96 | 97 | 98 |

Hello {{person}}!

99 | 100 | 101 | ``` 102 | 103 | 1. `{{}}` in mustache define a simple variable 104 | 105 |
106 | Now let's modify our C++ code to use the variable we just added to our webpage (or template): 107 | ``` cpp title="/main.cpp" linenums="1" hl_lines="9-12" 108 | #include "crow.h" 109 | //#include "crow_all.h" 110 | 111 | int main() 112 | { 113 | crow::SimpleApp app; 114 | 115 | //define your endpoint at the root directory 116 | CROW_ROUTE(app, "/")([](std::string name){ // (1) 117 | auto page = crow::mustache::load("fancypage.html"); // (2) 118 | crow::mustache::context ctx ({{"person", name}}); // (3) 119 | return page.render(ctx); //(4) 120 | }); 121 | 122 | app.port(18080).multithreaded().run(); 123 | } 124 | ``` 125 | 126 | 1. We are adding a `string` variable to the URL and a counterpart (`std::string name`) to our route - this can be anything the user wants. 127 | 2. We are using `load()` instead of `load_text()` since we have an actual variable now. 128 | 3. We are creating a new [context](../guides/templating.md#context) containing the `person` variable from our template and the `name` we got from the URL. 129 | 4. We are using `render(ctx)` to apply our context to the template. 130 | 131 | Now (after compiling the code and running the executable a second time) calling `http://localhost:18080/Bob` should return a webpage containing "Hello Bob!". **We did it!** 132 | 133 | For more details on templates and HTML pages in Crow please go [here](../guides/templating.md) 134 | -------------------------------------------------------------------------------- /docs/getting_started/project_templates_and_applications.md: -------------------------------------------------------------------------------- 1 | Here we collect samples and applications. 2 | 3 | The repositories mentioned here are 3rd party owned and are not supported or managed by the CrowCpp Project. 4 | 5 | # Template projects 6 | 7 | + [https://github.com/gittiver/crow_template](https://github.com/gittiver/crow_template) 8 | 9 | - Features: 10 | - Github template repository 11 | - Uses CPM for including crow into project 12 | - Uses catch2 for testing 13 | 14 | + [https://github.com/seobryn/corax-template](https://github.com/seobryn/corax-template) 15 | 16 | - Features: 17 | - `Scripts` to Make, Compile and Clean the build 18 | - `Libs` Folder to store all the external Libs, Crow is set by default. 19 | - Format rules using `clang-format` 20 | 21 | + [https://github.com/landiluigi746/cpp-backend-template](https://github.com/landiluigi746/cpp-backend-template) 22 | 23 | - Features: 24 | - Github template repository 25 | - Uses CMake as build system 26 | - Uses vcpkg as package manager 27 | - Format rules using `clang-format` 28 | - Docker support out of the box 29 | -------------------------------------------------------------------------------- /docs/getting_started/setup/linux.md: -------------------------------------------------------------------------------- 1 | Here's how you can install Crow on your favorite GNU/Linux distro. 2 | ## Getting Crow 3 | 4 | ### Requirements 5 | - C++ compiler with at least C++17 support. 6 | - Asio development headers (1.28 or later, up to 1.30). 7 | - **(optional)** ZLib for HTTP Compression. 8 | - **(optional)** OpenSSL for HTTPS support. 9 | - **(optional)** CMake for building tests, examples, and/or installing Crow. 10 | - **(optional)** Python3 to build tests and/or examples. 11 | !!! note 12 | 13 | Crow's CI uses `g++-9.4` and `clang-10.0` running on AMD64 (x86_64) and ARM64v8 architectures. 14 | 15 | 16 |

17 | 18 | ### Using a package Manager 19 | You can install Crow on GNU/Linux as a pre-made package 20 | === "Debian/Ubuntu" 21 | 22 | Simply download Crow's `.deb` file from the [release section](https://github.com/CrowCpp/Crow/releases/latest) and Install it. 23 | 24 | === "Arch" 25 | 26 | Crow is available for Arch based distros through the AUR package `crow`. 27 | 28 | 29 |

30 | ### Release package 31 | Crow provides an archive containing the framework and CMake files, just copy the `include` folder to `/usr/local/include` and `lib` folder to `/usr/local/lib`.

32 | You can also download the `crow_all.h` file and simply include that into your project. 33 |

34 | ### Installing from source 35 | #### Using CMake 36 | 1. Download Crow's source code (Either through Github's UI or by using
`git clone https://github.com/CrowCpp/Crow.git`). 37 | 2. Run `mkdir build` inside of crow's source directory. 38 | 3. Navigate to the new "build" directory and run the following:
39 | `cmake .. -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF` 40 | 4. Run `make install`. 41 | 42 | !!! note 43 | 44 | You can ignore `-DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF` if you want to build the Examples and Unit Tests. 45 | 46 | !!! note 47 | 48 | While building you can set: 49 | the `CROW_ENABLE_SSL` variable to enable the support for https 50 | the `CROW_ENABLE_COMPRESSION` variable to enable the support for http compression 51 | 52 | !!! note 53 | 54 | By default, Crow sets `CROW_ENABLE_SSL=true` and `CROW_ENABLE_COMPRESSION=true` for CMake builds. You must call `find_package` and manually link with these libraries in your build if you keep these defaults. 55 | 56 | 57 | ``` 58 | find_package(OpenSSL REQUIRED) 59 | find_package(ZLIB REQUIRED) 60 | 61 | target_link_libraries(main 62 | PRIVATE 63 | OpenSSL::SSL 64 | OpenSSL::Crypto 65 | ZLIB::ZLIB 66 | Crow::Crow 67 | ) 68 | ``` 69 | 70 | !!! note 71 | 72 | You can uninstall Crow at a later time using `make uninstall`. 73 | 74 |
75 | #### Manually 76 | Crow can be installed manually on your Linux computer. 77 | ##### Multiple header files 78 | === "Project Only" 79 | 80 | Copy Crow's `include` directory to your project's `include` directory. 81 | 82 | === "System wide" 83 | 84 | Copy Crow's `include` directory to the `/usr/local/include` directory. 85 | 86 | ##### Single header (crow_all.h) 87 | !!! warning 88 | 89 | `crow_all.h` is recommended only for small, possibly single source file projects, and ideally should not be installed on your system. 90 | 91 | 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. 92 | !!! note 93 | 94 | 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). 95 | 96 | ## Compiling your project 97 | ### Using CMake 98 | In order to get your CMake project to work with Crow, all you need are the following lines in your CMakeLists.txt: 99 | ``` 100 | find_package(Crow) 101 | target_link_libraries(your_project PUBLIC Crow::Crow) 102 | ``` 103 | From there CMake should handle compiling and linking your project. 104 | !!! note 105 | 106 | For optional features like HTTP Compression or HTTPS you can set 107 | 108 | the `CROW_ENABLE_SSL` variable to enable the support for https 109 | the `CROW_ENABLE_COMPRESSION` variable to enable the support for http compression 110 | 111 | ### Directly using a compiler 112 | All you need to do is run the following command: 113 | ``` 114 | g++ main.cpp -lpthread 115 | ``` 116 | 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++. 117 | -------------------------------------------------------------------------------- /docs/getting_started/setup/macos.md: -------------------------------------------------------------------------------- 1 | Here's how you can install Crow on your Mac. 2 | ## Getting Crow 3 | 4 | ### From Homebrew 5 | 1. Download and install [Homebrew](https://brew.sh). 6 | 2. Run `brew install crow` in your terminal. 7 | 8 | ### From a [release](https://github.com/CrowCpp/Crow/releases) 9 | #### Archive 10 | Crow provides an archive containing the framework and CMake files, You will only need the `include` folder inside that archive. 11 | #### Single header file 12 | You can also download the `crow_all.h` file which replaces the `include` folder. 13 | 14 | ### From Source 15 | To get Crow from source, you only need to download the repository (as a `.zip` or through `git clone https://github.com/CrowCpp/Crow.git`). 16 | #### include folder 17 | Once you've downloaded Crow's source code, you only need to take the `include` folder. 18 | #### Single header file 19 | You can generate your own single header file by navigating to the `scripts` folder with your terminal and running the following command: 20 | ``` 21 | python3 merge_all.py ../include crow_all.h 22 | ``` 23 | This will generate a `crow_all.h` file which you can use in the following steps 24 | !!! warning 25 | 26 | `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. 27 | 28 | 29 | ## Setting up your Crow project 30 | !!! note 31 | 32 | You can skip steps 1 and 2 if you've installed Crow via Homebrew 33 | 34 | ### Using XCode 35 | 1. Download and install [Homebrew](https://brew.sh). 36 | 2. Run `brew install asio` in your terminal. 37 | 3. Create a new XCode project (macOS -> Command Line Tool). 38 | 4. Change the following project settings: 39 | 40 | === "Multiple Headers" 41 | 42 | 1. Add header search paths for crow's include folder and asio's folder (`/usr/local/include`, `/usr/local/Cellar/asio/include`, and where you placed Crow's `include` folder) 43 | 2. Add linker flags (`-lpthread`) 44 | 45 | === "Single Header" 46 | 47 | 1. Place `crow_all.h` inside your project folder and add it to the project in XCode (you need to use File -> Add files to "project_name") 48 | 2. Add header search paths for asio's folder: 49 | 1. `/usr/local/include`, and 50 | 2. **Silicon**: `/opt/homebrew/Cellar/asio//include` 51 | 3. **Intel**: `/usr/local/Cellar/asio//include` 52 | 3. Add linker flags (`-lpthread` for g++, `-pthread` for clang++) 53 | 54 | 5. Write your Crow application in `main.cpp` (something like the Hello World example will work). 55 | 6. Press `▶` to compile and run your Crow application. 56 | 57 | 58 | ## Building Crow's tests/examples 59 | !!! note 60 | 61 | This tutorial can be used for Crow projects built with CMake as well 62 | 63 | !!! note 64 | 65 | You can skip steps 1 and 2 if you've installed Crow via Homebrew 66 | 67 | 1. Download and install [Homebrew](https://brew.sh). 68 | 2. Run `brew install cmake asio` in your terminal. 69 | 3. Get Crow's source code (the entire source code). 70 | 3. Run the following Commands: 71 | 1. `mkdir build` 72 | 2. `cd build` 73 | 3. `cmake ..` 74 | 4. `make -j12` 75 | !!! note 76 | 77 | You can add options like `-DCROW_ENABLE_COMPRESSION=ON` 78 | or `-DCROW_ENABLE_SSL=ON` 79 | or `-DCROW_AMALGAMATE` 80 | to `cmake ..` to build optional tests/examples for HTTP Compression or HTTPS. 81 | 82 | ## Compiling using a compiler directly 83 | All you need to do is run the following command: 84 | ``` 85 | g++ main.cpp -lpthread 86 | ``` 87 | !!! note 88 | 89 | You'll need to install GCC via `brew install gcc`. the Clang compiler should be part of XCode or XCode command line tools. 90 | 91 | 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++. 92 | 93 | If GCC throws errors and your program does not compile, you may be using C++03 instead of ≥C++17. Use the flag `-std=c++17`. 94 | -------------------------------------------------------------------------------- /docs/getting_started/setup/windows.md: -------------------------------------------------------------------------------- 1 | Here's how you can install Crow on your Windows machine. 2 | ## Getting and Compiling Crow 3 | ### Using A package manager 4 | #### VCPKG 5 | Crow can be simply installed through VCPKG using the command `vcpkg install crow` 6 | 7 | ### Manually (source or release) 8 | #### Microsoft Visual Studio and VCPKG 9 | 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. 10 | 11 | 1. Generate `crow_all.h` by navigating to the `scripts` folder and running `python3 merge_all.py ..\include crow_all.h`. 12 | 2. `git clone https://github.com/microsoft/vcpkg.git` 13 | 3. `.\vcpkg\bootstrap-vcpkg.bat` 14 | 4. `.\vcpkg\vcpkg integrate install` 15 | 5. Create empty Visual Studio project. 16 | 6. In solution explorer, right click the name of your project then click `Open Folder in File Explorer`. 17 | 7. Copy `crow_all.h`, `example_with_all.cpp`, `vcpkg.json` to opened folder. 18 | 8. Add `crow_all.h` to `Header Files` and `example_with_all.cpp` to `Source Files`. 19 | 9. In solution explorer, right click the name of your project then click `Properties`. 20 | 10. Under `vcpkg`, set `Use Vcpkg Manifest` to `Yes` and `Additional Options` to `--feature-flags="versions"`. 21 | 11. Set `Debug/Release` and `x64/x86`. 22 | 12. Run. 23 | -------------------------------------------------------------------------------- /docs/getting_started/your_first_application.md: -------------------------------------------------------------------------------- 1 | This page shows how you can get started with a simple hello world application. 2 | 3 | ## 1. Include 4 | 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. 5 | 6 | !!! note 7 | 8 | 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. 9 | 10 | ## 2. App declaration 11 | Next Create a `main()` and declare a `#!cpp crow::SimpleApp` inside, your code should look like this 12 | ``` cpp 13 | int main() 14 | { 15 | crow::SimpleApp app; 16 | } 17 | ``` 18 | The App (or SimpleApp) class organizes all the different parts of Crow and provides the developer (you) a simple interface to interact with these parts. 19 | For more information, please go [here](../guides/app.md). 20 | 21 | ## 3. Adding routes 22 | Once you have your app, the next step is to add routes (or endpoints). You can do so with the `CROW_ROUTE` macro. 23 | ``` cpp 24 | CROW_ROUTE(app, "/")([](){ 25 | return "Hello world"; 26 | }); 27 | ``` 28 | For more details on routes, please go [here](../guides/routes.md). 29 | 30 | ## 4. Running the app 31 | Once you're happy with how you defined all your routes, you're going to want to instruct Crow to run your app. This is done using the `run()` method. 32 | ``` cpp 33 | app.port(18080).multithreaded().run(); 34 | ``` 35 | Please note that the `port()` and `multithreaded()` methods aren't needed, though not using `port()` will cause the default port (`80`) to be used.
36 | 37 | ## Putting it all together 38 | 39 | Once you've followed all the steps above, your code should look similar to this 40 | 41 | ``` cpp title="main.cpp" linenums="1" 42 | #include "crow.h" 43 | //#include "crow_all.h" 44 | 45 | int main() 46 | { 47 | crow::SimpleApp app; //define your crow application 48 | 49 | //define your endpoint at the root directory 50 | CROW_ROUTE(app, "/")([](){ 51 | return "Hello world"; 52 | }); 53 | 54 | //set the port, set the app to run on multiple threads, and run the app 55 | app.port(18080).multithreaded().run(); 56 | } 57 | ``` 58 | 59 | You then need to compile your code on your 60 | [Linux](setup/linux.md#compiling-your-project), 61 | [MacOS](setup/macos.md#compiling-using-a-compiler-directly), or 62 | [Windows](setup/windows.md#getting-and-compiling-crow) machine 63 | 64 | After building your `.cpp` file and running the resulting executable, you should be able to access your endpoint at [http://localhost:18080](http://localhost:18080). Opening this URL in your browser will show a white screen with "Hello world" typed on it. 65 | -------------------------------------------------------------------------------- /docs/guides/app.md: -------------------------------------------------------------------------------- 1 | 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.

2 | An app allows access to the HTTP server (for handling connections), router (for handling URLs and requests), Middlewares (for extending Crow), among many others.

3 | 4 | Crow has 2 different app types: 5 | 6 | ## SimpleApp 7 | Has no middlewares. 8 | 9 | ## App<m1, m2, ...> 10 | Has middlewares. 11 | 12 | ## Using the app 13 | To use a Crow app, simply define `#!cpp crow::SimpleApp` or `#!cpp crow::App` if you're using middlewares.
14 | The methods of an app can be chained. That means that you can configure and run your app in the same code line. 15 | ``` cpp 16 | app.bindaddr("192.168.1.2").port(443).ssl_file("certfile.crt","keyfile.key").multithreaded().run(); 17 | ``` 18 | Or if you like your code neat 19 | ``` cpp 20 | app.bindaddr("192.168.1.2") 21 | .port(443) 22 | .ssl_file("certfile.crt","keyfile.key") 23 | .multithreaded() 24 | .run(); 25 | ``` 26 | 27 | !!! note 28 | 29 | The `run()` method is blocking. To run a Crow app asynchronously `run_async()` should be used instead. 30 | 31 | !!! warning 32 | 33 | When using `run_async()`, make sure to use a variable to save the function's output (such as `#!cpp auto _a = app.run_async()`). Otherwise the app will run synchronously. 34 | 35 |

36 | 37 | For more info on middlewares, check out [this page](middleware.md).

38 | For more info on what functions are available to a Crow app, go [here](../reference/classcrow_1_1_crow.html). 39 | -------------------------------------------------------------------------------- /docs/guides/base64.md: -------------------------------------------------------------------------------- 1 | ## Encoding 2 | 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. 3 | 4 | ## Decoding 5 | [:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0) 6 | 7 | 8 | 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. 9 | -------------------------------------------------------------------------------- /docs/guides/blueprints.md: -------------------------------------------------------------------------------- 1 | [:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0) 2 | 3 | 4 | Crow supports Flask-style blueprints.
5 | A blueprint is a limited app. It cannot handle networking, but it can handle routes.
6 | Blueprints allow developers to compartmentalize their Crow applications, making them much more modular.

7 | 8 | In order for a blueprint to work, it has to be registered with a Crow app before the app is run. This can be done using `#!cpp app.register_blueprint(blueprint);`.

9 | 10 | Blueprints let you do the following:

11 | 12 | ### Define Routes 13 | 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. 14 | 15 | ### Define a Prefix 16 | 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`. 17 | 18 | !!! Warning 19 | 20 | Unlike routes, blueprint prefixes should contain no slashes. 21 | 22 | 23 | ### Use a custom Static directory 24 | 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. 25 | 26 | !!! note 27 | 28 | 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`. 29 | 30 | 31 | ### Use a custom Templates directory 32 | 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")`. 33 | 34 | !!! note 35 | 36 | 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");`. 37 | 38 | ### Define a custom Catchall route 39 | 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. 40 | 41 | ### Register other Blueprints 42 | 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`. 43 | -------------------------------------------------------------------------------- /docs/guides/compression.md: -------------------------------------------------------------------------------- 1 | [:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3) 2 | 3 | 4 | Crow supports Zlib compression using Gzip or Deflate algorithms. 5 | 6 | ## HTTP Compression 7 | HTTP compression is by default disabled in crow. Do the following to enable it:
8 | - Define `CROW_ENABLE_COMPRESSION` in your compiler definitions (`g++ main.cpp -DCROW_ENABLE_COMPRESSION` for example) or `set(CROW_ENABLE_COMPRESSION ON)` in `CMakeLists.txt`. 9 | - Call `#!cpp use_compression(crow::compression::algorithm)` on your Crow app. 10 | - 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 | 12 | !!! note 13 | 14 | 3rd point is not needed for MSVC or CMake projects using `Crow::Crow` since `vcpkg.json` and Crow's target already include zlib as a dependency. 15 | 16 | For the compression algorithm you can use `crow::compression::algorithm::DEFLATE` or `crow::compression::algorithm::GZIP`.
17 | And now your HTTP responses will be compressed. 18 | 19 | ## Websocket Compression 20 | Crow currently does not support Websocket compression.
21 | 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. 22 | -------------------------------------------------------------------------------- /docs/guides/json.md: -------------------------------------------------------------------------------- 1 | Crow has built in support for JSON data.

2 | 3 | ## type 4 | The types of values that `rvalue and wvalue` can take are as follows:
5 | 6 | - `False`: from type `bool`. 7 | - `True`: from type `bool`. 8 | - `Number` 9 | - `Floating_point`: from type `double`. 10 | - `Signed_integer`: from type `int`. 11 | - `Unsigned_integer`: from type `unsigned int`. 12 | - `String`: from type `std::string`. 13 | - `List`: from type `std::vector`. 14 | - `Object`: from type `crow::json::wvalue or crow::json::rvalue`.
15 | This last type means that `rvalue or wvalue` can have keys. 16 | 17 | ## rvalue 18 | JSON read value, used for taking a JSON string and parsing it into `crow::json`.

19 | 20 | You can read individual items of the rvalue, but you cannot add items to it.
21 | To do that, you need to convert it to a `wvalue`, which can be done by simply writing `#!cpp crow::json::wvalue wval (rval);` (assuming `rval` is your `rvalue`).

22 | 23 | For more info on read values go [here](../reference/classcrow_1_1json_1_1rvalue.html).

24 | 25 | ## wvalue 26 | JSON write value, used for creating, editing and converting JSON to a string.

27 | 28 | !!! note 29 | 30 | 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. 31 | 32 | 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. 33 | 34 | !!! warning 35 | 36 | 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}`) 37 | 38 |

39 | 40 | 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.

41 | 42 | 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.

43 | 44 | A JSON `wvalue` can be returned directly inside a route handler, this will cause the `content-type` header to automatically be set to `Application/json` and the JSON value will be converted to string and placed in the response body. For more information go to [Routes](routes.md).

45 | 46 | For more info on write values go [here](../reference/classcrow_1_1json_1_1wvalue.html). 47 | 48 | !!! note 49 | 50 | 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). 51 | -------------------------------------------------------------------------------- /docs/guides/logging.md: -------------------------------------------------------------------------------- 1 | Crow comes with a simple and easy to use logging system.

2 | 3 | !!! note 4 | 5 | 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. 6 | 7 | ## Setting up logging level 8 | You can set up the level at which crow displays logs by using the app's `loglevel(crow::LogLevel)` method.

9 | 10 | The available log levels are as follows (please note that setting a level will also display all logs below this level): 11 | 12 | - Debug 13 | - Info 14 | - Warning 15 | - Error 16 | - Critical 17 |

18 | 19 | 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.

20 | 21 | !!! note 22 | 23 | Setting the Macro `CROW_ENABLE_DEBUG` during compilation will also set the log level to `Debug` (unless otherwise set using `loglevel()`). 24 | 25 | 26 | ## Writing a log 27 | Writing a log is as simple as `#!cpp CROW_LOG_ << "Hello";` (replace<LOG LEVEL> with the actual level in all caps, so you have `CROW_LOG_WARNING`). 28 | 29 | !!! note 30 | 31 | 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. 32 | 33 | ## Creating A custom logger 34 | [:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0) 35 | 36 | 37 | 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_` macro.
38 | All you need is a class extending `#!cpp crow::ILogHandler` containing the method `#!cpp void log(std::string, crow::LogLevel)`.
39 | Once you have your custom logger, you need to set it via `#!cpp crow::logger::setHandler(&MyLogger);`. Here's a full example:
40 | ```cpp 41 | class CustomLogger : public crow::ILogHandler { 42 | public: 43 | CustomLogger() {} 44 | void log(std::string message, crow::LogLevel /*level*/) { 45 | // "message" doesn't contain the timestamp and loglevel 46 | // prefix the default logger does and it doesn't end 47 | // in a newline. 48 | std::cerr << message << std::endl; 49 | } 50 | }; 51 | 52 | int main(int argc, char** argv) { 53 | CustomLogger logger; 54 | crow::logger::setHandler(&logger); 55 | 56 | crow::SimpleApp app; 57 | CROW_ROUTE(app, "/")([]() { return "Hello"; }); 58 | app.run(); 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/guides/middleware.md: -------------------------------------------------------------------------------- 1 | Middleware is used for altering and inspecting requests before and after calling the handler. In Crow it's very similar to middleware in other web frameworks. 2 | 3 | All middleware is registered in the Crow application 4 | 5 | ```cpp 6 | crow::App app; 7 | ``` 8 | 9 | and is called in this specified order. 10 | 11 | Any middleware requires the following 3 members: 12 | 13 | * A context struct for storing request local data. 14 | * A `before_handle` method, which is called before the handler. 15 | * A `after_handle` method, which is called after the handler. 16 | 17 | !!! warning 18 | 19 | As soon as `response.end()` is called, no other handlers and middleware is run, except for after_handlers of already visited middleware. 20 | 21 | 22 | ## Example 23 | 24 | A middleware that can be used to guard admin handlers 25 | 26 | ```cpp 27 | struct AdminAreaGuard 28 | { 29 | struct context 30 | {}; 31 | 32 | void before_handle(crow::request& req, crow::response& res, context& ctx) 33 | { 34 | if (req.remote_ip_address != ADMIN_IP) 35 | { 36 | res.code = 403; 37 | res.end(); 38 | } 39 | } 40 | 41 | void after_handle(crow::request& req, crow::response& res, context& ctx) 42 | {} 43 | }; 44 | ``` 45 | 46 | 47 | ### before_handle and after_handle 48 | There are two possible signatures for before_handle and after_handle 49 | 50 | 1. if you only need to access this middleware's context. 51 | 52 | ```cpp 53 | void before_handle(request& req, response& res, context& ctx) 54 | ``` 55 | 56 | 2. To get access to other middlewares context 57 | 58 | ``` cpp 59 | template 60 | void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx) 61 | { 62 | auto other_ctx = all_ctx.template get(); 63 | } 64 | ``` 65 | 66 | ## Local middleware 67 | 68 | By default, every middleware is called for each request. If you want to enable middleware for specific handlers or blueprints, you have to extend it from `crow::ILocalMiddleware` 69 | 70 | ```cpp 71 | struct LocalMiddleware : crow::ILocalMiddleware 72 | { 73 | ``` 74 | 75 | After this, you can enable it for specific handlers 76 | 77 | ```cpp 78 | CROW_ROUTE(app, "/with_middleware") 79 | .CROW_MIDDLEWARES(app, LocalMiddleware) 80 | ([]() { 81 | return "Hello world!"; 82 | }); 83 | ``` 84 | 85 | or blueprints 86 | 87 | ```cpp 88 | Blueprint bp("with_middleware"); 89 | bp.CROW_MIDDLEWARES(app, FistLocalMiddleware, SecondLocalMiddleware); 90 | ``` 91 | 92 | !!! warning 93 | 94 | Local and global middleware are called separately. First all global middleware is run, then all enabled local middleware for the current handler is run. In both cases middleware is called strongly 95 | in the order listed in the Crow application. 96 | -------------------------------------------------------------------------------- /docs/guides/multipart.md: -------------------------------------------------------------------------------- 1 | [:octicons-feed-tag-16: v0.2](https://github.com/CrowCpp/Crow/releases/0.2) 2 | 3 | 4 | Multipart is a way of forming HTTP requests or responses to contain multiple distinct parts.
5 | 6 | Such an approach allows a request to contain multiple different pieces of data with potentially conflicting data types in a single response payload.
7 | It is typically used either in HTML forms, or when uploading multiple files.

8 | 9 | ## How multipart messages work 10 | The structure of a multipart request is typically consistent of:
11 | 12 | - A Header: Typically `multipart/form-data;boundary=`, This defines the HTTP message as being multipart, as well as defining the separator used to distinguish the different parts.
13 | - 1 or more parts: 14 | - `--` 15 | - Part header: typically `content-disposition: mime/type; name=""` (`mime/type` should be replaced with the actual mime-type), can also contain a `filename` property (separated from the rest by a `;` and structured similarly to the `name` property) 16 | - Value 17 | - `----`

18 | 19 | ## Multipart messages in Crow 20 | Crow supports multipart requests and responses though `crow::multipart::message` and `crow::multipart::message_view`, where `crow::multipart::message` owns the contents of the message and `crow::multipart::message_view` stores views into its parts.
21 | 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`.

22 | 23 | Once a multipart message has been made, the individual parts can be accessed throughout `msg.parts`, `parts` is an `std::vector`.

24 | 25 | [:octicons-feed-tag-16: v1.0](https://github.com/CrowCpp/Crow/releases/v1.0) 26 | 27 | 28 | 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 for owning message and `crow::multipart::header_view` for non-owning message.

29 | 30 | The message's individual body parts can be accessed by name using `msg.get_part_by_name("part-name")`.

31 | 32 | For more info on Multipart messages, go [here](../reference/namespacecrow_1_1multipart.html) 33 | -------------------------------------------------------------------------------- /docs/guides/proxies.md: -------------------------------------------------------------------------------- 1 | You can set Crow up behind any HTTP proxy of your liking, but we will be focusing specifically on 2 of the most popular web server software solutions, Apache2 and Nginx.

2 | 3 | A reverse proxy allows you to use Crow without exposing it directly to the internet. It also allows you to, for example, have crow run on a certain specific domain name, subdomain, or even a path, such as `domain.abc/crow`.

4 | 5 | We advise that you set crow up behind some form of reverse proxy if you plan on running a production Crow server that isn't local.
6 | 7 | !!! warning "SSL" 8 | 9 | When using a proxy, make sure that you **do not** compile Crow with SSL enabled. SSL should be handled by the proxy. 10 | 11 | ## Apache2 12 | 13 | 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: 14 | 15 | ```sh 16 | a2enmod proxy 17 | a2enmod proxy_http 18 | a2enmod proxy_html 19 | a2enmod proxy_wstunnel 20 | ``` 21 | 22 | Next up you'll need to change your configuration (default is `/etc/apache2/sites-enabled/000-default.conf`) and add the following lines (replace `localhost` and `40080` with the address and port you defined for your Crow App): 23 | ``` 24 | ProxyPass / http://localhost:40080 25 | ProxyPassReverse / http://localhost:40080 26 | ``` 27 | If you want crow to run in a subdirectory (such as `domain.abc/crow`) you can use the `location` tag: 28 | ``` 29 | 30 | ProxyPass http://localhost:40080 31 | ProxyPassReverse http://localhost:40080 32 | 33 | ``` 34 | 35 | !!! note 36 | 37 | If you're using an Arch Linux based OS. You will have to access `/etc/httpd/conf/httpd.conf` to enable modules and change configuration. 38 | 39 | ## Nginx 40 | 41 | Setting Nginx up is slightly simpler than Apache, all you need is the Nginx package itself. Once you've installed it, go to the configuration file (usually a `.conf` file located in `/etc/nginx`) and add the following lines to your server section (replace `localhost` and `40080` with the address and port you defined for your Crow App): 42 | 43 | ``` 44 | location / { 45 | proxy_pass http://localhost:40080/; 46 | proxy_http_version 1.1; 47 | } 48 | ``` 49 | Remember to remove or comment out any existing `location /` section.

50 | 51 | Alternatively, if you want to use a subdirectory, you can simply change the location parameter as such: 52 | 53 | ``` 54 | location /crow/ { 55 | proxy_pass http://localhost:40080/; 56 | proxy_http_version 1.1; 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/guides/query-string.md: -------------------------------------------------------------------------------- 1 | A query string is the part of the URL that comes after a `?` character, it is usually formatted as `key=value&otherkey=othervalue`. 2 |

3 | 4 | Crow supports query strings through `crow::request::url_params`. The object is of type `crow::query_string` and can has the following functions:
5 | 6 | ## get(name) 7 | Returns the value (as char*) based on the given key (or name). Returns `nullptr` if the key is not found. 8 | ## pop(name) 9 | [:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3) 10 | 11 | 12 | Works the same as `get`, but removes the returned value. 13 | !!! note 14 | 15 | `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. 16 | 17 | ## get_list(name) 18 | 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` containing `[value1, value2, value3]`.

19 | 20 | `#!cpp get_list("key", false)` can be used to parse `http://example.com?key=value1&key=value2&key=value3` 21 | ## pop_list(name) 22 | [:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3) 23 | 24 | 25 | Works the same as `get_list` but removes all instances of values having the given key (`use_brackets` is also available here). 26 | ## get_dict(name) 27 | Returns an `std::unordered_map` from a query string such as `?key[sub_key1]=value1&key[sub_key2]=value2&key[sub_key3]=value3`.
28 | 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. 29 | ## pop_dict(name) 30 | [:octicons-feed-tag-16: v0.3](https://github.com/CrowCpp/Crow/releases/v0.3) 31 | 32 | 33 | Works the same as `get_dict` but removing the values from the query string. 34 | !!! warning 35 | 36 | 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. 37 | 38 |

39 | For more information take a look [here](../reference/classcrow_1_1query__string.html). 40 | -------------------------------------------------------------------------------- /docs/guides/ssl.md: -------------------------------------------------------------------------------- 1 | Crow supports HTTPS though SSL or TLS.

2 | 3 | !!! note 4 | 5 | When mentioning SSL in this documentation, it is often a reference to openSSL, which includes TLS.

6 | 7 | 8 | To enable SSL, first your application needs to define either a `.crt` and `.key` files, or a `.pem` file. You can create them using openssl locally or use CA based ones 9 | Once you have your files, you can add them to your app like this:
10 | `#!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.

11 | 12 | If you are using fullchain certificate files such as the ones issued using let's encrypt/certbot/acme.sh, the CA part should be provided to crow and processed accordingly.
13 | To do so, you should not use `#!cpp app.ssl_file("/path/to/cert.crt", "/path/to/keyfile.key")` but rather `#!cpp app.ssl_chainfile("/path/to/fullchain.cer", "/path/to/keyfile.key")` or your application will retrun `[ERROR ] Could not start adaptor: tlsv1 alert unknown ca (SSL routines)` on request and client side will get a similar error `#!bash ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)`.

14 | 15 | You also need to define `CROW_ENABLE_SSL` in your compiler definitions (`g++ main.cpp -DCROW_ENABLE_SSL` for example) or `set(CROW_ENABLE_SSL ON)` in `CMakeLists.txt`. 16 | 17 | You can also set your own SSL context (by using `asio::ssl::context ctx`) and then applying it via the `#!cpp app.ssl(ctx)` method.

18 | 19 | !!! warning 20 | 21 | If you plan on using a proxy like Nginx or Apache2, **DO NOT** use SSL in crow, instead define it in your proxy and keep the connection between the proxy and Crow non-SSL. 22 | -------------------------------------------------------------------------------- /docs/guides/static.md: -------------------------------------------------------------------------------- 1 | [:octicons-feed-tag-16: v0.2](https://github.com/CrowCpp/Crow/releases/0.2) 2 | 3 | 4 | A static file is any file that resides in the server's storage. 5 | 6 | Crow supports returning static files as responses either implicitly or explicitly. 7 | 8 | ## Implicit 9 | 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`.

10 | The static folder or endpoint can be changed by defining the macros `CROW_STATIC_DIRECTORY "alternative_directory/"` and `CROW_STATIC_ENDPOINT "/alternative_endpoint/"`.
11 | static directory changes the directory in the server's file system, while the endpoint changes the URL that the client needs to access. 12 | 13 | ## Explicit 14 | 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. 15 | 16 | !!! Warning 17 | 18 | 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. 19 | 20 | !!! note 21 | 22 | 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`. 23 | 24 | !!! note 25 | 26 | 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. 27 | -------------------------------------------------------------------------------- /docs/guides/syste.md: -------------------------------------------------------------------------------- 1 | Using Systemd allows you to run any executable or script when the system starts. This can be useful when you don't want to re-run your Crow application every single time you restart your server.

2 | 3 | ## Writing the Service Unit File 4 | In order to have Systemd recognize your application, you need to create a `.service` file that explains how Systemd should handle your program.

5 | 6 | To create a service file, you need to go to `/etc/systemd/system` and create an empty text file with the extension `.service`, the file name can be anything.

7 | 8 | Once the file is created, open it using your favorite text editor and add the following: 9 | 10 | ```sh 11 | [Unit] 12 | Description=My revolutionary Crow application 13 | 14 | Wants=network.target 15 | After=syslog.target network-online.target 16 | 17 | [Service] 18 | Type=simple 19 | ExecStart=/absolute/path/to/your/executable 20 | Restart=on-failure 21 | RestartSec=10 22 | KillMode=process 23 | 24 | [Install] 25 | WantedBy=multi-user.target 26 | ``` 27 | 28 | You will then need to give the correct permission, this can be done by using the following command (a `sudo` maybe required): 29 | 30 | ```sh 31 | chmod 640 /etc/systemd/system/crowthing.service 32 | ``` 33 | 34 | And that's it! You can now use your `systemctl` controls to `enable`, `start`, `stop`, or `disable` your Crow application.

35 | 36 | If you're not familiar with Systemd, `systemctl enable crowthing.service` will allow your Crow application to run at startup, `start` will start it, and the rest is simple. 37 | -------------------------------------------------------------------------------- /docs/guides/templating.md: -------------------------------------------------------------------------------- 1 | Templating is when you return an HTML page with custom data. You can probably tell why that's useful.

2 | 3 | Crow supports [mustache](http://mustache.github.io) for templates through its own implementation `crow::mustache`.

4 | 5 | !!! note 6 | 7 | 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. 8 | 9 | ## Components of mustache 10 | 11 | There are 2 components of a mustache template implementation: 12 | 13 | - Page 14 | - Context 15 | 16 | ### Page 17 | 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.

18 | 19 | The templates directory is usually called `templates`, but can be adjusted per Route (via `crow::mustache::set_base("new_templates_directory")`), per [Blueprint](blueprints.md), or globally (via `crow::mustache::set_global_base("new_templates_directory"")`).

20 | 21 | For more information on how to formulate a template, see [this mustache manual](http://mustache.github.io/mustache.5.html). 22 | 23 | ### Context 24 | A JSON object containing the tags as keys and their values. `crow::mustache::context` is actually a [crow::json::wvalue](json.md#wvalue).

25 | 26 | !!! note 27 | 28 | `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";};`. 29 | 30 | !!! note 31 | 32 | The string returned by the lamdba can contain mustache tags, Crow will parse it as any normal template string. 33 | 34 | ## Returning a template 35 | 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. 36 | 37 | !!! note 38 | 39 | 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. 40 | 41 | !!! Warning 42 | 43 | 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. 44 | 45 |
46 | 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;`).
47 | With your context and page ready, just `#!cpp return page.render(ctx);`. This will use the context data to return a filled template.
48 | Alternatively you could just render the page without a context using `#!cpp return page.render();`. 49 | 50 | !!! note 51 | 52 | `#!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. 53 | -------------------------------------------------------------------------------- /docs/guides/testing.md: -------------------------------------------------------------------------------- 1 | Unit tests can be written in 2 ways for a Crow application.

2 | 3 | ## The handler method 4 | 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. 5 | 6 | ```cpp linenums="1" 7 | CROW_ROUTE(app, "/place") 8 | ([] { return "hi"; }); 9 | 10 | app.validate(); //Used to make sure all the route handlers are in order. 11 | 12 | { 13 | request req; 14 | response res; 15 | 16 | req.url = "/place"; 17 | 18 | app.handle_full(req, res); 19 | // res will contain: 20 | // res.code == 200 21 | // res.body == "hi" 22 | } 23 | ``` 24 | 25 | !!! note 26 | 27 | This method is the simpler of the two and is usually all you really need to test your routes. 28 | 29 | 30 | !!! warning 31 | 32 | This method does not send any data, nor does it run any post handle code, so things like static file serving (as far as sending the actual data) or compression cannot be tested using this method. 33 | 34 | 35 | ## The client method 36 | This method involves creating a simple [ASIO](https://think-async.com/Asio/) client that sends the request and receives the response. It is considerably more complex than the earlier method, but it is both more realistic and includes post handle operations. 37 | 38 | ```cpp linenums="1" 39 | static char buf[2048]; 40 | SimpleApp app; 41 | CROW_ROUTE(app, "/")([] { return "A"; }); 42 | 43 | auto _ = async(launch::async,[&] { app1.bindaddr("127.0.0.1").port(45451).run(); }); 44 | app.wait_for_server_start(); 45 | 46 | std::string sendmsg = "GET /\r\nContent-Length:3\r\nX-HeaderTest: 123\r\n\r\nA=B\r\n"; 47 | asio::io_service is; 48 | { 49 | asio::ip::tcp::socket c(is); 50 | c.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 45451)); 51 | 52 | c.send(asio::buffer(sendmsg)); 53 | 54 | size_t recved = c.receive(asio::buffer(buf, 2048)); 55 | CHECK('A' == buf[recved - 1]); //This is specific to catch2 testing library, but it should give a general idea of how to read the response. 56 | } 57 | 58 | app.stop(); //THIS MUST RUN 59 | } 60 | 61 | ``` 62 | The first part is straightforward, create an app and add a route.
63 | The second part is launching the app asynchronously and waiting until it starts.
64 | The third is formulating our HTTP request string, the format is: 65 | ``` 66 | METHOD / 67 | Content-Length:123 68 | header1:value1 69 | header2:value2 70 | 71 | BODY 72 | 73 | ``` 74 | Next an `io_service` is created, then a TCP socket is created with the `io_service` and is connected to the application.
75 | Then send the HTTP request string through the socket inside a buffer, and read the result into the buffer in `line 1`.
76 | Finally check the result against the expected one. 77 | 78 | !!! warning 79 | 80 | 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. 81 | -------------------------------------------------------------------------------- /docs/guides/websockets.md: -------------------------------------------------------------------------------- 1 | Websockets are a way of connecting a client and a server without the request response nature of HTTP.

2 | 3 | ## Routes 4 | To create a websocket in Crow, you need a websocket route.
5 | A websocket route differs from a normal route quite a bit. It uses a slightly altered `CROW_WEBSOCKET_ROUTE(app, "/url")` macro, which is then followed by a series of methods (with handlers inside) for each event. These are (sorted by order of execution): 6 | 7 | 8 | - `#!cpp onaccept([&](const crow::request& req, void** userdata){handler code goes here})` 9 | - `#!cpp onopen([&](crow::websocket::connection& conn){handler code goes here})` 10 | - `#!cpp onmessage([&](crow::websocket::connection& conn, const std::string& message, bool is_binary){handler code goes here})` 11 | - `#!cpp onerror([&](crow::websocket::connection& conn, const std::string& error_message){handler code goes here})` 12 | - `#!cpp onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t with_status_code ){handler code goes here})` 13 | 14 | !!! note 15 | 16 | `onaccept` must return a boolean. In case `false` is returned, the connection is shut down, deleted, and no further communication is done. 17 | 18 | !!! Warning 19 | 20 | By default, Crow allows clients to send unmasked websocket messages. This is useful for debugging, but goes against the protocol specifications. Production Crow applications should enforce the protocol by adding `#!cpp #define CROW_ENFORCE_WS_SPEC` to their source code. 21 | 22 | These event methods and their handlers can be chained. The full route should look similar to this: 23 | ```cpp 24 | CROW_WEBSOCKET_ROUTE(app, "/ws") 25 | .onopen([&](crow::websocket::connection& conn){ 26 | do_something(); 27 | }) 28 | .onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t){ 29 | do_something(); 30 | }) 31 | .onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary){ 32 | if (is_binary) 33 | do_something(data); 34 | else 35 | do_something_else(data); 36 | }); 37 | ``` 38 | 39 | ## Maximum payload size 40 | [:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow) 41 | 42 | The maximum payload size that a connection accepts can be adjusted either globally by using `#!cpp app.websocket_max_payload()` or per route by using `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload()`. In case a message was sent that exceeded the limit. The connection would be shut down and `onerror` would be triggered. 43 | 44 | !!! note 45 | 46 | By default, this limit is disabled. To disable the global setting in specific routes, you only need to call `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").max_payload(UINT64_MAX)`. 47 | 48 | ## Subprotocols 49 | [:octicons-feed-tag-16: master](https://github.com/CrowCpp/Crow) 50 | 51 | Specifies the possible subprotocols that are available for the client. If specified, the first match with the client's requested subprotocols will be returned in the "Sec-WebSocket-Protocol" header of the handshake response. Otherwise, the connection will be closed. If no subprotocol are specified on both the client and the server side, the connection process will continue normally. It can be specified by using `#!cpp CROW_WEBSOCKET_ROUTE(app, "/url").subprotocols()`. 52 | 53 | 54 | For more info about websocket routes go [here](../reference/classcrow_1_1_web_socket_rule.html). 55 | 56 | For more info about websocket connections go [here](../reference/classcrow_1_1websocket_1_1_connection.html). 57 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | template: home.html 3 | --- 4 | -------------------------------------------------------------------------------- /docs/overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extrahead %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /docs/overrides/partials/footer.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | {% import "partials/language.html" as lang with context %} 24 | 25 | 33 | 34 | 35 | 113 | -------------------------------------------------------------------------------- /docs/overrides/partials/integrations/analytics/matomo.html: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /docs/overrides/partials/social.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 |
22 | {% for social in config.extra.social %} 23 | {% set title = social.name %} 24 | {% if not title and "//" in social.link %} 25 | {% set _, url = social.link.split("//") %} 26 | {% set title = url.split("/")[0] %} 27 | {% endif %} 28 | 34 | {% include ".icons/" ~ social.icon ~ ".svg" %} 35 | 36 | {% endfor %} 37 |
38 | -------------------------------------------------------------------------------- /docs/overrides/privacy_policy.html: -------------------------------------------------------------------------------- 1 | {% extends "main.html" %} 2 | 3 | {% block content %} 4 |

Privacy Policy for CrowCpp

5 | 6 |

If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us.

7 | 8 |

This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to the information that they shared and/or collect in CrowCpp. This policy is not applicable to any information collected offline or via channels other than this website.

9 | 10 |

Consent

11 | 12 |

By using our website, you hereby consent to our Privacy Policy and agree to its terms.

13 | 14 |

Log Files

15 | 16 |

CrowCpp follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services' analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users' movement on the website.

17 | 18 |

Other information we collect

19 | 20 |

If you contact us directly, we may receive information about you such as your name, email address, phone number, the contents of the message and/or attachments you may send us, and any other information you may choose to provide.

21 | 22 |

How we use your information

23 | 24 |

We use the information we collect in various ways, including to:

25 | 26 |
    27 |
  • Provide, operate, and maintain our website
  • 28 |
  • Improve or expand our website
  • 29 |
  • Understand and analyze how you use our website
  • 30 |
  • Find and prevent fraud
  • 31 |
32 | 33 |

Children's Information

34 | 35 |

CrowCpp does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.

36 | {% endblock %} 37 | {% block site_nav %}{% endblock %} 38 | -------------------------------------------------------------------------------- /docs/stylesheets/Lato-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/docs/stylesheets/Lato-Medium.ttf -------------------------------------------------------------------------------- /docs/stylesheets/colors.css: -------------------------------------------------------------------------------- 1 | [data-md-color-scheme="crow-light"]{ 2 | 3 | --md-primary-fg-color: #24404f; 4 | --md-accent-fg-color: #122027; 5 | --md-typeset-a-color: var(--md-accent-fg-color) !important; 6 | --md-default-bg-color: #e5f2f8; 7 | --md-code-bg-color: #cfcfcf !important; 8 | --md-code-hl-comment-color: var(--md-code-fg-color) !important; 9 | --md-code-hl-generic-color: var(--md-code-fg-color) !important; 10 | --md-code-hl-variable-color: var(--md-code-fg-color) !important; 11 | --md-code-fg-color: #1d1d1d !important; 12 | --md-code-hl-punctuation-color: #1d1d1d !important; 13 | --home-border-color: #00000080; 14 | --home-shadow-color: #00000040; 15 | --home-image-border: linear-gradient(90deg, rgba(0,0,0,0) 0%, rgb(0, 0, 0) 50%, rgba(0,0,0,0) 100%); 16 | --md-default-tag-bg-color: var(--md-default-bg-color); 17 | 18 | } 19 | 20 | [data-md-color-scheme="crow-dark"]{ 21 | 22 | --md-primary-fg-color: #24404f; 23 | --md-accent-fg-color: #445e6d; 24 | --md-default-fg-color: rgba(255, 255, 255, 0.87); 25 | --md-typeset-color: rgba(255, 255, 255, 0.80); 26 | --md-admonition-fg-color: rgba(255, 255, 255, 0.80); 27 | --md-default-fg-color--light: rgba(255, 255, 255, 0.54); 28 | --md-default-fg-color--lighter: rgba(255, 255, 255, 0.32); 29 | --md-default-fg-color--lightest: rgba(255, 255, 255, 0.07); 30 | --md-typeset-a-color: var(--md-accent-fg-color) !important; 31 | --md-default-bg-color: #1a2124; 32 | --md-code-bg-color: #101010 !important; 33 | --md-code-hl-comment-color: var(--md-code-fg-color) !important; 34 | --md-code-hl-generic-color: var(--md-code-fg-color) !important; 35 | --md-code-hl-variable-color: var(--md-code-fg-color) !important; 36 | --md-code-hl-operator-color: var(--md-code-fg-color) !important; 37 | --md-code-fg-color: #cfcfcf !important; 38 | --md-code-hl-punctuation-color: #adadad !important; 39 | --home-border-color: #ffffff20; 40 | --home-shadow-color: #00000040; 41 | --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%); 42 | --md-admonition-bg-color: #272a2b; 43 | --md-code-hl-color: rgba(255, 255, 0, 0.18); 44 | --md-default-tag-bg-color: var(--md-default-fg-color); 45 | 46 | } 47 | 48 | .md-typeset .md-button { 49 | color: var(--md-default-fg-color--light); 50 | border-radius: 0.5rem; 51 | } 52 | 53 | /* 54 | :root{ 55 | --md-default-fg-color: rgba(255, 255, 255, 0.87); 56 | --md-default-fg-color--light: rgba(255, 255, 255, 0.54); 57 | --md-default-fg-color--lighter: rgba(255, 255, 255, 0.32); 58 | --md-default-fg-color--lightest: rgba(255, 255, 255, 0.07); 59 | --md-default-bg-color: #1a2124; 60 | --md-typeset-a-color: var(--md-accent-fg-color) !important; 61 | } 62 | 63 | .md-typeset code { 64 | background-color: #2f2f2f; 65 | } 66 | 67 | 68 | .highlight .c, .highlight .c1, .highlight .ch, .highlight .cm, .highlight .cs, .highlight .sd { 69 | color: #606060 !important; 70 | } 71 | 72 | .linenos { 73 | background-color: #cfcfcf !important; 74 | color: #1d1d1d !important; 75 | } 76 | 77 | .highlighttable .linenodiv pre { 78 | background-color: #cfcfcf !important; 79 | color: #1d1d1d !important; 80 | } 81 | 82 | .highlight .p { 83 | color: #1d1d1d; 84 | } 85 | 86 | .highlight .o { 87 | color: #1d1d1d; 88 | } 89 | 90 | .highlight .n { 91 | color: #1d1d1d; 92 | } 93 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | .md-header__source { 2 | margin-left: 0px; 3 | width: autho; 4 | min-width: 9.6rem; 5 | } 6 | 7 | .md-typeset code { 8 | border-radius: .25rem !important; 9 | } 10 | 11 | .code { 12 | border-radius: .25rem !important; 13 | } 14 | 15 | .tag 16 | { 17 | background-color: var(--md-primary-fg-color); 18 | color: var(--md-default-tag-bg-color); 19 | border-radius: 50px; 20 | padding-left: 0.15em; 21 | padding-right: 0.35em; 22 | padding-top: 0.45em; 23 | padding-bottom: 0.35em; 24 | } 25 | 26 | .tag a 27 | { 28 | color: var(--md-default-tag-bg-color); 29 | } 30 | 31 | .tag a:hover 32 | { 33 | color: var(--md-default-tag-bg-color); 34 | } 35 | 36 | .md-typeset :is(.emojione, .twemoji, .gemoji) 37 | { 38 | vertical-align: text-bottom; 39 | } 40 | 41 | .md-typeset :is(.emojione, .twemoji, .gemoji) svg 42 | { 43 | width: 1.25em; 44 | } 45 | 46 | [data-md-color-scheme="crow-dark"] img[src$="#only-dark"] 47 | { 48 | display: block; 49 | } 50 | [data-md-color-scheme="crow-light"] img[src$="#only-light"] 51 | { 52 | display: block; 53 | } 54 | [data-md-color-scheme="crow-light"] img[src$="#only-dark"] 55 | { 56 | display: none; 57 | } 58 | [data-md-color-scheme="crow-dark"] img[src$="#only-light"] 59 | { 60 | display: none; 61 | } 62 | -------------------------------------------------------------------------------- /docs/stylesheets/latofonts.css: -------------------------------------------------------------------------------- 1 | /* Webfont: Lato-Medium */@font-face { 2 | font-family: 'Lato'; 3 | src: url('Lato-Medium.ttf') format('truetype'); 4 | font-style: normal; 5 | font-weight: normal; 6 | text-rendering: optimizeLegibility; 7 | } 8 | body, input { 9 | font-family: "Lato", -apple-system, Helvetica, Arial, sans-serif; 10 | } 11 | -------------------------------------------------------------------------------- /docs/versions.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"version": "master", "title": "master", "aliases": []}, 3 | {"version": "1.2", "title": "1.2.0", "aliases": []}, 4 | {"version": "1.1", "title": "1.1.0", "aliases": []}, 5 | {"version": "1.0", "title": "1.0+3", "aliases": []}, 6 | {"version": "0.3", "title": "0.3+4", "aliases": []} 7 | ] 8 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(crow_examples) 3 | 4 | include(${CMAKE_SOURCE_DIR}/cmake/compiler_options.cmake) 5 | 6 | add_executable(helloworld helloworld.cpp) 7 | add_warnings_optimizations(helloworld) 8 | target_link_libraries(helloworld PUBLIC Crow::Crow) 9 | 10 | # If compression is enabled, the example will be built 11 | if("compression" IN_LIST CROW_FEATURES) 12 | add_executable(example_compression example_compression.cpp) 13 | add_warnings_optimizations(example_compression) 14 | target_link_libraries(example_compression Crow::Crow) 15 | else() 16 | message(STATUS "example_compression example deactivated") 17 | endif() 18 | 19 | # If SSL is enabled, the example will be built 20 | if("ssl" IN_LIST CROW_FEATURES) 21 | add_executable(example_ssl ssl/example_ssl.cpp) 22 | add_warnings_optimizations(example_ssl) 23 | target_link_libraries(example_ssl PUBLIC Crow::Crow) 24 | else() 25 | message(STATUS "example_ssl example deactivated") 26 | endif() 27 | 28 | add_executable(example_websocket websocket/example_ws.cpp) 29 | add_warnings_optimizations(example_websocket) 30 | target_link_libraries(example_websocket PUBLIC Crow::Crow) 31 | add_custom_command(OUTPUT ws.html 32 | COMMAND ${CMAKE_COMMAND} -E 33 | copy ${PROJECT_SOURCE_DIR}/websocket/templates/ws.html ${CMAKE_CURRENT_BINARY_DIR}/templates/ws.html 34 | DEPENDS ${PROJECT_SOURCE_DIR}/websocket/templates/ws.html 35 | ) 36 | add_custom_target(example_ws_copy ALL DEPENDS ws.html) 37 | 38 | add_executable(basic_example example.cpp) 39 | add_warnings_optimizations(basic_example) 40 | target_link_libraries(basic_example PUBLIC Crow::Crow) 41 | 42 | if(CROW_AMALGAMATE) 43 | add_executable(example_with_all example_with_all.cpp) 44 | add_dependencies(example_with_all crow_amalgamated) 45 | add_warnings_optimizations(example_with_all) 46 | target_link_libraries(example_with_all PUBLIC Crow::Crow) 47 | target_include_directories(example_with_all PUBLIC ${CMAKE_BINARY_DIR}) 48 | endif() 49 | 50 | add_executable(example_chat example_chat.cpp) 51 | add_warnings_optimizations(example_chat) 52 | target_link_libraries(example_chat PUBLIC Crow::Crow) 53 | add_custom_command(OUTPUT example_chat.html 54 | COMMAND ${CMAKE_COMMAND} -E 55 | copy ${PROJECT_SOURCE_DIR}/example_chat.html ${CMAKE_CURRENT_BINARY_DIR}/templates/example_chat.html 56 | DEPENDS ${PROJECT_SOURCE_DIR}/example_chat.html 57 | ) 58 | add_custom_target(example_chat_copy ALL DEPENDS example_chat.html) 59 | 60 | add_executable(example_static_file example_static_file.cpp) 61 | add_warnings_optimizations(example_static_file) 62 | target_link_libraries(example_static_file PUBLIC Crow::Crow) 63 | 64 | add_executable(example_catchall example_catchall.cpp) 65 | add_warnings_optimizations(example_catchall) 66 | target_link_libraries(example_catchall PUBLIC Crow::Crow) 67 | 68 | add_executable(example_json_map example_json_map.cpp) 69 | add_warnings_optimizations(example_json_map) 70 | target_link_libraries(example_json_map PUBLIC Crow::Crow) 71 | 72 | add_executable(example_blueprint example_blueprint.cpp) 73 | add_warnings_optimizations(example_blueprint) 74 | target_link_libraries(example_blueprint PUBLIC Crow::Crow) 75 | 76 | add_executable(example_middleware example_middleware.cpp) 77 | add_warnings_optimizations(example_middleware) 78 | target_link_libraries(example_middleware PUBLIC Crow::Crow) 79 | 80 | add_executable(example_cors middlewares/example_cors.cpp) 81 | add_warnings_optimizations(example_cors) 82 | target_link_libraries(example_cors PUBLIC Crow::Crow) 83 | 84 | add_executable(example_cookies middlewares/example_cookies.cpp) 85 | add_warnings_optimizations(example_cookies) 86 | target_link_libraries(example_cookies PUBLIC Crow::Crow) 87 | 88 | add_executable(example_session middlewares/example_session.cpp) 89 | add_warnings_optimizations(example_session) 90 | target_link_libraries(example_session PUBLIC Crow::Crow) 91 | 92 | add_executable(example_file_upload example_file_upload.cpp) 93 | add_warnings_optimizations(example_file_upload) 94 | target_link_libraries(example_file_upload PUBLIC Crow::Crow) 95 | 96 | if(MSVC) 97 | add_executable(example_vs example_vs.cpp) 98 | add_warnings_optimizations(example_vs) 99 | target_link_libraries(example_vs Crow::Crow) 100 | endif() 101 | -------------------------------------------------------------------------------- /examples/example_blueprint.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | 3 | int main() 4 | { 5 | crow::SimpleApp app; 6 | 7 | crow::Blueprint bp("bp_prefix", "cstat", "ctemplate"); 8 | 9 | 10 | crow::Blueprint sub_bp("bp2", "csstat", "cstemplate"); 11 | 12 | CROW_BP_ROUTE(sub_bp, "/") 13 | ([]() { 14 | return "Hello world!"; 15 | }); 16 | 17 | /* CROW_BP_ROUTE(bp, "/templatt") 18 | ([]() { 19 | crow::mustache::context ctxdat; 20 | ctxdat["messg"] = "fifty five!!"; 21 | 22 | auto page = crow::mustache::load("indks.html"); 23 | 24 | return page.render(ctxdat); 25 | }); 26 | */ 27 | CROW_BP_CATCHALL_ROUTE(sub_bp) 28 | ([]() { 29 | return "WRONG!!"; 30 | }); 31 | 32 | 33 | bp.register_blueprint(sub_bp); 34 | app.register_blueprint(bp); 35 | 36 | app.loglevel(crow::LogLevel::Debug).port(18080).run(); 37 | } 38 | -------------------------------------------------------------------------------- /examples/example_catchall.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | 5 | int main() 6 | { 7 | crow::SimpleApp app; 8 | 9 | CROW_ROUTE(app, "/") 10 | ([]() { 11 | return "Hello"; 12 | }); 13 | 14 | //Setting a custom route for any URL that isn't defined, instead of a simple 404. 15 | CROW_CATCHALL_ROUTE(app) 16 | ([](crow::response& res) { 17 | if (res.code == 404) 18 | { 19 | res.body = "The URL does not seem to be correct."; 20 | } 21 | else if (res.code == 405) 22 | { 23 | res.body = "The HTTP method does not seem to be correct."; 24 | } 25 | res.end(); 26 | }); 27 | 28 | app.port(18080).run(); 29 | } 30 | -------------------------------------------------------------------------------- /examples/example_chat.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | vector msgs; 9 | vector> ress; 10 | 11 | void broadcast(const string& msg) 12 | { 13 | msgs.push_back(msg); 14 | crow::json::wvalue x; 15 | x["msgs"][0] = msgs.back(); 16 | x["last"] = msgs.size(); 17 | string body = x.dump(); 18 | for (auto p : ress) 19 | { 20 | auto* res = p.first; 21 | CROW_LOG_DEBUG << res << " replied: " << body; 22 | res->end(body); 23 | } 24 | ress.clear(); 25 | } 26 | // To see how it works go on {ip}:40080 but I just got it working with external build (not directly in IDE, I guess a problem with dependency) 27 | int main() 28 | { 29 | crow::SimpleApp app; 30 | crow::mustache::set_base("."); 31 | 32 | CROW_ROUTE(app, "/") 33 | ([] { 34 | crow::mustache::context ctx; 35 | return crow::mustache::load("example_chat.html").render(); 36 | }); 37 | 38 | CROW_ROUTE(app, "/logs") 39 | ([] { 40 | CROW_LOG_INFO << "logs requested"; 41 | crow::json::wvalue x; 42 | int start = max(0, (int)msgs.size() - 100); 43 | for (int i = start; i < (int)msgs.size(); i++) 44 | x["msgs"][i - start] = msgs[i]; 45 | x["last"] = msgs.size(); 46 | CROW_LOG_INFO << "logs completed"; 47 | return x; 48 | }); 49 | 50 | CROW_ROUTE(app, "/logs/") 51 | ([](const crow::request& /*req*/, crow::response& res, int after) { 52 | CROW_LOG_INFO << "logs with last " << after; 53 | if (after < (int)msgs.size()) 54 | { 55 | crow::json::wvalue x; 56 | for (int i = after; i < (int)msgs.size(); i++) 57 | x["msgs"][i - after] = msgs[i]; 58 | x["last"] = msgs.size(); 59 | 60 | res.write(x.dump()); 61 | res.end(); 62 | } 63 | else 64 | { 65 | vector> filtered; 66 | for (auto p : ress) 67 | { 68 | if (p.first->is_alive() && chrono::steady_clock::now() - p.second < chrono::seconds(30)) 69 | filtered.push_back(p); 70 | else 71 | p.first->end(); 72 | } 73 | ress.swap(filtered); 74 | ress.push_back({&res, chrono::steady_clock::now()}); 75 | CROW_LOG_DEBUG << &res << " stored " << ress.size(); 76 | } 77 | }); 78 | 79 | CROW_ROUTE(app, "/send") 80 | .methods("GET"_method, "POST"_method)([](const crow::request& req) { 81 | CROW_LOG_INFO << "msg from client: " << req.body; 82 | broadcast(req.body); 83 | return ""; 84 | }); 85 | 86 | app.port(40080) 87 | //.multithreaded() 88 | .run(); 89 | } 90 | -------------------------------------------------------------------------------- /examples/example_chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/example_compression.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | #include "crow/compression.h" 3 | 4 | int main() 5 | { 6 | crow::SimpleApp app; 7 | //crow::App app; 8 | 9 | CROW_ROUTE(app, "/hello") 10 | ([&](const crow::request&, crow::response& res) { 11 | res.compressed = false; 12 | 13 | res.body = "Hello World! This is uncompressed!"; 14 | res.end(); 15 | }); 16 | 17 | CROW_ROUTE(app, "/hello_compressed") 18 | ([]() { 19 | return "Hello World! This is compressed by default!"; 20 | }); 21 | 22 | 23 | app.port(18080) 24 | .use_compression(crow::compression::algorithm::DEFLATE) 25 | //.use_compression(crow::compression::algorithm::GZIP) 26 | .loglevel(crow::LogLevel::Debug) 27 | .multithreaded() 28 | .run(); 29 | } 30 | -------------------------------------------------------------------------------- /examples/example_file_upload.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | 3 | int main() 4 | { 5 | crow::SimpleApp app; 6 | 7 | CROW_ROUTE(app, "/uploadfile") 8 | .methods(crow::HTTPMethod::Post)([](const crow::request& req) { 9 | crow::multipart::message_view file_message(req); 10 | for (const auto& part : file_message.part_map) 11 | { 12 | const auto& part_name = part.first; 13 | const auto& part_value = part.second; 14 | CROW_LOG_DEBUG << "Part: " << part_name; 15 | if ("InputFile" == part_name) 16 | { 17 | // Extract the file name 18 | auto headers_it = part_value.headers.find("Content-Disposition"); 19 | if (headers_it == part_value.headers.end()) 20 | { 21 | CROW_LOG_ERROR << "No Content-Disposition found"; 22 | return crow::response(400); 23 | } 24 | auto params_it = headers_it->second.params.find("filename"); 25 | if (params_it == headers_it->second.params.end()) 26 | { 27 | CROW_LOG_ERROR << "Part with name \"InputFile\" should have a file"; 28 | return crow::response(400); 29 | } 30 | const std::string outfile_name{params_it->second}; 31 | 32 | for (const auto& part_header : part_value.headers) 33 | { 34 | const auto& part_header_name = part_header.first; 35 | const auto& part_header_val = part_header.second; 36 | CROW_LOG_DEBUG << "Header: " << part_header_name << '=' << part_header_val.value; 37 | for (const auto& param : part_header_val.params) 38 | { 39 | const auto& param_key = param.first; 40 | const auto& param_val = param.second; 41 | CROW_LOG_DEBUG << " Param: " << param_key << ',' << param_val; 42 | } 43 | } 44 | 45 | // Create a new file with the extracted file name and write file contents to it 46 | std::ofstream out_file(outfile_name); 47 | if (!out_file) 48 | { 49 | CROW_LOG_ERROR << " Write to file failed\n"; 50 | continue; 51 | } 52 | out_file << part_value.body; 53 | out_file.close(); 54 | CROW_LOG_INFO << " Contents written to " << outfile_name << '\n'; 55 | } 56 | else 57 | { 58 | CROW_LOG_DEBUG << " Value: " << part_value.body << '\n'; 59 | } 60 | } 61 | return crow::response(200); 62 | }); 63 | 64 | // enables all log 65 | app.loglevel(crow::LogLevel::Debug); 66 | 67 | app.port(18080) 68 | .multithreaded() 69 | .run(); 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /examples/example_json_map.cpp: -------------------------------------------------------------------------------- 1 | #define CROW_JSON_USE_MAP 2 | #include "crow.h" 3 | 4 | int main() 5 | { 6 | crow::SimpleApp app; 7 | 8 | // simple json response using a map 9 | // To see it in action enter {ip}:18080/json 10 | // it shoud show amessage before zmessage despite adding zmessage first. 11 | CROW_ROUTE(app, "/json") 12 | ([] { 13 | crow::json::wvalue x({{"zmessage", "Hello, World!"}, 14 | {"amessage", "Hello, World2!"}}); 15 | return x; 16 | }); 17 | 18 | app.port(18080) 19 | .multithreaded() 20 | .run(); 21 | } 22 | -------------------------------------------------------------------------------- /examples/example_middleware.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | 3 | struct RequestLogger 4 | { 5 | struct context 6 | {}; 7 | 8 | // This method is run before handling the request 9 | void before_handle(crow::request& req, crow::response& /*res*/, context& /*ctx*/) 10 | { 11 | CROW_LOG_INFO << "Request to:" + req.url; 12 | } 13 | 14 | // This method is run after handling the request 15 | void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/) 16 | {} 17 | }; 18 | 19 | // Per handler middleware has to extend ILocalMiddleware 20 | // It is called only if enabled 21 | struct SecretContentGuard : crow::ILocalMiddleware 22 | { 23 | struct context 24 | {}; 25 | 26 | void before_handle(crow::request& /*req*/, crow::response& res, context& /*ctx*/) 27 | { 28 | // A request can be aborted prematurely 29 | res.write("SECRET!"); 30 | res.code = 403; 31 | res.end(); 32 | } 33 | 34 | void after_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/) 35 | {} 36 | }; 37 | 38 | struct RequestAppend : crow::ILocalMiddleware 39 | { 40 | // Values from this context can be accessed from handlers 41 | struct context 42 | { 43 | std::string message; 44 | }; 45 | 46 | void before_handle(crow::request& /*req*/, crow::response& /*res*/, context& /*ctx*/) 47 | {} 48 | 49 | void after_handle(crow::request& /*req*/, crow::response& res, context& ctx) 50 | { 51 | // The response can be modified 52 | res.write(" + (" + ctx.message + ")"); 53 | } 54 | }; 55 | 56 | int main() 57 | { 58 | // ALL middleware (including per handler) is listed 59 | crow::App app; 60 | 61 | CROW_ROUTE(app, "/") 62 | ([]() { 63 | return "Hello, world!"; 64 | }); 65 | 66 | CROW_ROUTE(app, "/secret") 67 | // Enable SecretContentGuard for this handler 68 | .CROW_MIDDLEWARES(app, SecretContentGuard)([]() { 69 | return ""; 70 | }); 71 | 72 | crow::Blueprint bp("bp", "c", "c"); 73 | // Register middleware on all routes on a specific blueprint 74 | // This also applies to sub blueprints 75 | bp.CROW_MIDDLEWARES(app, RequestAppend); 76 | 77 | CROW_BP_ROUTE(bp, "/") 78 | ([&](const crow::request& req) { 79 | // Get RequestAppends context 80 | auto& ctx = app.get_context(req); 81 | ctx.message = "World"; 82 | return "Hello:"; 83 | }); 84 | app.register_blueprint(bp); 85 | 86 | app.port(18080).run(); 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /examples/example_static_file.cpp: -------------------------------------------------------------------------------- 1 | //#define CROW_STATIC_DIRECTORY "alternative_directory/" 2 | //#define CROW_STATIC_ENDPOINT "/alternative_endpoint/" 3 | //#define CROW_DISABLE_STATIC_DIR 4 | #include "crow.h" 5 | 6 | int main() 7 | { 8 | 9 | //Crow app initialization 10 | crow::SimpleApp app; 11 | 12 | // 13 | CROW_ROUTE(app, "/") 14 | ([](const crow::request&, crow::response& res) { 15 | //replace cat.jpg with your file path 16 | res.set_static_file_info("cat.jpg"); 17 | res.end(); 18 | }); 19 | 20 | 21 | app.port(18080).run(); 22 | 23 | 24 | return 0; 25 | } 26 | 27 | /// You can also use the `/static` directory and endpoint (the directory needs to have the same path as your executable). 28 | /// Any file inside the static directory will have the same path it would in your filesystem. 29 | 30 | /// TO change the static directory or endpoint, use the macros above (replace `alternative_directory` and/or `alternative_endpoint` with your own) 31 | -------------------------------------------------------------------------------- /examples/example_vs.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | 3 | class ExampleLogHandler : public crow::ILogHandler 4 | { 5 | public: 6 | void log(std::string message, crow::LogLevel level) override 7 | { 8 | // cerr << "ExampleLogHandler -> " << message; 9 | } 10 | }; 11 | 12 | struct ExampleMiddleware 13 | { 14 | std::string message; 15 | 16 | ExampleMiddleware(): 17 | message("foo") 18 | { 19 | } 20 | 21 | void setMessage(const std::string& newMsg) 22 | { 23 | message = newMsg; 24 | } 25 | 26 | struct context 27 | {}; 28 | 29 | void before_handle(crow::request& req, crow::response& res, context& ctx) 30 | { 31 | CROW_LOG_DEBUG << " - MESSAGE: " << message; 32 | } 33 | 34 | void after_handle(crow::request& req, crow::response& res, context& ctx) 35 | { 36 | // no-op 37 | } 38 | }; 39 | 40 | int main() 41 | { 42 | crow::App app; 43 | 44 | app.get_middleware().setMessage("hello"); 45 | 46 | CROW_ROUTE(app, "/") 47 | .name("hello")([] { 48 | return "Hello World!"; 49 | }); 50 | 51 | CROW_ROUTE(app, "/about") 52 | ([]() { 53 | return "About Crow example."; 54 | }); 55 | 56 | // a request to /path should be forwarded to /path/ 57 | CROW_ROUTE(app, "/path/") 58 | ([]() { 59 | return "Trailing slash test case.."; 60 | }); 61 | 62 | // simple json response 63 | CROW_ROUTE(app, "/json") 64 | ([] { 65 | crow::json::wvalue x; 66 | x["message"] = "Hello, World!"; 67 | return x; 68 | }); 69 | 70 | CROW_ROUTE(app, "/hello/") 71 | ([](int count) { 72 | if (count > 100) 73 | return crow::response(400); 74 | std::ostringstream os; 75 | os << count << " bottles of beer!"; 76 | return crow::response(os.str()); 77 | }); 78 | 79 | CROW_ROUTE(app, "/add//") 80 | ([](crow::response& res, int a, int b) { 81 | std::ostringstream os; 82 | os << a + b; 83 | res.write(os.str()); 84 | res.end(); 85 | }); 86 | 87 | // Compile error with message "Handler type is mismatched with URL paramters" 88 | //CROW_ROUTE(app,"/another/") 89 | //([](int a, int b){ 90 | //return crow::response(500); 91 | //}); 92 | 93 | // more json example 94 | CROW_ROUTE(app, "/add_json") 95 | .methods(crow::HTTPMethod::Post)([](const crow::request& req) { 96 | auto x = crow::json::load(req.body); 97 | if (!x) 98 | return crow::response(400); 99 | auto sum = x["a"].i() + x["b"].i(); 100 | std::ostringstream os; 101 | os << sum; 102 | return crow::response{os.str()}; 103 | }); 104 | 105 | app.route_dynamic("/params")([](const crow::request& req) { 106 | std::ostringstream os; 107 | os << "Params: " << req.url_params << "\n\n"; 108 | os << "The key 'foo' was " << (req.url_params.get("foo") == nullptr ? "not " : "") << "found.\n"; 109 | if (req.url_params.get("pew") != nullptr) 110 | { 111 | double countD = crow::utility::lexical_cast(req.url_params.get("pew")); 112 | os << "The value of 'pew' is " << countD << '\n'; 113 | } 114 | auto count = req.url_params.get_list("count"); 115 | os << "The key 'count' contains " << count.size() << " value(s).\n"; 116 | for (const auto& countVal : count) 117 | { 118 | os << " - " << countVal << '\n'; 119 | } 120 | return crow::response{os.str()}; 121 | }); 122 | 123 | // ignore all log 124 | crow::logger::setLogLevel(crow::LogLevel::Debug); 125 | //crow::logger::setHandler(std::make_shared()); 126 | 127 | app.port(18080) 128 | .multithreaded() 129 | .run(); 130 | } 131 | -------------------------------------------------------------------------------- /examples/helloworld.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | 3 | int main() 4 | { 5 | crow::SimpleApp app; 6 | 7 | CROW_ROUTE(app, "/") 8 | ([]() { 9 | return "Hello, world!"; 10 | }); 11 | 12 | app.port(18080).run(); 13 | } 14 | -------------------------------------------------------------------------------- /examples/middlewares/example_cookies.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | #include "crow/middlewares/cookie_parser.h" 3 | 4 | int main() 5 | { 6 | // Include CookieParser middleware 7 | crow::App app; 8 | 9 | CROW_ROUTE(app, "/read") 10 | ([&](const crow::request& req) { 11 | auto& ctx = app.get_context(req); 12 | // Read cookies with get_cookie 13 | auto value = ctx.get_cookie("key"); 14 | return "value: " + value; 15 | }); 16 | 17 | CROW_ROUTE(app, "/write") 18 | ([&](const crow::request& req) { 19 | auto& ctx = app.get_context(req); 20 | // Store cookies with set_cookie 21 | ctx.set_cookie("key", "word") 22 | // configure additional parameters 23 | .path("/") 24 | .max_age(120) 25 | .httponly(); 26 | return "ok!"; 27 | }); 28 | 29 | app.port(18080).run(); 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /examples/middlewares/example_cors.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | #include "crow/middlewares/cors.h" 3 | 4 | // Warning! 5 | // If you want to use CORS with OPTIONS cache on browser requests, 6 | // be sure to specify each headers you use, please do not use "*" 7 | // else otherwise the browser will ignore you 8 | // Example: 9 | // .headers("Origin", "Content-Type", "Accept", *Your-Headers*) 10 | // .max_age(5); 11 | 12 | int main() 13 | { 14 | // Enable CORS 15 | crow::App app; 16 | 17 | // Customize CORS 18 | auto& cors = app.get_middleware(); 19 | 20 | // clang-format off 21 | cors 22 | .global() 23 | .headers("X-Custom-Header", "Upgrade-Insecure-Requests") 24 | .methods("POST"_method, "GET"_method) 25 | .prefix("/cors") 26 | .origin("example.com") 27 | .prefix("/nocors") 28 | .ignore(); 29 | // clang-format on 30 | 31 | CROW_ROUTE(app, "/") 32 | ([]() { 33 | return "Check Access-Control-Allow-Methods header"; 34 | }); 35 | 36 | CROW_ROUTE(app, "/cors") 37 | ([]() { 38 | return "Check Access-Control-Allow-Origin header"; 39 | }); 40 | 41 | app.port(18080).run(); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /examples/middlewares/example_session.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | #include "crow/middlewares/session.h" 3 | 4 | crow::response redirect() 5 | { 6 | crow::response res; 7 | res.redirect("/"); 8 | return res; 9 | } 10 | 11 | int main() 12 | { 13 | // Choose a storage kind for: 14 | // - InMemoryStore stores all entries in memory 15 | // - FileStore stores all entries in json files 16 | using Session = crow::SessionMiddleware; 17 | 18 | // Writing your own store is easy 19 | // Check out the existing ones for guidelines 20 | 21 | // Make sure the CookieParser is registered before the Session 22 | crow::App app{Session{ 23 | // customize cookies 24 | crow::CookieParser::Cookie("session").max_age(/*one day*/ 24 * 60 * 60).path("/"), 25 | // set session id length (small value only for demonstration purposes) 26 | 4, 27 | // init the store 28 | crow::InMemoryStore{}}}; 29 | 30 | // List all values 31 | CROW_ROUTE(app, "/") 32 | ([&](const crow::request& req) { 33 | // get session as middleware context 34 | auto& session = app.get_context(req); 35 | // the session acts as a multi-type map 36 | // that can store string, integers, doubles and bools 37 | // besides get/set/remove it also supports more advanced locking operations 38 | 39 | // Atomically increase number of views 40 | // This will not skip a view even on multithreaded applications 41 | // with multiple concurrent requests from a client 42 | // if "views" doesn't exist, it'll be default initialized 43 | session.apply("views", [](int v) { 44 | return v + 1; 45 | }); 46 | 47 | // get all currently present keys 48 | auto keys = session.keys(); 49 | 50 | std::string out; 51 | for (const auto& key : keys) 52 | // .string(key) converts a value of any type to a string 53 | out += "

" + key + " = " + session.string(key) + "

"; 54 | return out; 55 | }); 56 | 57 | // Get a key 58 | CROW_ROUTE(app, "/get") 59 | ([&](const crow::request& req) { 60 | auto& session = app.get_context(req); 61 | auto key = req.url_params.get("key"); 62 | 63 | // get a string 64 | // return "_NOT_FOUND_" if value is not found or of another type 65 | std::string string_v = session.get(key, "_NOT_FOUND_"); 66 | // alternatively one can use 67 | // session.get(key) 68 | // where the fallback is an empty value "" 69 | (void)string_v; 70 | 71 | // get int 72 | // because supporting multiple integer types in a type bound map would be cumbersome, 73 | // all integral values (except uint64_t) are promoted to int64_t 74 | // that is why get, get, get are all accessing the same type 75 | int int_v = session.get(key, -1); 76 | (void)int_v; 77 | 78 | return session.string(key); 79 | }); 80 | 81 | // Set a key 82 | // A session is stored as soon as it becomes non empty 83 | CROW_ROUTE(app, "/set") 84 | ([&](const crow::request& req) { 85 | auto& session = app.get_context(req); 86 | 87 | auto key = req.url_params.get("key"); 88 | auto value = req.url_params.get("value"); 89 | 90 | session.set(key, value); 91 | 92 | return redirect(); 93 | }); 94 | 95 | // Remove a key 96 | CROW_ROUTE(app, "/remove") 97 | ([&](const crow::request& req) { 98 | auto& session = app.get_context(req); 99 | auto key = req.url_params.get("key"); 100 | session.remove(key); 101 | 102 | return redirect(); 103 | }); 104 | 105 | // Manually lock a session for synchronization in parallel requests 106 | CROW_ROUTE(app, "/lock") 107 | ([&](const crow::request& req) { 108 | auto& session = app.get_context(req); 109 | 110 | std::lock_guard l(session.mutex()); 111 | 112 | if (session.get("views", 0) % 2 == 0) 113 | { 114 | session.set("even", true); 115 | } 116 | else 117 | { 118 | session.remove("even"); 119 | } 120 | 121 | return redirect(); 122 | }); 123 | 124 | app.port(18080) 125 | //.multithreaded() 126 | .run(); 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /examples/ssl/example_ssl.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | 3 | int main() 4 | { 5 | crow::SimpleApp app; 6 | 7 | CROW_ROUTE(app, "/") 8 | ([]() { 9 | return "Hello world!"; 10 | }); 11 | 12 | app.port(18080).ssl_file("test.crt", "test.key").run(); 13 | 14 | // Use .pem file 15 | //app.port(18080).ssl_file("test.pem").run(); 16 | 17 | // Use custom context; see asio::ssl::context 18 | /* 19 | * crow::ssl_context_t ctx; 20 | * ctx.set_verify_mode(...) 21 | * 22 | * ... configuring ctx 23 | * 24 | * app.port(18080).ssl(ctx).run(); 25 | */ 26 | } 27 | -------------------------------------------------------------------------------- /examples/websocket/example_ws.cpp: -------------------------------------------------------------------------------- 1 | #include "crow.h" 2 | #include 3 | #include 4 | 5 | 6 | int main() 7 | { 8 | crow::SimpleApp app; 9 | 10 | std::mutex mtx; 11 | std::unordered_set users; 12 | 13 | CROW_WEBSOCKET_ROUTE(app, "/ws") 14 | .onopen([&](crow::websocket::connection& conn) { 15 | CROW_LOG_INFO << "new websocket connection from " << conn.get_remote_ip(); 16 | std::lock_guard _(mtx); 17 | users.insert(&conn); 18 | }) 19 | .onclose([&](crow::websocket::connection& conn, const std::string& reason, uint16_t) { 20 | CROW_LOG_INFO << "websocket connection closed: " << reason; 21 | std::lock_guard _(mtx); 22 | users.erase(&conn); 23 | }) 24 | .onmessage([&](crow::websocket::connection& /*conn*/, const std::string& data, bool is_binary) { 25 | std::lock_guard _(mtx); 26 | for (auto u : users) 27 | if (is_binary) 28 | u->send_binary(data); 29 | else 30 | u->send_text(data); 31 | }); 32 | 33 | CROW_ROUTE(app, "/") 34 | ([] { 35 | char name[256]; 36 | gethostname(name, 256); 37 | crow::mustache::context x; 38 | x["servername"] = name; 39 | 40 | auto page = crow::mustache::load("ws.html"); 41 | return page.render(x); 42 | }); 43 | 44 | app.port(40080) 45 | .multithreaded() 46 | .run(); 47 | } 48 | -------------------------------------------------------------------------------- /examples/websocket/templates/ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
11 | 13 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /include/crow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "crow/query_string.h" 3 | #include "crow/http_parser_merged.h" 4 | #include "crow/ci_map.h" 5 | #include "crow/TinySHA1.hpp" 6 | #include "crow/settings.h" 7 | #include "crow/socket_adaptors.h" 8 | #include "crow/json.h" 9 | #include "crow/mustache.h" 10 | #include "crow/logging.h" 11 | #include "crow/task_timer.h" 12 | #include "crow/utility.h" 13 | #include "crow/common.h" 14 | #include "crow/http_request.h" 15 | #include "crow/websocket.h" 16 | #include "crow/parser.h" 17 | #include "crow/http_response.h" 18 | #include "crow/multipart.h" 19 | #include "crow/multipart_view.h" 20 | #include "crow/routing.h" 21 | #include "crow/middleware.h" 22 | #include "crow/middleware_context.h" 23 | #include "crow/compression.h" 24 | #include "crow/http_connection.h" 25 | #include "crow/http_server.h" 26 | #include "crow/app.h" 27 | -------------------------------------------------------------------------------- /include/crow/ci_map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "crow/utility.h" 8 | 9 | namespace crow 10 | { 11 | /// Hashing function for ci_map (unordered_multimap). 12 | struct ci_hash 13 | { 14 | size_t operator()(const std::string_view key) const 15 | { 16 | std::size_t seed = 0; 17 | std::locale locale; 18 | 19 | for (auto c : key) 20 | hash_combine(seed, std::toupper(c, locale)); 21 | 22 | return seed; 23 | } 24 | 25 | private: 26 | static inline void hash_combine(std::size_t& seed, char v) 27 | { 28 | std::hash hasher; 29 | seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); 30 | } 31 | }; 32 | 33 | /// Equals function for ci_map (unordered_multimap). 34 | struct ci_key_eq 35 | { 36 | bool operator()(const std::string_view l, const std::string_view r) const 37 | { 38 | return utility::string_equals(l, r); 39 | } 40 | }; 41 | 42 | using ci_map = std::unordered_multimap; 43 | } // namespace crow 44 | -------------------------------------------------------------------------------- /include/crow/compression.h: -------------------------------------------------------------------------------- 1 | #ifdef CROW_ENABLE_COMPRESSION 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | // http://zlib.net/manual.html 8 | namespace crow // NOTE: Already documented in "crow/app.h" 9 | { 10 | namespace compression 11 | { 12 | // Values used in the 'windowBits' parameter for deflateInit2. 13 | enum algorithm 14 | { 15 | // 15 is the default value for deflate 16 | DEFLATE = 15, 17 | // windowBits can also be greater than 15 for optional gzip encoding. 18 | // Add 16 to windowBits to write a simple gzip header and trailer around the compressed data instead of a zlib wrapper. 19 | GZIP = 15 | 16, 20 | }; 21 | 22 | inline std::string compress_string(std::string const& str, algorithm algo) 23 | { 24 | std::string compressed_str; 25 | z_stream stream{}; 26 | // Initialize with the default values 27 | if (::deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, algo, 8, Z_DEFAULT_STRATEGY) == Z_OK) 28 | { 29 | char buffer[8192]; 30 | 31 | stream.avail_in = str.size(); 32 | // zlib does not take a const pointer. The data is not altered. 33 | stream.next_in = const_cast(reinterpret_cast(str.c_str())); 34 | 35 | int code = Z_OK; 36 | do 37 | { 38 | stream.avail_out = sizeof(buffer); 39 | stream.next_out = reinterpret_cast(&buffer[0]); 40 | 41 | code = ::deflate(&stream, Z_FINISH); 42 | // Successful and non-fatal error code returned by deflate when used with Z_FINISH flush 43 | if (code == Z_OK || code == Z_STREAM_END) 44 | { 45 | std::copy(&buffer[0], &buffer[sizeof(buffer) - stream.avail_out], std::back_inserter(compressed_str)); 46 | } 47 | 48 | } while (code == Z_OK); 49 | 50 | if (code != Z_STREAM_END) 51 | compressed_str.clear(); 52 | 53 | ::deflateEnd(&stream); 54 | } 55 | 56 | return compressed_str; 57 | } 58 | 59 | inline std::string decompress_string(std::string const& deflated_string) 60 | { 61 | std::string inflated_string; 62 | Bytef tmp[8192]; 63 | 64 | z_stream zstream{}; 65 | zstream.avail_in = deflated_string.size(); 66 | // Nasty const_cast but zlib won't alter its contents 67 | zstream.next_in = const_cast(reinterpret_cast(deflated_string.c_str())); 68 | // Initialize with automatic header detection, for gzip support 69 | if (::inflateInit2(&zstream, MAX_WBITS | 32) == Z_OK) 70 | { 71 | do 72 | { 73 | zstream.avail_out = sizeof(tmp); 74 | zstream.next_out = &tmp[0]; 75 | 76 | auto ret = ::inflate(&zstream, Z_NO_FLUSH); 77 | if (ret == Z_OK || ret == Z_STREAM_END) 78 | { 79 | std::copy(&tmp[0], &tmp[sizeof(tmp) - zstream.avail_out], std::back_inserter(inflated_string)); 80 | } 81 | else 82 | { 83 | // Something went wrong with inflate; make sure we return an empty string 84 | inflated_string.clear(); 85 | break; 86 | } 87 | 88 | } while (zstream.avail_out == 0); 89 | 90 | // Free zlib's internal memory 91 | ::inflateEnd(&zstream); 92 | } 93 | 94 | return inflated_string; 95 | } 96 | } // namespace compression 97 | } // namespace crow 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /include/crow/exceptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace crow 5 | { 6 | struct bad_request : public std::runtime_error 7 | { 8 | bad_request(const std::string& what_arg) 9 | : std::runtime_error(what_arg) {} 10 | 11 | bad_request(const char* what_arg) 12 | : std::runtime_error(what_arg) {} 13 | }; 14 | } -------------------------------------------------------------------------------- /include/crow/http_request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef CROW_USE_BOOST 4 | #include 5 | #else 6 | #ifndef ASIO_STANDALONE 7 | #define ASIO_STANDALONE 8 | #endif 9 | #include 10 | #endif 11 | 12 | #include "crow/common.h" 13 | #include "crow/ci_map.h" 14 | #include "crow/query_string.h" 15 | 16 | namespace crow // NOTE: Already documented in "crow/app.h" 17 | { 18 | #ifdef CROW_USE_BOOST 19 | namespace asio = boost::asio; 20 | #endif 21 | 22 | /// Find and return the value associated with the key. (returns an empty string if nothing is found) 23 | template 24 | inline const std::string& get_header_value(const T& headers, const std::string& key) 25 | { 26 | if (headers.count(key)) 27 | { 28 | return headers.find(key)->second; 29 | } 30 | static std::string empty; 31 | return empty; 32 | } 33 | 34 | /// An HTTP request. 35 | struct request 36 | { 37 | HTTPMethod method; 38 | std::string raw_url; ///< The full URL containing the `?` and URL parameters. 39 | std::string url; ///< The endpoint without any parameters. 40 | query_string url_params; ///< The parameters associated with the request. (everything after the `?` in the URL) 41 | ci_map headers; 42 | std::string body; 43 | std::string remote_ip_address; ///< The IP address from which the request was sent. 44 | unsigned char http_ver_major, http_ver_minor; 45 | bool keep_alive, ///< Whether or not the server should send a `connection: Keep-Alive` header to the client. 46 | close_connection, ///< Whether or not the server should shut down the TCP connection once a response is sent. 47 | upgrade; ///< Whether or noth the server should change the HTTP connection to a different connection. 48 | 49 | void* middleware_context{}; 50 | void* middleware_container{}; 51 | asio::io_context* io_context{}; 52 | 53 | /// Construct an empty request. (sets the method to `GET`) 54 | request(): 55 | method(HTTPMethod::Get) 56 | {} 57 | 58 | /// Construct a request with all values assigned. 59 | 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): 60 | 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) 61 | {} 62 | 63 | void add_header(std::string key, std::string value) 64 | { 65 | headers.emplace(std::move(key), std::move(value)); 66 | } 67 | 68 | const std::string& get_header_value(const std::string& key) const 69 | { 70 | return crow::get_header_value(headers, key); 71 | } 72 | 73 | bool check_version(unsigned char major, unsigned char minor) const 74 | { 75 | return http_ver_major == major && http_ver_minor == minor; 76 | } 77 | 78 | /// Get the body as parameters in QS format. 79 | 80 | /// 81 | /// This is meant to be used with requests of type "application/x-www-form-urlencoded" 82 | const query_string get_body_params() const 83 | { 84 | return query_string(body, false); 85 | } 86 | 87 | /// Send data to whoever made this request with a completion handler and return immediately. 88 | template 89 | void post(CompletionHandler handler) 90 | { 91 | asio::post(io_context, handler); 92 | } 93 | 94 | /// Send data to whoever made this request with a completion handler. 95 | template 96 | void dispatch(CompletionHandler handler) 97 | { 98 | asio::dispatch(io_context, handler); 99 | } 100 | }; 101 | } // namespace crow 102 | -------------------------------------------------------------------------------- /include/crow/middleware_context.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "crow/utility.h" 4 | #include "crow/http_request.h" 5 | #include "crow/http_response.h" 6 | 7 | namespace crow 8 | { 9 | namespace detail 10 | { 11 | 12 | 13 | template 14 | struct partial_context : public black_magic::pop_back::template rebind, public black_magic::last_element_type::type::context 15 | { 16 | using parent_context = typename black_magic::pop_back::template rebind<::crow::detail::partial_context>; 17 | template 18 | using partial = typename std::conditional>::type; 19 | 20 | template 21 | typename T::context& get() 22 | { 23 | return static_cast(*this); 24 | } 25 | }; 26 | 27 | 28 | 29 | template<> 30 | struct partial_context<> 31 | { 32 | template 33 | using partial = partial_context; 34 | }; 35 | 36 | 37 | template 38 | struct context : private partial_context 39 | //struct context : private Middlewares::context... // simple but less type-safe 40 | { 41 | template 42 | friend typename std::enable_if<(N == 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res); 43 | template 44 | friend typename std::enable_if<(N > 0)>::type after_handlers_call_helper(const CallCriteria& cc, Container& middlewares, Context& ctx, request& req, response& res); 45 | 46 | template 47 | friend typename std::enable_if<(N < std::tuple_size::type>::value), bool>::type 48 | middleware_call_helper(const CallCriteria& cc, Container& middlewares, request& req, response& res, Context& ctx); 49 | 50 | template 51 | typename T::context& get() 52 | { 53 | return static_cast(*this); 54 | } 55 | 56 | template 57 | using partial = typename partial_context::template partial; 58 | }; 59 | } // namespace detail 60 | } // namespace crow 61 | -------------------------------------------------------------------------------- /include/crow/middlewares/utf-8.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "crow/http_request.h" 3 | #include "crow/http_response.h" 4 | 5 | namespace crow 6 | { 7 | 8 | struct UTF8 9 | { 10 | struct context 11 | {}; 12 | 13 | void before_handle(request& /*req*/, response& /*res*/, context& /*ctx*/) 14 | {} 15 | 16 | void after_handle(request& /*req*/, response& res, context& /*ctx*/) 17 | { 18 | if (get_header_value(res.headers, "Content-Type").empty()) 19 | { 20 | res.set_header("Content-Type", "text/plain; charset=utf-8"); 21 | } 22 | } 23 | }; 24 | 25 | } // namespace crow 26 | -------------------------------------------------------------------------------- /include/crow/returnable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace crow 6 | { 7 | /// An abstract class that allows any other class to be returned by a handler. 8 | struct returnable 9 | { 10 | std::string content_type; 11 | virtual std::string dump() const = 0; 12 | 13 | returnable(std::string ctype): 14 | content_type{ctype} 15 | {} 16 | 17 | virtual ~returnable(){}; 18 | }; 19 | } // namespace crow 20 | -------------------------------------------------------------------------------- /include/crow/settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // settings for crow 3 | // TODO(ipkn) replace with runtime config. libucl? 4 | 5 | /* #ifdef - enables debug mode */ 6 | //#define CROW_ENABLE_DEBUG 7 | 8 | /* #ifdef - enables logging */ 9 | #define CROW_ENABLE_LOGGING 10 | 11 | /* #ifdef - enforces section 5.2 and 6.1 of RFC6455 (only accepting masked messages from clients) */ 12 | //#define CROW_ENFORCE_WS_SPEC 13 | 14 | /* #define - specifies log level */ 15 | /* 16 | Debug = 0 17 | Info = 1 18 | Warning = 2 19 | Error = 3 20 | Critical = 4 21 | 22 | default to INFO 23 | */ 24 | #ifndef CROW_LOG_LEVEL 25 | #define CROW_LOG_LEVEL 1 26 | #endif 27 | 28 | #ifndef CROW_STATIC_DIRECTORY 29 | #define CROW_STATIC_DIRECTORY "static/" 30 | #endif 31 | #ifndef CROW_STATIC_ENDPOINT 32 | #define CROW_STATIC_ENDPOINT "/static/" 33 | #endif 34 | 35 | // compiler flags 36 | 37 | #if defined(_MSC_VER) 38 | #if _MSC_VER < 1900 39 | #define CROW_MSVC_WORKAROUND 40 | #define constexpr const 41 | #define noexcept throw() 42 | #endif 43 | #endif 44 | -------------------------------------------------------------------------------- /include/crow/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace crow 4 | { 5 | constexpr const char VERSION[] = "master"; 6 | } 7 | -------------------------------------------------------------------------------- /logo55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/logo55.png -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Crow 2 | 3 | # Repository 4 | repo_name: CrowCpp/Crow 5 | repo_url: https://github.com/CrowCpp/Crow 6 | site_url: https://crowcpp.org 7 | edit_uri: "" 8 | 9 | theme: 10 | name: material 11 | font: false 12 | language: 'en' 13 | features: 14 | - navigation.tabs 15 | - content.code.annotate 16 | favicon: 'assets/favicon.svg' 17 | logo: 'assets/favicon.svg' 18 | icon: 19 | repo: fontawesome/brands/square-github 20 | static_templates: 21 | - privacy_policy.html 22 | palette: 23 | - media: "(prefers-color-scheme: light)" 24 | scheme: crow-light 25 | toggle: 26 | icon: fontawesome/solid/sun 27 | name: Using Light Theme 28 | - media: "(prefers-color-scheme: dark)" 29 | scheme: crow-dark 30 | toggle: 31 | icon: fontawesome/solid/moon 32 | name: Using Dark Theme 33 | custom_dir: docs/overrides 34 | 35 | markdown_extensions: 36 | - toc: 37 | permalink: true 38 | - admonition 39 | - pymdownx.highlight 40 | - pymdownx.tabbed 41 | - pymdownx.superfences 42 | - pymdownx.inlinehilite 43 | - pymdownx.keys 44 | - attr_list 45 | - pymdownx.emoji: 46 | emoji_index: !!python/name:material.extensions.emoji.twemoji 47 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 48 | 49 | 50 | nav: 51 | - Home: index.md 52 | - Getting Started: 53 | - Setup: 54 | - Linux: getting_started/setup/linux.md 55 | - MacOS: getting_started/setup/macos.md 56 | - Windows: getting_started/setup/windows.md 57 | - Project templates and applications: getting_started/project_templates_and_applications.md 58 | - Your First Application: getting_started/your_first_application.md 59 | - A Simple Webpage: getting_started/a_simple_webpage.md 60 | - Guides: 61 | - Different parts of Crow: 62 | - App: guides/app.md 63 | - Routes: guides/routes.md 64 | - Logging: guides/logging.md 65 | - JSON: guides/json.md 66 | - Templating (Mustache): guides/templating.md 67 | - Multipart: guides/multipart.md 68 | - Query Strings: guides/query-string.md 69 | - Middleware: guides/middleware.md 70 | - SSL: guides/ssl.md 71 | - Static Files: guides/static.md 72 | - Blueprints: guides/blueprints.md 73 | - Compression: guides/compression.md 74 | - Websockets: guides/websockets.md 75 | - Base64: guides/base64.md 76 | - Writing Tests: guides/testing.md 77 | - Using Crow: 78 | - HTTP Authorization: guides/auth.md 79 | - Included Middlewares: guides/included-middleware.md 80 | - Server setup: 81 | - Proxies: guides/proxies.md 82 | - Systemd run on startup: guides/syste.md 83 | - API Reference: 84 | - API Reference: 'reference/index.html' 85 | 86 | extra: 87 | version: 88 | provider: mike 89 | default: latest 90 | analytics: 91 | provider: matomo 92 | id: 1 93 | link: //thee.dev/matomo/ 94 | social: 95 | - icon: fontawesome/brands/github 96 | link: https://github.com/crowcpp/crow 97 | - icon: fontawesome/brands/gitter 98 | link: https://gitter.im/crowfork/community 99 | 100 | plugins: 101 | - redirects: 102 | redirect_maps: 103 | 'getting_started/setup.md': 'getting_started/setup/linux.md' 104 | - meta-descriptions 105 | - search 106 | 107 | extra_css: 108 | - 'stylesheets/colors.css' 109 | - 'stylesheets/latofonts.css' 110 | - 'stylesheets/extra.css' 111 | 112 | copyright: 'Copyright © 2020-2023 CrowCpp' 113 | -------------------------------------------------------------------------------- /scripts/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: The-EDev 2 | pkgname=crow 3 | pkgver=master 4 | pkgrel=1 5 | pkgdesc="A Fast and Easy to use C++ microframework for the web." 6 | arch=(any) 7 | url="https://crowcpp.org" 8 | license=('custom:BSD-3-Clause') 9 | depends=('asio') 10 | optdepends=('openssl: HTTPS support' 'zlib: HTTP compression support' 'cmake: Choose this if you plan on using CMake for your Crow project') 11 | conflicts=("$pkgname-git") 12 | changelog='changelog.md' 13 | source=("https://github.com/CrowCpp/$pkgname/releases/download/v$pkgver/crow-v$pkgver.tar.gz") 14 | md5sums=('SKIP') 15 | sha256sums=('SKIP') 16 | 17 | package() { 18 | echo "installing to \"$pkgdir/usr/local/\"" 19 | mkdir -p "$pkgdir/usr/local/include" 20 | mkdir -p "$pkgdir/usr/local/lib" 21 | cp -r "include" "$pkgdir/usr/local" 22 | cp -r "lib" "$pkgdir/usr/local" 23 | } 24 | -------------------------------------------------------------------------------- /scripts/merge_all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Merges all the header files.""" 4 | from glob import glob 5 | from os import path as pt, name as osname 6 | from os.path import sep 7 | import re 8 | from collections import defaultdict 9 | import sys, getopt 10 | 11 | if len(sys.argv) < 3: 12 | print("Usage: {} (-i(include) OR -e(exclude) item1,item2...)".format(sys.argv[0])) 13 | print("Available middlewares are in `include/crow/middlewares`. Do NOT type the `.h` when including or excluding") 14 | sys.exit(1) 15 | 16 | header_path = sys.argv[1] 17 | output_path = sys.argv[2] 18 | 19 | middlewares = [x.rsplit(sep, 1)[-1][:-2] for x in glob(pt.join(header_path, ('crow'+sep+'middlewares'+sep+'*.h*')))] 20 | 21 | with open(header_path+'/../LICENSE', 'r') as file: 22 | lsc = '/*' + file.read() + '*/' 23 | 24 | middlewares_actual = [] 25 | if len(sys.argv) > 3: 26 | opts, args = getopt.getopt(sys.argv[3:],"i:e:",["include=","exclude="]) 27 | if (len(opts) > 1): 28 | print("Error:Cannot simultaneously include and exclude middlewares.") 29 | sys.exit(1) 30 | if (opts[0][0] in ("-i", "--include")): 31 | middleware_list = opts[0][1].split(',') 32 | for item in middlewares: 33 | if (item in middleware_list): 34 | middlewares_actual.append(item) 35 | elif (opts[0][0] in ("-e", "--exclude")): 36 | middleware_list = opts[0][1].split(',') 37 | for item in middlewares: 38 | if (item not in middleware_list): 39 | middlewares_actual.append(item) 40 | else: 41 | print("ERROR:Unknown argument " + opts[0][0]) 42 | sys.exit(1) 43 | else: 44 | middlewares_actual = middlewares 45 | print("Middlewares: " + str(middlewares_actual)) 46 | 47 | re_depends = re.compile('^#include \"(.*)\"\n', re.MULTILINE) 48 | re_pragma = re.compile('^(.*)#pragma once(.*)\n', re.MULTILINE) 49 | headers = [x.rsplit(sep, 1)[-1] for x in glob(pt.join(header_path, '*.h*'))] 50 | headers += ['crow'+sep + x.rsplit(sep, 1)[-1] for x in glob(pt.join(header_path, 'crow'+sep+'*.h*'))] 51 | headers += [('crow'+sep+'middlewares'+sep + x + '.h') for x in middlewares_actual] 52 | print(headers) 53 | edges = defaultdict(list) 54 | for header in headers: 55 | d = open(pt.join(header_path, header), encoding='UTF-8').read() 56 | match = re_depends.findall(d) 57 | for m in match: 58 | actual_m = m 59 | if (osname == 'nt'): #Windows 60 | actual_m = m.replace('/', '\\') 61 | # m should included before header 62 | edges[actual_m].append(header) 63 | 64 | visited = defaultdict(bool) 65 | order = [] 66 | 67 | 68 | 69 | def dfs(x): 70 | """Ensure all header files are visited.""" 71 | visited[x] = True 72 | for y in edges[x]: 73 | if not visited[y]: 74 | dfs(y) 75 | order.append(x) 76 | 77 | for header in headers: 78 | if not visited[header]: 79 | dfs(header) 80 | 81 | order = order[::-1] 82 | for x in edges: 83 | print(x, edges[x]) 84 | for x in edges: 85 | for y in edges[x]: 86 | assert order.index(x) < order.index(y), 'cyclic include detected' 87 | 88 | print(order) 89 | spdx_lsc = '// SPDX-License-Identifier: BSD-3-Clause AND ISC AND MIT' 90 | build = [spdx_lsc, lsc, '#pragma once'] 91 | for header in order: 92 | d = open(pt.join(header_path, header), encoding='UTF-8').read() 93 | d_no_depend = re_depends.sub(lambda x: '', d) 94 | d_no_pragma = re_pragma.sub(lambda x: '', d_no_depend) 95 | build.append(d_no_pragma) 96 | #build.append('\n') 97 | 98 | open(output_path, 'w', encoding='UTF-8').write('\n'.join(build)) 99 | -------------------------------------------------------------------------------- /scripts/nginx_mime2cpp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | #get mime.types file from the nginx repository at nginx/conf/mime.types 4 | #typical output filename: mime_types.h 5 | import sys 6 | from datetime import date 7 | 8 | if (len(sys.argv) != 3) and (len(sys.argv) != 2): 9 | print("Usage (local file): {} ".format(sys.argv[0])) 10 | print("(downloads file) : {} ".format(sys.argv[0])) 11 | sys.exit(1) 12 | if len(sys.argv) == 3: 13 | file_path = sys.argv[1] 14 | output_path = sys.argv[2] 15 | elif len(sys.argv) == 2: 16 | import requests 17 | open("mime.types", "wb").write(requests.get("https://hg.nginx.org/nginx/raw-file/tip/conf/mime.types").content) 18 | file_path = "mime.types" 19 | output_path = sys.argv[1] 20 | 21 | tabspace = " " 22 | tabandahalfspace = " " 23 | def main(): 24 | outLines = [] 25 | outLines.append("// This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py on " + date.today().strftime('%Y-%m-%d') + ".") 26 | outLines.extend([ 27 | "#include ", 28 | "#include ", 29 | "", 30 | "namespace crow", 31 | "{", 32 | tabspace + "const std::unordered_map mime_types{"]) 33 | 34 | with open(file_path, "r") as mtfile: 35 | incomplete = "" 36 | incompleteExists = False 37 | for line in mtfile: 38 | if ('{' not in line and '}' not in line): 39 | splitLine = line.split() 40 | if (';' not in line): 41 | incomplete += line 42 | incompleteExists = True 43 | continue 44 | elif (incompleteExists): 45 | splitLine = incomplete.split() 46 | splitLine.extend(line.split()) 47 | incomplete = "" 48 | incompleteExists = False 49 | outLines.extend(mime_line_to_cpp(splitLine)) 50 | 51 | outLines[-1] = outLines[-1][:-1] + "};" 52 | outLines.append("}") 53 | 54 | with open(output_path, "w") as mtcppfile: 55 | mtcppfile.writelines(x + '\n' for x in outLines) 56 | 57 | def mime_line_to_cpp(mlist): 58 | #print("recieved: " + str(mlist)) 59 | stringReturn = [] 60 | for i in range (len(mlist)): 61 | if (mlist[i].endswith(";")): 62 | mlist[i] = mlist[i][:-1] 63 | for i in range (len(mlist)-1, 0, -1): 64 | stringReturn.append(tabandahalfspace + "{\"" + mlist[i] + "\", \"" + mlist[0] + "\"},") 65 | #print("created: " + stringReturn) 66 | return stringReturn 67 | 68 | main() 69 | -------------------------------------------------------------------------------- /scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ##### Different color outputs (to distinguish errors from standard output) ##### 4 | prGreen() { 5 | echo -e "\e[36m-->\e[92m $1\e[00m" 6 | } 7 | prRed() { 8 | echo -e "\e[36m-->\e[91m $1\e[00m" 9 | } 10 | prYellow() { 11 | echo -e "\e[36m-->\e[93m $1\e[00m" 12 | } 13 | 14 | ##### Check whether the script is called properly ##### 15 | if [ $# != 1 ] 16 | then 17 | echo "Usage: $0 " 18 | echo " should be similar to \"1.2+3\" (+3 is optional)" 19 | exit 1; 20 | fi 21 | 22 | ##### Defining the different ways the version is represented ##### 23 | VERSION=$1 24 | VERSION_NO_PATCH=${VERSION/+*/} 25 | VERSION_VCPKG=$(echo $VERSION | tr + .) 26 | 27 | ##### Defining the paths used ##### 28 | SCRIPT_PATH=$(dirname $(realpath $BASH_SOURCE)) 29 | SOURCE_PATH=$SCRIPT_PATH/../include/crow 30 | RELEASE_PATH=$SCRIPT_PATH/../build_release 31 | RELEASE_CODE_PATH=$SCRIPT_PATH/../build_release/_CPack_Packages/Linux 32 | RELEASE_AUR_PATH=$SCRIPT_PATH/../build_release/_CPack_Packages/Linux/AUR 33 | 34 | if [ -d $RELEASE_PATH ] 35 | then 36 | prYellow "Detected existing release directory, overriding..." 37 | rm -rf $RELEASE_PATH 38 | fi 39 | 40 | ##### Create the dir ##### 41 | prGreen "Creating new release directory..." 42 | mkdir $RELEASE_PATH 43 | 44 | ##### Changing "master" version to ##### 45 | cd $SOURCE_PATH 46 | prGreen "Applying new version to Crow server name..." 47 | sed -i "s/char VERSION\\[\\] = \".*\";/char VERSION\\[\\] = \"$VERSION_NO_PATCH\";/g" version.h 48 | prGreen "Applying new version to vcpkg..." 49 | sed -i "s/\"version-string\": \".*\",/\"version\": \"$VERSION_VCPKG\",/g" ../../vcpkg.json 50 | prGreen "Applying new version to PKGBUILD..." 51 | sed -i "s/^pkgver=.*/pkgver=$VERSION/" $SCRIPT_PATH/PKGBUILD 52 | sed -i "s/^pkgrel=.*/pkgrel=1/" $SCRIPT_PATH/PKGBUILD 53 | 54 | ##### Running CMake to compile crow_all and the deb package ##### 55 | cd $RELEASE_PATH 56 | prGreen "Compiling Release..." 57 | cmake -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF -DCROW_AMALGAMATE=ON -DCPACK_PACKAGE_VERSION=$VERSION -DCPACK_PACKAGE_FILE_NAME="crow-$VERSION" .. && make -j4 58 | prGreen "Compiling DEB package..." 59 | cpack -R $VERSION 60 | 61 | ##### Create standalone package from DEB data ##### 62 | prGreen "Compiling Standalone tar package..." 63 | cd $RELEASE_CODE_PATH 64 | tar -czf crow-v$VERSION.tar.gz -C DEB/crow-$VERSION/usr ./ 65 | cp crow-v$VERSION.tar.gz $RELEASE_PATH/crow-v$VERSION.tar.gz 66 | 67 | ##### Getting the AUR package data ##### 68 | prGreen "Cloning AUR package..." 69 | git clone ssh://aur@aur.archlinux.org/crow.git AUR 70 | 71 | ##### Updating the PKGBUILD in build dir, and adding the tarball for checksum calculations ##### 72 | prGreen "Updating AUR package..." 73 | cp crow-v$VERSION.tar.gz AUR/crow-v$VERSION.tar.gz 74 | cp $SCRIPT_PATH/PKGBUILD AUR/PKGBUILD 75 | 76 | ##### Updating Checksums ##### 77 | cd $RELEASE_AUR_PATH 78 | prGreen "Updating AUR package Checksums..." 79 | MD5=$(md5sum crow-v$VERSION.tar.gz | cut -c -32) 80 | SHA256=$(sha256sum crow-v$VERSION.tar.gz | cut -c -64) 81 | sed -i "s/^md5sums=.*/md5sums=(\'$MD5\')/" PKGBUILD 82 | sed -i "s/^sha256sums=.*/sha256sums=(\'$SHA256\')/" PKGBUILD 83 | 84 | ##### Updating SRCINFO file ##### 85 | prGreen "Updating AUR SRCINFO..." 86 | which makepkg &>/dev/null 87 | if [ $? -eq 0 ] 88 | then 89 | makepkg --printsrcinfo > .SRCINFO 90 | else 91 | prRed "makepkg not found, AUR SRCINFO cannot be updated." 92 | fi 93 | 94 | ##### Give instructions on how to upload the finished release ##### 95 | prYellow "Release for Crow-$VERSION was made successfully. To publish the release please do the following: 96 | 1. Commit the code changes to a separate branch (Recommended name is \"$VERSION_NO_PATCH\"). 97 | 2. Create a tag (Github release) from the branch. 98 | 3. Upload the \"crow-$VERSION.deb\", \"crow-v$VERSION.tar.gz\" and \"crow_all.h\" files to the release. 99 | 4. Update the changelog in \"$RELEASE_AUR_PATH\". 100 | 5. push the changes to AUR (using git and only if AUR update ran without errors). 101 | 6. Open issues to update the packages in VCPKG and ConanCenter." 102 | 103 | exit 0 104 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(crow_test) 3 | 4 | set(CMAKE_POLICY_DEFAULT_CMP0077 new) 5 | set(CMAKE_POLICY_WARNING_CMP0126 new) 6 | 7 | include(${CMAKE_SOURCE_DIR}/cmake/compiler_options.cmake) 8 | include(${CMAKE_SOURCE_DIR}/cmake/CPM.cmake) 9 | 10 | 11 | CPMAddPackage(Catch2 12 | VERSION 3.7.0 13 | GITHUB_REPOSITORY catchorg/Catch2 14 | OPTIONS 15 | "CATCH_INSTALL_DOCS Off" 16 | "CATCH_INSTALL_EXTRAS Off" 17 | "CATCH_ENABLE_REPRODUCIBLE_BUILD Off" 18 | ) 19 | 20 | enable_testing() 21 | 22 | set(TEST_SRCS 23 | unittest.cpp 24 | query_string_tests.cpp 25 | ) 26 | 27 | add_executable(unittest ${TEST_SRCS}) 28 | target_link_libraries(unittest Crow::Crow Catch2::Catch2WithMain) 29 | add_warnings_optimizations(unittest) 30 | 31 | if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android") 32 | target_link_libraries(unittest log) 33 | endif() 34 | 35 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 36 | set_target_properties(unittest PROPERTIES COMPILE_FLAGS "--coverage -fprofile-arcs -ftest-coverage") 37 | target_link_libraries(unittest gcov) 38 | endif() 39 | 40 | add_subdirectory(template) 41 | add_subdirectory(multi_file) 42 | add_subdirectory(external_definition) 43 | if(NOT MSVC) 44 | if (CROW_ENABLE_SSL) 45 | add_subdirectory(ssl) 46 | endif() 47 | endif() 48 | add_subdirectory(img) 49 | -------------------------------------------------------------------------------- /tests/external_definition/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(test_external_definition) 2 | 3 | add_executable( 4 | ${PROJECT_NAME} 5 | main.cpp 6 | ) 7 | 8 | target_include_directories( 9 | ${PROJECT_NAME} 10 | PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} 11 | ) 12 | 13 | target_link_libraries( 14 | ${PROJECT_NAME} 15 | PUBLIC Crow::Crow 16 | ) 17 | -------------------------------------------------------------------------------- /tests/external_definition/main.cpp: -------------------------------------------------------------------------------- 1 | // Testing whether crow routes can be defined in an external function. 2 | #include "crow.h" 3 | 4 | void define_endpoints(crow::SimpleApp& app) 5 | { 6 | CROW_ROUTE(app, "/") 7 | ([]() { 8 | return "Hello, world!"; 9 | }); 10 | CROW_WEBSOCKET_ROUTE(app, "/ws") 11 | .onaccept([&](const crow::request&, void**) { 12 | return true; 13 | }) 14 | .onopen([](crow::websocket::connection&) {}) 15 | .onclose([](crow::websocket::connection&, const std::string&, uint16_t) {}); 16 | } 17 | 18 | int main() 19 | { 20 | crow::SimpleApp app; 21 | 22 | define_endpoints(app); 23 | 24 | app.port(18080).run(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/fuzz/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Utilized by OSSFuzz to build the harness(es) for continuous fuzz-testing 2 | # OSSFuzz defines the following environment variables, that this target relies upon: 3 | # CXX, CFLAGS, LIB_FUZZING_ENGINE, OUT 4 | cmake_minimum_required(VERSION 3.14) 5 | 6 | function(define_fuzzer executable_name) 7 | add_executable(${executable_name} ${executable_name}.cpp) 8 | target_link_libraries(${executable_name} PRIVATE Crow $ENV{LIB_FUZZING_ENGINE}) 9 | target_compile_features(${executable_name} PRIVATE cxx_std_17) 10 | 11 | if (DEFINED ENV{OUT}) 12 | install(TARGETS ${executable_name} DESTINATION $ENV{OUT}) 13 | else () 14 | message(WARNING "Cannot install if $OUT is not defined!") 15 | endif () 16 | endfunction() 17 | 18 | add_definitions(-DNDEBUG) # Do not want assertions 19 | 20 | if (DEFINED ENV{CFLAGS}) 21 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}") 22 | endif () 23 | if (DEFINED ENV{CXXFLAGS}) 24 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{CXXFLAGS}") 25 | endif () 26 | 27 | define_fuzzer(template_fuzzer) 28 | define_fuzzer(request_fuzzer) 29 | define_fuzzer(b64_fuzzer) -------------------------------------------------------------------------------- /tests/fuzz/b64_fuzzer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "crow.h" 5 | 6 | class FuzzException : public std::exception 7 | { 8 | virtual const char* what() const throw() 9 | { 10 | return "Base64 decoding error!"; 11 | } 12 | }; 13 | 14 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size) 15 | { 16 | FuzzedDataProvider fdp{data, size}; 17 | 18 | std::string plaintext = fdp.ConsumeRandomLengthString(); 19 | std::string encoded = crow::utility::base64encode(plaintext, plaintext.size()); 20 | std::string decoded = crow::utility::base64decode(encoded, encoded.size()); 21 | 22 | if (plaintext != decoded) 23 | { 24 | throw FuzzException(); 25 | } 26 | return 0; 27 | } -------------------------------------------------------------------------------- /tests/fuzz/build.sh: -------------------------------------------------------------------------------- 1 | cd $SRC/crow 2 | mkdir -p build 3 | cmake -S . -B build -DCROW_BUILD_FUZZER=ON -DCROW_BUILD_EXAMPLES=OFF -DCROW_BUILD_TESTS=OFF && cmake --build build --target install 4 | 5 | # Build the corpora 6 | cd tests/fuzz 7 | zip -q $OUT/template_fuzzer_seed_corpus.zip template_corpus/* 8 | zip -q $OUT/request_fuzzer_seed_corpus.zip html_corpus/* 9 | -------------------------------------------------------------------------------- /tests/fuzz/html_corpus/get.seed: -------------------------------------------------------------------------------- 1 | GET /test/?string_param=exampleString&integer_param=12345 HTTP/1.1 2 | Host: example.com 3 | User-Agent: Fuzzer/1.0 4 | Accept: */* 5 | -------------------------------------------------------------------------------- /tests/fuzz/request_fuzzer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "crow.h" 7 | 8 | constexpr const int SERVER_PORT = 18080; 9 | 10 | /** 11 | * To be run in a separate thread, 12 | * 13 | * Starts up the web-server, configures a dummy route, and serves incoming requests 14 | */ 15 | static void start_web_server() 16 | { 17 | crow::SimpleApp app{}; 18 | 19 | CROW_ROUTE(app, "/test//") 20 | ([](const crow::request& req, std::string a, int b) 21 | { 22 | std::string resp{}; 23 | for (const auto & param : req.get_body_params().keys()) 24 | { 25 | resp += param; 26 | } 27 | return resp; 28 | }); 29 | 30 | crow::logger::setLogLevel(crow::LogLevel::CRITICAL); 31 | app.bindaddr("127.0.0.1") 32 | .port(SERVER_PORT) 33 | .multithreaded() 34 | .run(); 35 | } 36 | 37 | /** 38 | * Called once at fuzzer start-up, initializes the web-server 39 | * @return True, 40 | */ 41 | static bool initialize_web_server() 42 | { 43 | static std::thread ws_th{start_web_server}; 44 | return true; 45 | } 46 | 47 | static int send_request_to_web_server(FuzzedDataProvider &fdp) 48 | { 49 | int rc = -1; 50 | 51 | int sock = socket(AF_INET, SOCK_STREAM, 0); 52 | auto http_msg = fdp.ConsumeRemainingBytesAsString(); 53 | sockaddr_in ws_addr{.sin_family=AF_INET, .sin_port= htons(SERVER_PORT)}; 54 | ws_addr.sin_addr.s_addr = INADDR_ANY; 55 | 56 | if (-1 == sock) 57 | { 58 | goto done; 59 | } 60 | 61 | if (-1 == connect(sock, (struct sockaddr*) &ws_addr, sizeof(ws_addr))) 62 | { 63 | close(sock); 64 | goto done; 65 | } 66 | http_msg.insert(0, "GET / HTTP/1.1\r\n"); 67 | 68 | send(sock, http_msg.c_str(), http_msg.length(), 0); 69 | close(sock); 70 | rc = 0; 71 | done: 72 | return rc; 73 | } 74 | 75 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size) 76 | { 77 | static bool initialized = initialize_web_server(); 78 | FuzzedDataProvider fdp{data, size}; 79 | 80 | send_request_to_web_server(fdp); 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /tests/fuzz/template_corpus/template.seed: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | User Profile 6 | 7 | 8 |

{{name}}'s Profile

9 |

{{bio}}

10 |

Favorite Programming Language: {{favoriteLanguage}}

11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/fuzz/template_fuzzer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "crow.h" 5 | 6 | static crow::mustache::context build_context_object(FuzzedDataProvider &fdp) 7 | { 8 | crow::mustache::context ctx{}; 9 | 10 | for (auto i = 0; i < fdp.ConsumeIntegralInRange(0, 10); ++i) 11 | { 12 | ctx[fdp.ConsumeRandomLengthString()] = fdp.ConsumeRandomLengthString(); 13 | } 14 | 15 | return ctx; 16 | } 17 | 18 | extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size) 19 | { 20 | FuzzedDataProvider fdp{data, size}; 21 | try 22 | { 23 | auto page = crow::mustache::compile(fdp.ConsumeRandomLengthString()); 24 | auto ctx = build_context_object(fdp); 25 | page.render_string(ctx); 26 | } 27 | catch (const std::exception& e) 28 | { 29 | // No special handling for invalid inputs or rendering errors 30 | } 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /tests/img/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | file(COPY . DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 4 | -------------------------------------------------------------------------------- /tests/img/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrowCpp/Crow/bf4ac3369214ac7af7702dc4a3fb29939acf67ea/tests/img/cat.jpg -------------------------------------------------------------------------------- /tests/img/filewith.badext: -------------------------------------------------------------------------------- 1 | Test file with a strange extension. 2 | -------------------------------------------------------------------------------- /tests/multi_file/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(test_multi_file) 2 | 3 | file (GLOB_RECURSE SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 4 | add_executable(${PROJECT_NAME} ${SRC}) 5 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 6 | target_link_libraries(${PROJECT_NAME} PUBLIC Crow::Crow) 7 | -------------------------------------------------------------------------------- /tests/multi_file/main.cpp: -------------------------------------------------------------------------------- 1 | // Test of a project containing more than 1 source file which includes Crow headers. 2 | // The test succeeds if the project is linked successfully. 3 | #include "crow.h" 4 | 5 | int main() 6 | { 7 | crow::SimpleApp app; 8 | 9 | CROW_ROUTE(app, "/") 10 | ([]() { 11 | return "Hello, world!"; 12 | }); 13 | 14 | app.port(18080).run(); 15 | } 16 | -------------------------------------------------------------------------------- /tests/multi_file/secondary.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /tests/query_string_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "catch2/catch_all.hpp" 6 | #include "crow/query_string.h" 7 | 8 | namespace 9 | { 10 | std::string buildQueryStr(const std::vector>& paramList) 11 | { 12 | std::string paramsStr{}; 13 | for (const auto& param : paramList) 14 | paramsStr.append(param.first).append(1, '=').append(param.second).append(1, '&'); 15 | if (!paramsStr.empty()) 16 | paramsStr.resize(paramsStr.size() - 1); 17 | return paramsStr; 18 | } 19 | } 20 | 21 | TEST_CASE( "empty query params" ) 22 | { 23 | const crow::query_string query_params(""); 24 | const std::vector keys = query_params.keys(); 25 | 26 | REQUIRE(keys.empty() == true); 27 | } 28 | 29 | TEST_CASE( "query string keys" ) 30 | { 31 | const std::vector> params { 32 | {"foo", "bar"}, {"mode", "night"}, {"page", "2"}, 33 | {"tag", "js"}, {"name", "John Smith"}, {"age", "25"}, 34 | }; 35 | 36 | const crow::query_string query_params("params?" + buildQueryStr(params)); 37 | const std::vector keys = query_params.keys(); 38 | 39 | for (const auto& entry: params) 40 | { 41 | const bool exist = std::any_of(keys.cbegin(), keys.cend(), [&](const std::string& key) { 42 | return key == entry.first;}); 43 | REQUIRE(exist == true); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/ssl/BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_test( 2 | name = "ssltest", 3 | srcs = select({ 4 | "//:ssl_enabled": ["ssltest.cpp"], 5 | "//conditions:default": [], 6 | }), 7 | copts = ["-DASIO_STANDALONE", "-DCROW_ENABLE_SSL"], 8 | deps = [ 9 | "//:crow", 10 | "@catch2//:catch2_main", 11 | ], 12 | visibility = ["//visibility:public"], 13 | ) 14 | -------------------------------------------------------------------------------- /tests/ssl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project (crow_ssl_test) 3 | 4 | include(${CMAKE_SOURCE_DIR}/cmake/compiler_options.cmake) 5 | 6 | set(TEST_SRCS 7 | ssltest.cpp 8 | ) 9 | 10 | add_executable(ssltest ${TEST_SRCS}) 11 | target_link_libraries(ssltest Crow::Crow Catch2::Catch2WithMain) 12 | add_warnings_optimizations(ssltest) 13 | 14 | if("${CMAKE_SYSTEM_NAME}" STREQUAL "Android") 15 | target_link_libraries(ssltest log) 16 | endif() 17 | 18 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 19 | set_target_properties(ssltest PROPERTIES COMPILE_FLAGS "--coverage -fprofile-arcs -ftest-coverage") 20 | target_link_libraries(ssltest gcov) 21 | endif() 22 | 23 | -------------------------------------------------------------------------------- /tests/ssl/ssltest.cpp: -------------------------------------------------------------------------------- 1 | #define CROW_LOG_LEVEL 0 2 | 3 | #include 4 | 5 | #include "catch2/catch_all.hpp" 6 | #include "crow.h" 7 | 8 | #define LOCALHOST_ADDRESS "127.0.0.1" 9 | 10 | // TODO(EDev): SSL test with .pem file 11 | TEST_CASE("SSL") 12 | { 13 | static char buf[2048]; 14 | //static char buf2[2048]; 15 | 16 | std::system("openssl req -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out test.crt -keyout test.key -subj '/CN=127.0.0.1'"); 17 | //std::system("cat test.key > test.pem"); 18 | //std::system("cat test.crt >> test.pem"); 19 | 20 | crow::SimpleApp app; 21 | //crow::SimpleApp app2; 22 | 23 | CROW_ROUTE(app, "/") 24 | ([]() { 25 | return "Hello world, I'm keycrt."; 26 | }); 27 | /* 28 | CROW_ROUTE(app2, "/") 29 | ([]() { 30 | return "Hello world, I'm pem."; 31 | }); 32 | */ 33 | auto _ = async(std::launch::async, [&] { 34 | app.bindaddr(LOCALHOST_ADDRESS).port(45460).ssl_file("test.crt", "test.key").run(); 35 | }); 36 | //auto _1 = async(std::launch::async,[&] { app2.bindaddr(LOCALHOST_ADDRESS).port(45461).ssl_file("test.pem").run(); }); 37 | 38 | app.wait_for_server_start(); 39 | //app2.wait_for_server_start(); 40 | 41 | std::string sendmsg = "GET /\r\n\r\n"; 42 | 43 | asio::ssl::context ctx(asio::ssl::context::sslv23); 44 | 45 | asio::io_context ioc; 46 | { 47 | asio::ssl::stream c(ioc, ctx); 48 | c.lowest_layer().connect(asio::ip::tcp::endpoint(asio::ip::make_address(LOCALHOST_ADDRESS), 45460)); 49 | 50 | c.handshake(asio::ssl::stream_base::client); 51 | c.write_some(asio::buffer(sendmsg)); 52 | 53 | std::string http_response; 54 | size_t y = 0; 55 | 56 | asio::error_code ec{}; 57 | while (!ec) { 58 | y = c.read_some(asio::buffer(buf, 2048),ec); 59 | http_response.append(buf,y); 60 | } 61 | auto start_body = http_response.find("\r\n\r\n"); 62 | CHECK(start_body!=std::string::npos); 63 | 64 | CHECK(std::string("Hello world, I'm keycrt.") == http_response.substr(start_body+4)); 65 | 66 | c.lowest_layer().shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); 67 | } 68 | 69 | /* 70 | asio::io_service is2; 71 | { 72 | std::cout << "started second one" << std::endl; 73 | 74 | asio::ssl::stream c(is2, ctx); 75 | c.lowest_layer().connect(asio::ip::tcp::endpoint( 76 | asio::ip::address::from_string(LOCALHOST_ADDRESS), 45461)); 77 | 78 | c.handshake(asio::ssl::stream_base::client); 79 | 80 | c.write_some(asio::buffer(sendmsg)); 81 | 82 | size_t sz = c.read_some(asio::buffer(buf2, 2048)); 83 | CHECK("Hello world, I'm pem." == std::string(buf2).substr((sz - 21))); 84 | } 85 | */ 86 | app.stop(); 87 | //app2.stop(); 88 | 89 | std::system("rm test.crt test.key" /*test.pem*/); 90 | } 91 | -------------------------------------------------------------------------------- /tests/template/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(template_test) 3 | 4 | set(PROJECT_INCLUDE_DIR 5 | ${PROJECT_SOURCE_DIR}/include 6 | ) 7 | 8 | set(TEST_SRCS 9 | mustachetest.cpp 10 | ) 11 | 12 | add_executable(mustachetest ${TEST_SRCS}) 13 | target_link_libraries(mustachetest Crow::Crow) 14 | add_warnings_optimizations(mustachetest) 15 | 16 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 17 | set_target_properties(mustachetest PROPERTIES COMPILE_FLAGS "--coverage -fprofile-arcs -ftest-coverage") 18 | target_link_libraries(mustachetest gcov) 19 | endif() 20 | 21 | if(NOT MSVC) 22 | # there is a bug in the python script, it does not find the path 23 | file(COPY DIRECTORY . DESTINATION ${CMAKE_CURRENT_BINARY_DIR} FILES_MATCHING PATTERN "*.json") 24 | 25 | add_custom_command(OUTPUT test.py 26 | COMMAND ${CMAKE_COMMAND} -E 27 | copy ${PROJECT_SOURCE_DIR}/test.py ${CMAKE_CURRENT_BINARY_DIR}/test.py 28 | DEPENDS ${PROJECT_SOURCE_DIR}/test.py 29 | ) 30 | 31 | add_custom_target(template_test_copy ALL DEPENDS test.py) 32 | 33 | add_test( 34 | NAME template_test 35 | COMMAND python3 ${CMAKE_CURRENT_BINARY_DIR}/test.py 36 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 37 | ) 38 | endif() 39 | -------------------------------------------------------------------------------- /tests/template/README.md: -------------------------------------------------------------------------------- 1 | Spec json files from https://github.com/mustache/spec 2 | -------------------------------------------------------------------------------- /tests/template/comments.json: -------------------------------------------------------------------------------- 1 | {"overview":"Comment tags represent content that should never appear in the resulting\noutput.\n\nThe tag's content may contain any substring (including newlines) EXCEPT the\nclosing delimiter.\n\nComment tags SHOULD be treated as standalone when appropriate.\n","tests":[{"name":"Inline","desc":"Comment blocks should be removed from the template.","data":{},"template":"12345{{! Comment Block! }}67890","expected":"1234567890"},{"name":"Multiline","desc":"Multiline comments should be permitted.","data":{},"template":"12345{{!\n This is a\n multi-line comment...\n}}67890\n","expected":"1234567890\n"},{"name":"Standalone","desc":"All standalone comment lines should be removed.","data":{},"template":"Begin.\n{{! Comment Block! }}\nEnd.\n","expected":"Begin.\nEnd.\n"},{"name":"Indented Standalone","desc":"All standalone comment lines should be removed.","data":{},"template":"Begin.\n {{! Indented Comment Block! }}\nEnd.\n","expected":"Begin.\nEnd.\n"},{"name":"Standalone Line Endings","desc":"\"\\r\\n\" should be considered a newline for standalone tags.","data":{},"template":"|\r\n{{! Standalone Comment }}\r\n|","expected":"|\r\n|"},{"name":"Standalone Without Previous Line","desc":"Standalone tags should not require a newline to precede them.","data":{},"template":" {{! I'm Still Standalone }}\n!","expected":"!"},{"name":"Standalone Without Newline","desc":"Standalone tags should not require a newline to follow them.","data":{},"template":"!\n {{! I'm Still Standalone }}","expected":"!\n"},{"name":"Multiline Standalone","desc":"All standalone comment lines should be removed.","data":{},"template":"Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n","expected":"Begin.\nEnd.\n"},{"name":"Indented Multiline Standalone","desc":"All standalone comment lines should be removed.","data":{},"template":"Begin.\n {{!\n Something's going on here...\n }}\nEnd.\n","expected":"Begin.\nEnd.\n"},{"name":"Indented Inline","desc":"Inline comments should not strip whitespace","data":{},"template":" 12 {{! 34 }}\n","expected":" 12 \n"},{"name":"Surrounding Whitespace","desc":"Comment removal should preserve surrounding whitespace.","data":{},"template":"12345 {{! Comment Block! }} 67890","expected":"12345 67890"}],"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file."} -------------------------------------------------------------------------------- /tests/template/crow_extra_mustache_tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "overview": "Check Crow's behaviour when given invalid mustache templates", 3 | "tests": [ 4 | { 5 | "name": "Missing end-tags", 6 | "desc": "Missing end-tags should fail to render ... and not enter infinite loops or other undefined behaviour", 7 | "data": { 8 | "boolean": true 9 | }, 10 | "template": "\"{{#boolean}}{{^boolean}}\"", 11 | "expected": "COMPILE EXCEPTION: crow::mustache error: open tag has no matching end tag {{# {{/ pair: boolean" 12 | }, 13 | { 14 | "name": "Unexpected end-tags", 15 | "desc": "Unexpected end-tags should fail to render ... and not enter infinite loops or other undefined behaviour", 16 | "data": { 17 | "boolean": true 18 | }, 19 | "template": "\"{{/unexpected}}\"", 20 | "expected": "COMPILE EXCEPTION: crow::mustache error: unexpected closing tag: unexpected" 21 | } 22 | ], 23 | "__ATTN__": "This file was hand-written" 24 | } 25 | -------------------------------------------------------------------------------- /tests/template/delimiters.json: -------------------------------------------------------------------------------- 1 | {"overview":"Set Delimiter tags are used to change the tag delimiters for all content\nfollowing the tag in the current compilation unit.\n\nThe tag's content MUST be any two non-whitespace sequences (separated by\nwhitespace) EXCEPT an equals sign ('=') followed by the current closing\ndelimiter.\n\nSet Delimiter tags SHOULD be treated as standalone when appropriate.\n","tests":[{"name":"Pair Behavior","desc":"The equals sign (used on both sides) should permit delimiter changes.","data":{"text":"Hey!"},"template":"{{=<% %>=}}(<%text%>)","expected":"(Hey!)"},{"name":"Special Characters","desc":"Characters with special meaning regexen should be valid delimiters.","data":{"text":"It worked!"},"template":"({{=[ ]=}}[text])","expected":"(It worked!)"},{"name":"Sections","desc":"Delimiters set outside sections should persist.","data":{"section":true,"data":"I got interpolated."},"template":"[\n{{#section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|#section|\n {{data}}\n |data|\n|/section|\n]\n","expected":"[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n"},{"name":"Inverted Sections","desc":"Delimiters set outside inverted sections should persist.","data":{"section":false,"data":"I got interpolated."},"template":"[\n{{^section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|^section|\n {{data}}\n |data|\n|/section|\n]\n","expected":"[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n"},{"name":"Partial Inheritence","desc":"Delimiters set in a parent template should not affect a partial.","data":{"value":"yes"},"partials":{"include":".{{value}}."},"template":"[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n","expected":"[ .yes. ]\n[ .yes. ]\n"},{"name":"Post-Partial Behavior","desc":"Delimiters set in a partial should not affect the parent template.","data":{"value":"yes"},"partials":{"include":".{{value}}. {{= | | =}} .|value|."},"template":"[ {{>include}} ]\n[ .{{value}}. .|value|. ]\n","expected":"[ .yes. .yes. ]\n[ .yes. .|value|. ]\n"},{"name":"Surrounding Whitespace","desc":"Surrounding whitespace should be left untouched.","data":{},"template":"| {{=@ @=}} |","expected":"| |"},{"name":"Outlying Whitespace (Inline)","desc":"Whitespace should be left untouched.","data":{},"template":" | {{=@ @=}}\n","expected":" | \n"},{"name":"Standalone Tag","desc":"Standalone lines should be removed from the template.","data":{},"template":"Begin.\n{{=@ @=}}\nEnd.\n","expected":"Begin.\nEnd.\n"},{"name":"Indented Standalone Tag","desc":"Indented standalone lines should be removed from the template.","data":{},"template":"Begin.\n {{=@ @=}}\nEnd.\n","expected":"Begin.\nEnd.\n"},{"name":"Standalone Line Endings","desc":"\"\\r\\n\" should be considered a newline for standalone tags.","data":{},"template":"|\r\n{{= @ @ =}}\r\n|","expected":"|\r\n|"},{"name":"Standalone Without Previous Line","desc":"Standalone tags should not require a newline to precede them.","data":{},"template":" {{=@ @=}}\n=","expected":"="},{"name":"Standalone Without Newline","desc":"Standalone tags should not require a newline to follow them.","data":{},"template":"=\n {{=@ @=}}","expected":"=\n"},{"name":"Pair with Padding","desc":"Superfluous in-tag whitespace should be ignored.","data":{},"template":"|{{= @ @ =}}|","expected":"||"}],"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file."} -------------------------------------------------------------------------------- /tests/template/mustachetest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "crow/json.h" 7 | #include "crow/mustache.h" 8 | 9 | using namespace std; 10 | using namespace crow; 11 | using namespace crow::mustache; 12 | 13 | string read_all(const string& filename) 14 | { 15 | ifstream is(filename); 16 | return {istreambuf_iterator(is), istreambuf_iterator()}; 17 | } 18 | 19 | int main() 20 | { 21 | try { 22 | auto data = json::load(read_all("data")); 23 | auto templ = compile(read_all("template")); 24 | auto partials = json::load(read_all("partials")); 25 | set_loader([&](std::string name) -> std::string { 26 | if (partials.count(name)) 27 | { 28 | return partials[name].s(); 29 | } 30 | return ""; 31 | }); 32 | context ctx(data); 33 | cout << templ.render_string(ctx); 34 | } 35 | // catch and return compile errors as text, for the python test to compare 36 | catch (invalid_template_exception & err) { 37 | cout << "COMPILE EXCEPTION: " << err.what(); 38 | } 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /tests/template/partials.json: -------------------------------------------------------------------------------- 1 | {"overview":"Partial tags are used to expand an external template into the current\ntemplate.\n\nThe tag's content MUST be a non-whitespace character sequence NOT containing\nthe current closing delimiter.\n\nThis tag's content names the partial to inject. Set Delimiter tags MUST NOT\naffect the parsing of a partial. The partial MUST be rendered against the\ncontext stack local to the tag. If the named partial cannot be found, the\nempty string SHOULD be used instead, as in interpolations.\n\nPartial tags SHOULD be treated as standalone when appropriate. If this tag\nis used standalone, any whitespace preceding the tag should treated as\nindentation, and prepended to each line of the partial before rendering.\n","tests":[{"name":"Basic Behavior","desc":"The greater-than operator should expand to the named partial.","data":{},"template":"\"{{>text}}\"","partials":{"text":"from partial"},"expected":"\"from partial\""},{"name":"Failed Lookup","desc":"The empty string should be used when the named partial is not found.","data":{},"template":"\"{{>text}}\"","partials":{},"expected":"\"\""},{"name":"Context","desc":"The greater-than operator should operate within the current context.","data":{"text":"content"},"template":"\"{{>partial}}\"","partials":{"partial":"*{{text}}*"},"expected":"\"*content*\""},{"name":"Recursion","desc":"The greater-than operator should properly recurse.","data":{"content":"X","nodes":[{"content":"Y","nodes":[]}]},"template":"{{>node}}","partials":{"node":"{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"},"expected":"X>"},{"name":"Surrounding Whitespace","desc":"The greater-than operator should not alter surrounding whitespace.","data":{},"template":"| {{>partial}} |","partials":{"partial":"\t|\t"},"expected":"| \t|\t |"},{"name":"Inline Indentation","desc":"Whitespace should be left untouched.","data":{"data":"|"},"template":" {{data}} {{> partial}}\n","partials":{"partial":">\n>"},"expected":" | >\n>\n"},{"name":"Standalone Line Endings","desc":"\"\\r\\n\" should be considered a newline for standalone tags.","data":{},"template":"|\r\n{{>partial}}\r\n|","partials":{"partial":">"},"expected":"|\r\n>|"},{"name":"Standalone Without Previous Line","desc":"Standalone tags should not require a newline to precede them.","data":{},"template":" {{>partial}}\n>","partials":{"partial":">\n>"},"expected":" >\n >>"},{"name":"Standalone Without Newline","desc":"Standalone tags should not require a newline to follow them.","data":{},"template":">\n {{>partial}}","partials":{"partial":">\n>"},"expected":">\n >\n >"},{"name":"Standalone Indentation","desc":"Each line of the partial should be indented before rendering.","data":{"content":"<\n->"},"template":"\\\n {{>partial}}\n/\n","partials":{"partial":"|\n{{{content}}}\n|\n"},"expected":"\\\n |\n <\n->\n |\n/\n"},{"name":"Padding Whitespace","desc":"Superfluous in-tag whitespace should be ignored.","data":{"boolean":true},"template":"|{{> partial }}|","partials":{"partial":"[]"},"expected":"|[]|"}],"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file."} -------------------------------------------------------------------------------- /tests/template/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | import glob 4 | import json 5 | import os 6 | import subprocess 7 | 8 | for testfile in glob.glob("*.json"): 9 | testdoc = json.load(open(testfile)) 10 | for test in testdoc["tests"]: 11 | if "lambda" in test["data"]: 12 | continue 13 | 14 | open('data', 'w').write(json.dumps(test["data"])) 15 | open('template', 'w').write(test["template"]) 16 | if "partials" in test: 17 | open('partials', 'w').write(json.dumps(test["partials"])) 18 | else: 19 | open('partials', 'w').write("{}") 20 | 21 | if os.name == 'nt': 22 | ret = subprocess.check_output("mustachetest.exe").decode('utf8') 23 | else: 24 | ret = subprocess.check_output('./mustachetest').decode('utf8') 25 | print(testfile, test["name"]) 26 | 27 | if ret != test["expected"]: 28 | if 'partials' in test: 29 | print('Partials:', json.dumps(test["partials"])) 30 | print('Data: ', json.dumps(test["data"])) 31 | print('Template: ', test["template"]) 32 | print('Expected:', repr(test["expected"])) 33 | print('Actual: ', repr(ret)) 34 | assert ret == test["expected"] 35 | 36 | os.unlink('data') 37 | os.unlink('template') 38 | os.unlink('partials') 39 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crow", 3 | "version-string": "master", 4 | "dependencies": [ 5 | "asio", 6 | "openssl", 7 | "zlib" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------