Compare commits

..

No commits in common. "master" and "v1.9.2" have entirely different histories.

281 changed files with 2462 additions and 8587 deletions

View File

@ -75,7 +75,6 @@ IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertNewlineAtEOF: true
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false

View File

@ -17,7 +17,7 @@ env:
jobs:
windows:
name: windows/msvc - ${{ matrix.link }}
runs-on: windows-2022
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
@ -70,7 +70,7 @@ jobs:
strategy:
fail-fast: false
matrix:
osver: [13, 14, 15]
osver: [12, 13]
steps:
- name: Checkout Drogon source code
uses: actions/checkout@v4
@ -99,6 +99,7 @@ jobs:
- name: Prepare for testing
run: |
brew tap homebrew/services
brew services restart postgresql@14
brew services start mariadb
brew services start redis
@ -175,13 +176,6 @@ jobs:
sudo apt-get --purge remove postgresql postgresql-doc postgresql-common postgresql-client-common
sudo apt-get -y install postgresql-all
- name: Install g++-13
if: startsWith(matrix.compiler.cxx, 'g++') && matrix.compiler.ver == 13
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get install g++-${{ matrix.compiler.ver }}
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Install Clang
if: startsWith(matrix.compiler.cxx, 'clang') && matrix.compiler.ver < 13
run: sudo apt-get install clang-${{ matrix.compiler.ver }}

View File

@ -11,5 +11,5 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y codespell
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows,NotIn,aNULL," --skip="*.csp"
- run: pip install --user codespell[toml]
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows" --skip="*.csp"

View File

@ -17,25 +17,5 @@ jobs:
- name: Install dos2unix
run: sudo apt-get install -y dos2unix
- name: Install clang-format-17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh 17
sudo apt-get install -y clang-format-17
- name: Check formatting
run: ./format.sh && git diff --exit-code
env:
CLANG_FORMAT: clang-format-17
cpplint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install cpplint
run: pip install cpplint
- name: Run lint
run: cpplint --recursive .

View File

@ -1,28 +0,0 @@
name: Build and Push Docker Image
on:
release:
types: [created] # 当新版本被创建时触发
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker image
run: |
cd docker/ubuntu
docker build -t drogonframework/drogon:latest .
- name: Push Docker image
run: |
docker push drogonframework/drogon:latest

1
.gitignore vendored
View File

@ -35,6 +35,7 @@ build/
cmake-build-debug/
cmake-build-debug-visual-studio/
.idea/
lib/inc/drogon/version.h
html/
latex/
.vscode

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5...3.31)
cmake_minimum_required(VERSION 3.5)
project(drogon)
@ -13,7 +13,6 @@ option(BUILD_DOC "Build Doxygen documentation" OFF)
option(BUILD_BROTLI "Build Brotli" ON)
option(BUILD_YAML_CONFIG "Build yaml config" ON)
option(USE_SUBMODULE "Use trantor as a submodule" ON)
option(USE_STATIC_LIBS_ONLY "Use only static libraries as dependencies" OFF)
include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(BUILD_POSTGRESQL "Build with postgresql support" ON "BUILD_ORM" OFF)
@ -25,7 +24,7 @@ CMAKE_DEPENDENT_OPTION(USE_SPDLOG "Allow using the spdlog logging library" OFF "
set(DROGON_MAJOR_VERSION 1)
set(DROGON_MINOR_VERSION 9)
set(DROGON_PATCH_VERSION 11)
set(DROGON_PATCH_VERSION 2)
set(DROGON_VERSION
${DROGON_MAJOR_VERSION}.${DROGON_MINOR_VERSION}.${DROGON_PATCH_VERSION})
set(DROGON_VERSION_STRING "${DROGON_VERSION}")
@ -42,7 +41,7 @@ set(INSTALL_DROGON_CMAKE_DIR ${DEF_INSTALL_DROGON_CMAKE_DIR}
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
# Force MSVC to use UTF-8 because that's what we use. Otherwise it uses
# the default of whatever Windows sets and causes encoding issues.
message(STATUS "You are using MSVC. Forcing to use UTF-8")
message(STATUS "You are using MSVC. Forceing to use UTF-8")
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
if (MSVC_VERSION GREATER_EQUAL 1914)
@ -78,10 +77,6 @@ if (BUILD_SHARED_LIBS)
endif ()
endif (BUILD_SHARED_LIBS)
if(USE_STATIC_LIBS_ONLY)
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}")
endif(USE_STATIC_LIBS_ONLY)
if(USE_SPDLOG)
find_package(spdlog CONFIG)
if(spdlog_FOUND)
@ -121,7 +116,6 @@ endif()
target_include_directories(
${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/lib/inc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/orm_lib/inc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
@ -259,7 +253,7 @@ set(DROGON_SOURCES
lib/src/Cookie.cc
lib/src/DrClassMap.cc
lib/src/DrTemplateBase.cc
lib/src/MiddlewaresFunction.cc
lib/src/FiltersFunction.cc
lib/src/FixedWindowRateLimiter.cc
lib/src/GlobalFilters.cc
lib/src/Histogram.cc
@ -274,7 +268,6 @@ set(DROGON_SOURCES
lib/src/HttpFileUploadRequest.cc
lib/src/HttpRequestImpl.cc
lib/src/HttpRequestParser.cc
lib/src/RequestStream.cc
lib/src/HttpResponseImpl.cc
lib/src/HttpResponseParser.cc
lib/src/HttpServer.cc
@ -285,7 +278,6 @@ set(DROGON_SOURCES
lib/src/ListenerManager.cc
lib/src/LocalHostFilter.cc
lib/src/MultiPart.cc
lib/src/MultipartStreamParser.cc
lib/src/NotFound.cc
lib/src/PluginsManager.cc
lib/src/PromExporter.cc
@ -310,7 +302,7 @@ set(private_headers
lib/src/CacheFile.h
lib/src/ConfigLoader.h
lib/src/ControllerBinderBase.h
lib/src/MiddlewaresFunction.h
lib/src/FiltersFunction.h
lib/src/HttpAppFrameworkImpl.h
lib/src/HttpClientImpl.h
lib/src/HttpConnectionLimit.h
@ -340,24 +332,23 @@ set(private_headers
lib/src/ConfigAdapterManager.h
lib/src/JsonConfigAdapter.h
lib/src/YamlConfigAdapter.h
lib/src/ConfigAdapter.h
lib/src/MultipartStreamParser.h)
lib/src/ConfigAdapter.h)
if (NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
if (NOT WIN32)
set(DROGON_SOURCES
${DROGON_SOURCES}
lib/src/SharedLibManager.cc)
set(private_headers
${private_headers}
lib/src/SharedLibManager.h)
elseif(WIN32)
else (NOT WIN32)
set(DROGON_SOURCES
${DROGON_SOURCES}
third_party/mman-win32/mman.c)
set(private_headers
${private_headers}
third_party/mman-win32/mman.h)
endif()
endif (NOT WIN32)
if (BUILD_POSTGRESQL)
# find postgres
@ -511,7 +502,7 @@ execute_process(COMMAND "git" rev-parse HEAD
OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/lib/inc/drogon/version.h" @ONLY)
"${PROJECT_SOURCE_DIR}/lib/inc/drogon/version.h" @ONLY)
if (DROGON_CXX_STANDARD EQUAL 20)
option(USE_COROUTINE "Enable C++20 coroutine support" ON)
@ -566,9 +557,7 @@ set(DROGON_HEADERS
lib/inc/drogon/HttpClient.h
lib/inc/drogon/HttpController.h
lib/inc/drogon/HttpFilter.h
lib/inc/drogon/HttpMiddleware.h
lib/inc/drogon/HttpRequest.h
lib/inc/drogon/RequestStream.h
lib/inc/drogon/HttpResponse.h
lib/inc/drogon/HttpSimpleController.h
lib/inc/drogon/HttpTypes.h
@ -584,7 +573,7 @@ set(DROGON_HEADERS
lib/inc/drogon/WebSocketConnection.h
lib/inc/drogon/WebSocketController.h
lib/inc/drogon/drogon.h
${CMAKE_CURRENT_BINARY_DIR}/lib/inc/drogon/version.h
lib/inc/drogon/version.h
lib/inc/drogon/drogon_callbacks.h
lib/inc/drogon/PubSubService.h
lib/inc/drogon/drogon_test.h
@ -693,7 +682,6 @@ set(ORM_HEADERS
orm_lib/inc/drogon/orm/BaseBuilder.h
orm_lib/inc/drogon/orm/Criteria.h
orm_lib/inc/drogon/orm/DbClient.h
orm_lib/inc/drogon/orm/DbConfig.h
orm_lib/inc/drogon/orm/DbListener.h
orm_lib/inc/drogon/orm/DbTypes.h
orm_lib/inc/drogon/orm/Exception.h

View File

@ -1,36 +0,0 @@
# Stop searching for additional config files.
set noparent
exclude_files=trantor
exclude_files=build
# Use non-const reference rather than a pointer.
filter=-runtime/references
# CHECK macros are from Drogon, not Google Test.
filter=-readability/check
# Don't warn about the use of C++11 or C++17 features.
filter=-build/c++11
filter=-build/c++17
filter=-build/include_subdir
# We prioritize clang-format for now.
filter=-whitespace
# We don't require a username in TODO comments.
filter=-readability/todo
# TODO: Fix these.
filter=-legal/copyright
filter=-build/namespaces
filter=-build/include
filter=-build/include_what_you_use
filter=-runtime/explicit
filter=-runtime/string
filter=-runtime/int
filter=-readability/casting
filter=-readability/braces
filter=-readability/fn_size
filter=-runtime/threadsafe_fn

View File

@ -4,273 +4,6 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
## [1.9.11] - 2025-06-20
### API changes list
- Add a new overload for execSqlCoro.
### Changed
- Do not write to source directory during build.
- Improve Postgres connection stability.
- Add handleFatalError in handleClosed.
- Add -o|--output option to drogon_ctl create models.
- Add qrcode for WeChat official account to the README file.
- Support for iOS compiling.
- Add cors example to demonstrate cross-origin support in drogon.
- Add support for continuation frame in WebSocketMessageParser.
- Add RawParameter API to pass raw SQL parameters.
- Upgrade Windows image and re-enable tests on Windows.
### Fixed
- Fix a bug in isAutoCreationClass<T>.
- Fix CI on MacOS.
- Fix issue with precision loss of double-type parameters in ORM inputs.
## [1.9.10] - 2025-02-20
### API changes list
- Add setConnectionCallback.
### Changed
- ORM:Avoid unnecessary copies when returning search results.
- Improve the zh-TW README translation.
- Make quit function thread safe.
- Added path_exempt in AccessLogger plugin config to exclude desired paths.
### Fixed
- Fix the issue in view generation by including the missing header file.
- Fix ci: codespell.
## [1.9.9] - 2025-01-01
### API changes list
- Added Partitioned flag for cookies.
### Changed
- Update FindFilesystem.cmake to check for GNU instead of GCC for CMAKE_CXX_COMPILER_ID.
- Update README.
- Chore(workflow/cmake.yml): upgrade macos runner.
- Add emptiness check to the LogStream &operator<< with std::string_view.
### Fixed
- Fix a bug in plugin Redirector.
- Fix CMAKE issues mentioned in #2144 and a linking problem which manifest with gcc12.3 when building with shared libs.
- Fix: Remove dependency on locales being installed on the system.
## [1.9.8] - 2024-10-27
### API changes list
- Add in-place base64 encode and decode.
- Add check the client connection status.
### Changed
- Add Hodor whitelists.
- Include exception header for std::exception_ptr.
- Add support for escaped identifiers in Postgresql.
- Remove content-length header from 101 Switching Protocols response.
- Remove websocketResponseTest from windows shared library env.
- Optimize query params and allow for empty values.
- Replace rejection sampling and remove use of rand().
- Add sending customized http requests to drogon_ctl.
### Fixed
- Fix coroutine continuation handle.
- Fix some bugs in plugin PromExporter.
- Fix a bug after removing content-length header in some responses.
## [1.9.7] - 2024-09-10
### API changes list
- Add coroutine mutex.
- Add requestsBufferSize function.
- Refine SQLite3 error types with new exception handling.
- Add a new method to reload SSL files on the fly.
### Changed
- Allow MultiPartParser to be movable.
- Add quotes to the table name in the ORM generator.
- Change stoi to stoul in the Field class.
- Modernize cookies.
- Change a log level.
### Fixed
- Use correct libraries when compiling statically.
## [1.9.6] - 2024-07-20
### API changes list
- Add setsockopt to HttpServer.
- Support request stream.
### Changed
- Allow MultiPartParser to parse PATCH requests.
- Add an example of prometheus.
- Delay parsing json for HttpClient.
- Update README.md.
### Fixed
- Fix some compilation warnings.
- Fix typo in yaml config.
## [1.9.5] - 2024-06-08
### API changes list
- Fix an error in the yaml format config file.
- Support postgresql connection options.
- Add regex support for websocket controller.
- Add the registerMiddleware method.
### Changed
- Add the conan badge to readme files.
- Install gcc in ci.
- Intention to present an alternative to improve the performance of a method in models.
### Fixed
- Fix an error in the yaml format config file.
- Fix CI on Windows.
- Fix some spelling errors.
## [1.9.4] - 2024-05-04
### API changes list
- Add client cert support for websocket.
- Add JSON send overloads for WebSocket connections.
### Changed
- Minor enhancement: move some smart pointers around instead of copying them.
- Remove the request shared_ptr from the multipart parser.
- Fix typo in HttpAppFrameworkImpl.cc.
- Avoid string copy and lowercasing on every request.
- Implemented database reconnection loop.
### Fixed
- Bypass clang thread_local error.
## [1.9.3] - 2024-02-09
### API changes list
- Added getParameter() and getOptionalParameter().
- Change drogon::MultiPartParser's parameters data type.
- Use std::string_view for WebSockets.
### Changed
- Add support for gentoo linux, dev-db/mariadb contains mysql.
- Introduce cpplint to the CI.
- Enable readability/alt_tokens for cpplint.
- Use clang-format-17.
- Add newline at EOF.
- Enable readability/inheritance for cpplint.
- Enable build/explicit_make_pair for cpplint.
- Enable build/include_order for cpplint.
- Enable build/header_guard for cpplint.
- Enable build/storage_class for cpplint.
- Enable readability/multiline_string for cpplint.
- Alias the safe hashmap template.
- Simplify traits in utils.
- Enhancement: extend drogon::ContentType for file handling.
### Fixed
- Fix a wrong place of return.
- Fix drogon::util::fromString().
## [1.9.2] - 2024-01-18
### API changes list
@ -520,7 +253,7 @@ All notable changes to this project will be documented in this file.
- Remove unused CI files and Jekyll config.
- Ensure that all filters, AOP advice, and handlers are executed within the IO threads.
- Ensure that all filters, AOP advices, and handlers are executed within the IO threads.
- Update test.sh and build.sh by appending prefix "X" to string variable comparisons.
@ -852,7 +585,7 @@ All notable changes to this project will be documented in this file.
- Check HTTP client is not sending requests in sync mode on the same event loop.
- Start listening after beginning advice.
- Start listening after beginning advices.
- Allow using json_cpp in other sublibraries.
@ -1842,25 +1575,7 @@ All notable changes to this project will be documented in this file.
## [1.0.0-beta1] - 2019-06-11
[Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.11...HEAD
[1.9.11]: https://github.com/an-tao/drogon/compare/v1.9.10...v1.9.11
[1.9.10]: https://github.com/an-tao/drogon/compare/v1.9.9...v1.9.10
[1.9.9]: https://github.com/an-tao/drogon/compare/v1.9.8...v1.9.9
[1.9.8]: https://github.com/an-tao/drogon/compare/v1.9.7...v1.9.8
[1.9.7]: https://github.com/an-tao/drogon/compare/v1.9.6...v1.9.7
[1.9.6]: https://github.com/an-tao/drogon/compare/v1.9.5...v1.9.6
[1.9.5]: https://github.com/an-tao/drogon/compare/v1.9.4...v1.9.5
[1.9.4]: https://github.com/an-tao/drogon/compare/v1.9.3...v1.9.4
[1.9.3]: https://github.com/an-tao/drogon/compare/v1.9.2...v1.9.3
[Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.2...HEAD
[1.9.2]: https://github.com/an-tao/drogon/compare/v1.9.1...v1.9.2

View File

@ -1,14 +1,14 @@
![](https://github.com/an-tao/drogon/wiki/images/drogon-white17.jpg)
[![Build Status](https://github.com/drogonframework/drogon/actions/workflows/cmake.yml/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions)
[![Conan Center](https://img.shields.io/conan/v/drogon)](https://conan.io/center/recipes/drogon)
[![Build Status](https://github.com/an-tao/drogon/workflows/Build%20Drogon/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions)
[![Join the chat at https://gitter.im/drogon-web/community](https://badges.gitter.im/drogon-web/community.svg)](https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the telegram group at https://t.me/joinchat/_mMNGv0748ZkMDAx](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/joinchat/_mMNGv0748ZkMDAx)
[![Join our Discord](https://dcbadge.vercel.app/api/server/3DvHY6Ewuj?style=flat)](https://discord.gg/3DvHY6Ewuj)
[![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon)
English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md)
### Overview
**Drogon** is a C++17/20 based HTTP application framework. Drogon can be used to easily build various types of web application server programs using C++. **Drogon** is the name of a dragon from the American TV series *Game of Thrones*, which I really enjoy.
**Drogon** is a C++17/20 based HTTP application framework. Drogon can be used to easily build various types of web application server programs using C++. **Drogon** is the name of a dragon in the American TV series "Game of Thrones" that I really like.
Drogon is a cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD, HaikuOS, and Windows. Its main features are as follows:
@ -183,7 +183,7 @@ As you can see, users can use the `HttpController` to map paths and parameters a
In addition, you can also find that all handler interfaces are in asynchronous mode, where the response is returned by a callback object. This design is for performance reasons because in asynchronous mode the drogon application can handle a large number of concurrent requests with a small number of threads.
After compiling all of the above source files, we get a very simple web application. This is a good start. **For more information, please visit the [documentation](https://drogonframework.github.io/drogon-docs/#/) on GitHub**.
After compiling all of the above source files, we get a very simple web application. This is a good start. **For more information, please visit the [wiki](https://github.com/an-tao/drogon/wiki/ENG-01-Overview)**
## Cross-compilation

View File

@ -1,7 +1,7 @@
![](https://github.com/an-tao/drogon/wiki/images/drogon-white17.jpg)
[![Build Status](https://github.com/drogonframework/drogon/actions/workflows/cmake.yml/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions)
[![Conan Center](https://img.shields.io/conan/v/drogon)](https://conan.io/center/recipes/drogon)
[![Build Status](https://github.com/an-tao/drogon/workflows/Build%20Drogon/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions)
[![Join the chat at https://gitter.im/drogon-web/community](https://badges.gitter.im/drogon-web/community.svg)](https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the telegram group at https://t.me/joinchat/_mMNGv0748ZkMDAx](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/joinchat/_mMNGv0748ZkMDAx)
[![Join our Discord](https://dcbadge.vercel.app/api/server/3DvHY6Ewuj?style=flat)](https://discord.gg/3DvHY6Ewuj)
[![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon)
@ -186,7 +186,7 @@ class User : public drogon::HttpController<User>
另外,你可以发现前面所有的处理函数接口都是异步的,处理器的响应是通过回调对象返回的。这种设计是出于对高性能的考虑,因为在异步模式下,可以使用少量的线程(比如和处理器核心数相等的线程)处理大量的并发请求。
编译上述的所有源文件后我们得到了一个非常简单的web应用程序这是一个不错的开始。**请访问GitHub上的[文档](https://drogonframework.github.io/drogon-docs/#/CHN/CHN-01-%E6%A6%82%E8%BF%B0)**
编译上述的所有源文件后我们得到了一个非常简单的web应用程序这是一个不错的开始。**请访问[wiki](https://github.com/an-tao/drogon/wiki/CHN-01-概述)**
## 贡献方式
@ -197,9 +197,3 @@ class User : public drogon::HttpController<User>
## QQ交流群1137909452
欢迎交流探讨。
## 微信公众号:
![](https://github.com/an-tao/drogon/wiki/images/qrcode_wechat.jpg)
会不定期推送一些Drogon的使用技巧和更新信息欢迎关注。

View File

@ -1,49 +1,48 @@
![](https://github.com/an-tao/drogon/wiki/images/drogon-white17.jpg)
[![Build Status](https://github.com/drogonframework/drogon/actions/workflows/cmake.yml/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions)
[![Conan Center](https://img.shields.io/conan/v/drogon)](https://conan.io/center/recipes/drogon)
[![Build Status](https://github.com/an-tao/drogon/workflows/Build%20Drogon/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions)
[![Join the chat at https://gitter.im/drogon-web/community](https://badges.gitter.im/drogon-web/community.svg)](https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the telegram group at https://t.me/joinchat/_mMNGv0748ZkMDAx](https://img.shields.io/badge/Telegram-2CA5E0?style=flat&logo=telegram&logoColor=white)](https://t.me/joinchat/_mMNGv0748ZkMDAx)
[![Join our Discord](https://dcbadge.vercel.app/api/server/3DvHY6Ewuj?style=flat)](https://discord.gg/3DvHY6Ewuj)
[![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon)
[English](./README.md) | [简体中文](./README.zh-CN.md) | 繁體中文
**Drogon** 是一個基於 C++17/20 的 HTTP 應用程式框架,使用 Drogon 可以方便地用 C++ 建立各種類型的 Web App 伺服器端程式。
**Drogon**是一個基於C++17/20的Http應用框架使用Drogon可以方便的使用C++構建各種類型的Web App伺服器程式。
本版本庫是github上[Drogon](https://github.com/an-tao/drogon)的鏡像庫。 **Drogon**是作者非常喜歡的美劇《冰與火之歌:權力遊戲》中的一條龍的名字(漢譯作卓耿)和龍有關但並不是dragon的誤寫為了不至於引起不必要的誤會這裡說明一下。
這個版本庫是 GitHub 上 [Drogon](https://github.com/an-tao/drogon) 的鏡像庫。**Drogon** 是作者非常喜歡的美劇《冰與火之歌:權力遊戲》中的一條龍的名字(中文譯作卓耿),和龍有關但並不是 dragon 的誤寫,為了避免不必要的誤會在此說明。
Drogon是一個跨平台框架它支援Linux也支援macOS、FreeBSD/OpenBSD、HaikuOS和Windows。它的主要特點如下
Drogon 是一個跨平台框架,支援 Linux、macOS、FreeBSD/OpenBSD、HaikuOS 和 Windows。主要特點如下
* 網路層使用基於 epollmacOS/FreeBSD 下是 kqueue的非阻塞 IO 框架,提供高並行、高效能的網路 IO。詳細請見 [TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite)
* 完全非同步的程式撰寫邏輯;
* 支援 HTTP 1.0/1.1(伺服器端和用戶端);
* 基於樣板template實作的簡單反射機制使主程式框架、控制器controller和視圖view完全解耦
* 支援 cookies 和內建的 session
* 支援後端算繪,將控制器產生的資料交給視圖產生 HTML 頁面,視圖由 CSP 樣板檔案描述,透過 CSP 標籤將 C++ 程式碼嵌入 HTML 頁面,由 drogon 的命令列工具在編譯階段自動產生 C++ 程式碼並編譯;
* 支援執行期的視圖頁面動態載入(動態編譯和載入 so 檔案);
* 非常方便靈活的路徑path到控制器處理函式handler的對應方案
* 支援過濾器filter方便在控制器之前執行統一的邏輯如登入驗證、HTTP Method 限制驗證等);
* 支援 HTTPS基於 OpenSSL
* 支援 WebSocket伺服器端和用戶端
* 支援 JSON 格式的請求和回應,方便開發 RESTful API
* 支援檔案下載和上傳,支援 `sendfile` 系統呼叫;
* 支援 Gzip/Brotli 壓縮傳輸;
* 支援 pipelining
* 提供輕量的命令列工具 `drogon_ctl`,幫助簡化各種類別的建立和視圖程式碼的產生過程;
* 非同步的讀寫資料庫,目前支援 PostgreSQL 和 MySQLMariaDB資料庫
* 支援非同步讀寫 Redis
* 基於執行緒池實作 sqlite3 資料庫的非同步讀寫,提供與上述資料庫相同的介面;
* 支援 ARM 架構;
* 方便的輕量級 ORM 實現,一般物件到資料庫的雙向對應;
* 支援外掛,可透過設定檔案在載入時動態載入;
* 支援內建插入點的 AOP
* 支援 C++ coroutine。
* 網路層使用基於epoll(macOS/FreeBSD下是kqueue)的非阻塞IO框架提供高並發、高性能的網路IO。詳細請見[TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite)
* 全異步程式設計;
* 支援Http1.0/1.1(server端和client端)
* 基於模板(template)實現了簡單的反射機制,使主程式框架、控制器(controller)和視圖(view)完全去耦;
* 支援cookies和內建的session
* 支援後端渲染把控制器生成的數據交給視圖生成Html頁面視圖由CSP模板文件描述通過CSP標籤把C++程式碼嵌入到Html頁面由drogon的指令列工具在編譯階段自動生成C++程式碼並編譯;
* 支援運行期的視圖頁面動態加載(動態編譯和載入so文件)
* 非常方便靈活的路徑(path)到控制器處理函數(handler)的映射方案;
* 支援過濾器(filter)鏈,方便在控制器之前執行統一的邏輯(如登錄驗證、Http Method約束驗證等)
* 支援https(基於OpenSSL);
* 支援websocket(server端和client端);
* 支援Json格式的請求和回應, 方便開發Restful API;
* 支援文件下載和上傳,支援sendfile系統呼叫
* 支援gzip/brotli壓縮傳輸
* 支援pipelining
* 提供一個輕量的指令列工具drogon_ctl幫助簡化各種類的創造和視圖程式碼的生成過程
* 非同步的讀寫資料庫目前支援PostgreSQL和MySQL(MariaDB)資料庫;
* 支援異步讀寫Redis;
* 基於執行序池實現sqlite3資料庫的異步讀寫提供與上文資料庫相同的接口
* 支援ARM架構
* 方便的輕量級ORM實現一般物件到資料庫的雙向映射
* 支援外掛,可通過設定文件在載入時動態載入;
* 支援內建插入點的AOP
* 支援C++ coroutine
## 一個非常簡單的例子
不像大多數 C++ 框架drogon 的主程式可以非常簡單。Drogon 使用了一些小技巧使主程式和控制器解耦。控制器的路由設定可以在控制器類別中定義或在設定檔案中完成。
不像大多數C++框架那樣drogon的主程式可以非常簡單。 Drogon使用了一些小技巧使主程式和控制器去耦. 控制器的路由設定可以在控制器類別中定義或者設定文件中完成.
下面是一個典型主程式的樣子:
下面是一個典型的主程式的樣子:
```c++
#include <drogon/drogon.h>
@ -59,7 +58,7 @@ int main()
}
```
如果使用設定檔案,可以進一步簡化成:
如果使用設定文件,可以進一步簡化成這樣:
```c++
#include <drogon/drogon.h>
@ -70,7 +69,7 @@ int main()
}
```
當然Drogon 也提供了一些函式,讓使用者可以在 `main()` 函式中直接加入控制器邏輯,例如,使用者可以註冊一個 lambda 處理常式到 drogon 框架中,如下所示:
當然Drogon也提供了一些函數使使用者可以在main()函數中直接添加控制器邏輯比如使用者可以註冊一個lambda處理器到drogon框架中,如下所示:
```c++
app().registerHandler("/test?username={name}",
@ -87,7 +86,9 @@ app().registerHandler("/test?username={name}",
{Get,"LoginFilter"});
```
這看起來很方便,但不適用於複雜的場景,試想如果有數十個或數百個處理函式要註冊進框架,`main()` 函式將變得難以閱讀。顯然,讓每個包含處理函式的類別在自己的定義中完成註冊是更好的選擇。所以,除非你的應用邏輯非常簡單,我們不建議使用上述介面,更好的做法是建立一個 HttpSimpleController 類別,如下:
這看起來是很方便但是這並不適用於復雜的場景試想假如有數十個或者數百個處理函數要註冊進框架main()函數將膨脹到不可讀的程度。顯然讓每個包含處理函數的類在自己的定義中完成註冊是更好的選擇。所以除非你的應用邏輯非常簡單我們不推薦使用上述接口更好的實踐是我們可以創造一個HttpSimpleController類別如下
```c++
/// The TestCtrl.h file
@ -116,9 +117,9 @@ void TestCtrl::asyncHandleHttpRequest(const HttpRequestPtr& req,
}
```
**上述程式的大部分程式碼都可以由 `drogon_ctl` 指令產生**(使用指令 `drogon_ctl create controller TestCtr`)。使用者只需要加入自己的業務邏輯。在這個範例中,當用戶端存取 URL `http://ip/test` 時,控制器簡單地回傳一個 `Hello, world!` 頁面。
**上面程式的大部分程式碼都可以由`drogon_ctl`指令創造**(這個指令是`drogon_ctl create controller TestCtr`。使用者所需做的就是添加自己的業務邏輯。在這個例子中當客戶端訪問URL`http://ip/test`時,控制器簡單的回傳了一個`Hello, world!`頁面。
對於 JSON 格式的回應,我們可以這樣建立控制器:
對於JSON格式的回應我們可以像下面這樣創造控制器:
```c++
/// The header file
@ -147,7 +148,7 @@ void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
}
```
讓我們更進一步,透過 HttpController 類別建立一個 RESTful API 的範例,如下所示(省略實作檔案
讓我們更進一步,通過HttpController類別創造一個RESTful API的例子如下所示忽略了實做文件
```c++
/// The header file
@ -181,18 +182,18 @@ class User : public drogon::HttpController<User>
} // namespace api
```
如你所見,透過 `HttpController` 類別,使用者可以同時對應路徑和路徑參數,這對 RESTful API 應用來說非常方便。
如你所見,通過`HttpController`類別使用者可以同時映射路徑和路徑參數這對RESTful API應用來說非常方便。
另外,你可以發現前面所有的處理函式介面都是非同步的,處理器的回應是透過回呼物件回傳的。這種設計是考慮到效能,因為在非同步模式下,可以使用少量的執行緒(例如和處理器核心數相等的執行緒)處理大量的並行請求。
另外,你可以發現前面所有的處理函數接口都是異步的,處理器的回應是通過回調對象回傳的。這種設計是出於對高性能的考慮,因為在異步模式下,可以使用少量的執行序(比如和處理器核心數相等的執行序)處理大量的並發請求。
編譯上述所有原始檔案後,我們得到了一個非常簡單的網頁應用程式,這是一個不錯的開始。**請瀏覽 GitHub 上的[文件](https://drogonframework.github.io/drogon-docs/#/CHN/CHN-01-%E6%A6%82%E8%BF%B0)**
編譯上述的所有源文件後我們得到了一個非常簡單的web應用程式這是一個不錯的開始。 **請瀏覽[wiki](https://github.com/an-tao/drogon/wiki/CHN-01-概述)**
## 貢獻方式
歡迎您的貢獻。請閱讀[貢獻指南](CONTRIBUTING.md)以取得更多資訊
歡迎您的貢獻。請閱讀[貢獻指南](CONTRIBUTING.md)以獲取更多的信息
<a href="https://github.com/drogonframework/drogon/graphs/contributors"><img src="https://contributors-svg.opencollective.com/drogon/contributors.svg?width=890&button=false" alt="Code contributors" /></a>
## QQ 交流群1137909452
## QQ交流群1137909452
歡迎交流討
歡迎交流討。

View File

@ -17,7 +17,7 @@
# ParseAndAddDrogonTests(${PROJECT_NAME}) #
#==================================================================================================#
cmake_minimum_required(VERSION 3.5...3.31)
cmake_minimum_required(VERSION 3.5)
# This removes the contents between
# - block comments (i.e. /* ... */)

View File

@ -1,5 +1,3 @@
#pragma once
#define MAJOR @DROGON_MAJOR_VERSION@
#define MINOR @DROGON_MINOR_VERSION@
#define PATCH @DROGON_PATCH_VERSION@

View File

@ -212,7 +212,7 @@ if(CXX_FILESYSTEM_HAVE_FS)
]] code @ONLY)
# HACK: Needed to compile correctly on Yocto Linux
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
if(CMAKE_CXX_COMPILER_ID STREQUAL "GCC" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(CMAKE_REQUIRED_FLAGS ${prev_req_flags} -std=c++17)
endif ()

View File

@ -51,9 +51,7 @@ if(Jsoncpp_FOUND)
COMMAND awk "{ printf \$3 }"
COMMAND sed -e "s/\"//g"
OUTPUT_VARIABLE jsoncpp_ver)
if(NOT Jsoncpp_FIND_QUIETLY)
message(STATUS "jsoncpp version:" ${jsoncpp_ver})
endif()
if(jsoncpp_ver LESS 1.7)
message(
FATAL_ERROR

View File

@ -27,26 +27,6 @@
# ##############################################################################
# -------------- FIND MYSQL_INCLUDE_DIRS ------------------
find_path(MARIADB_INCLUDE_DIRS
NAMES mysql.h
PATH_SUFFIXES mariadb
PATHS /usr/include/mysql
/usr/local/include/mysql
/usr/include/mariadb
/usr/local/include/mariadb
/opt/mysql/mysql/include
/opt/mysql/mysql/include/mysql
/opt/mysql/include
/opt/local/include/mysql5
/usr/local/mysql/include
/usr/local/mysql/include/mysql
/usr/local/mariadb/include
/usr/local/mariadb/include/mariadb
/opt/rh/rh-mariadb105/root/usr/include
/opt/rh/rh-mariadb105/root/usr/include/mysql
$ENV{ProgramFiles}/MySQL/*/include
$ENV{SystemDrive}/MySQL/*/include)
find_path(MYSQL_INCLUDE_DIRS
NAMES mysql.h
PATH_SUFFIXES mysql
@ -67,9 +47,7 @@ find_path(MYSQL_INCLUDE_DIRS
$ENV{ProgramFiles}/MySQL/*/include
$ENV{SystemDrive}/MySQL/*/include)
if(EXISTS "${MARIADB_INCLUDE_DIRS}/mysql.h")
set(MYSQL_INCLUDE_DIRS ${MARIADB_INCLUDE_DIRS})
elseif(EXISTS "${MYSQL_INCLUDE_DIRS}/mysql.h")
if(EXISTS "${MYSQL_INCLUDE_DIRS}/mysql.h")
elseif(EXISTS "${MYSQL_INCLUDE_DIRS}/mysql/mysql.h")
set(MYSQL_INCLUDE_DIRS ${MYSQL_INCLUDE_DIRS}/mysql)
@ -99,7 +77,7 @@ if(WIN32)
$ENV{SystemDrive}/MySQL/*/lib/${libsuffixDist})
else(WIN32)
find_library(MYSQL_LIBRARIES
NAMES mysqlclient_r mariadbclient mariadb
NAMES mysqlclient_r mariadbclient
PATHS /usr/lib/mysql
/usr/lib/mariadb
/usr/local/lib/mysql

View File

@ -39,7 +39,7 @@
"db_clients": [
{
//name: Name of the client,'default' by default
"name": "default",
//"name":"",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
"rdbms": "postgresql",
//filename: Sqlite3 db file name
@ -66,18 +66,15 @@
//timeout: -1.0 by default, in seconds, the timeout for executing a SQL query.
//zero or negative value means no timeout.
"timeout": -1.0,
//auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
//the wiki for more details.
//"auto_batch": this feature is only available for the PostgreSQL driver(version >= 14.0), see
// the wiki for more details.
"auto_batch": false
//connect_options: extra options for the connection. Only works for PostgreSQL now.
//For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
//"connect_options": { "statement_timeout": "1s" }
}
],
"redis_clients": [
{
//name: Name of the client,'default' by default
"name": "default",
//"name":"",
//host: Server IP, 127.0.0.1 by default
"host": "127.0.0.1",
//port: Server port, 6379 by default
@ -108,7 +105,7 @@
"session_timeout": 0,
//string value of SameSite attribute of the Set-Cookie HTTP response header
//valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
"session_same_site": "Null",
"session_same_site" : "Null",
//session_cookie_key: The cookie key of the session, "JSESSIONID" by default
"session_cookie_key": "JSESSIONID",
//session_max_age: The max age of the session cookie, -1 by default
@ -218,6 +215,7 @@
//log: Set log output, drogon output logs to stdout by default
"log": {
//use_spdlog: Use spdlog library to log
//"use_spdlog": false
"use_spdlog": false,
//log_path: Log file path,empty by default,in which case,logs are output to the stdout
//"log_path": "./",
@ -310,10 +308,7 @@
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
// will be rejected.
"enabled_compressed_request": false,
// enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
// See the wiki for more details.
"enable_request_stream": false,
"enabled_compressed_request": false
},
//plugins: Define all plugins running in the application
"plugins": [

View File

@ -3,8 +3,8 @@
# ssl:The global SSL settings. "key" and "cert" are the path to the SSL key and certificate. While
# "conf" is an array of 1 or 2-element tuples that supplies file style options for `SSL_CONF_cmd`.
# ssl:
# cert: ../../trantor/trantor/tests/server.crt
# key: ../../trantor/trantor/tests/server.key
# cert: ../../trantor/trantor/tests/server.pem
# key: ../../trantor/trantor/tests/server.pem
# conf: [
# # [Options, -SessionTicket],
# # [Options, Compression]
@ -30,11 +30,11 @@
# ]
# db_clients:
# # name: Name of the client,'default' by default
# - name: default
# - name: ''
# # rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
# rdbms: postgresql
# # filename: Sqlite3 db file name
# # filename: ''
# # filename: '',
# # host: Server address,localhost by default
# host: 127.0.0.1
# # port: Server port, 5432 by default
@ -50,23 +50,19 @@
# is_fast: false
# # client_encoding: The character set used by the client. it is empty string by default which
# # means use the default character set.
# # client_encoding: ''
# # client_encoding: '',
# # number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
# # connections per IO thread, otherwise it is the total number of all connections.
# number_of_connections: 1
# # timeout: -1 by default, in seconds, the timeout for executing a SQL query.
# # zero or negative value means no timeout.
# timeout: -1
# # auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
# # "auto_batch": this feature is only available for the PostgreSQL driver(version >= 14.0), see
# # the wiki for more details.
# auto_batch: false
# # connect_options: extra options for the connection. Only works for PostgreSQL now.
# # For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
# # connect_options:
# # statement_timeout: '1s'
# redis_clients:
# # name: Name of the client,'default' by default
# - name: default
# - name: ''
# # host: Server IP, 127.0.0.1 by default
# host: 127.0.0.1
# # port: Server port, 6379 by default
@ -140,10 +136,8 @@ app:
# mime: A dictionary that extends the internal MIME type support. Maps extensions into new MIME types
# note: This option only adds MIME to the sever. `file_types` above have to be set for the server to serve them.
mime: {
# text/markdown: md
# text/gemini:
# - gmi
# - gemini
# text/markdown: md,
# text/gemini: [gmi, gemini]
}
# locations: An array of locations of static files for GET requests.
locations:
@ -196,7 +190,7 @@ app:
# log: Set log output, drogon output logs to stdout by default
log:
# use_spdlog: Use spdlog library to log
use_spdlog: false
use_spdlog: false,
# log_path: Log file path,empty by default,in which case,logs are output to the stdout
# log_path: ./
# logfile_base_name: Log file base name,empty by default which means drogon names logfile as
@ -205,9 +199,6 @@ app:
# log_size_limit: 100000000 bytes by default,
# When the log file size reaches "log_size_limit", the log file is switched.
log_size_limit: 100000000
# max_files: 0 by default,
# When the number of old log files exceeds "max_files", the oldest file will be deleted. 0 means never delete.
max_files: 0
# log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
# The TRACE level is only valid when built in DEBUG mode.
log_level: DEBUG
@ -230,14 +221,14 @@ app:
# 0 means cache forever, the negative value means no cache
static_files_cache_time: 5
# simple_controllers_map: Used to configure mapping from path to simple controller
# simple_controllers_map:
# - path: /path/name
# controller: controllerClassName
# http_methods:
# - get
# - post
# filters:
# - FilterClassName
simple_controllers_map:
- path: /path/name
controller: controllerClassName
http_methods:
- get
- post
filters:
- FilterClassName
# idle_connection_timeout: Defaults to 60 seconds, the lifetime
# of the connection without read or write
idle_connection_timeout: 60
@ -278,37 +269,23 @@ app:
client_max_websocket_message_size: 128K
# reuse_port: Defaults to false, users can run multiple processes listening on the same port at the same time.
reuse_port: false
# enabled_compressed_request: Defaults to false. If true the server will automatically decompress compressed request bodies.
# enabled_compresed_request: Defaults to false. If true the server will automatically decompress compressed request bodies.
# Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
# will be rejected.
enabled_compressed_request: false
# enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
# See the wiki for more details.
enable_request_stream: false
enabled_compresed_request: false
# plugins: Define all plugins running in the application
plugins:
# name: The class name of the plugin
- name: drogon::plugin::PromExporter
- name: '' # drogon::plugin::SecureSSLRedirector
# dependencies: Plugins that the plugin depends on. It can be commented out
dependencies: []
# config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
# It can be commented out
config:
path: /metrics
- name: drogon::plugin::AccessLogger
dependencies: []
config:
use_spdlog: false
log_path: ''
log_format: ''
log_file: access.log
log_size_limit: 0
use_local_time: true
log_index: 0
# show_microseconds: true
# custom_time_format: ''
# use_real_ip: false
ssl_redirect_exempt:
- .*\.jpg
secure_ssl_host: 'localhost:8849'
# custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
custom_config:
realm: drogonRealm
@ -316,3 +293,4 @@ custom_config:
credentials:
- user: drogon
password: dr0g0n

View File

@ -42,8 +42,7 @@ std::string create::detail()
"create a plugin named class_name\n\n"
"drogon_ctl create project <project_name> //"
"create a project named project_name\n\n"
"drogon_ctl create model <model_path> [-o <output path>] "
"[--table=<table_name>] [-f]//"
"drogon_ctl create model <model_path> [--table=<table_name>] [-f]//"
"create model classes in model_path\n";
}

View File

@ -66,17 +66,6 @@ static std::string escapeConnString(const std::string &str)
return escaped;
}
std::string drogon_ctl::escapeIdentifier(const std::string &identifier,
const std::string &rdbms)
{
if (rdbms != "postgresql")
{
return identifier;
}
return "\\\"" + identifier + "\\\"";
}
static std::map<std::string, std::vector<ConvertMethod>> getConvertMethods(
const Json::Value &convertColumns)
{
@ -177,7 +166,7 @@ void create_model::createModelClassFromPG(
auto className = nameTransform(tableName, true);
HttpViewData data;
data["className"] = className;
data["tableName"] = tableName;
data["tableName"] = toLower(tableName);
data["hasPrimaryKey"] = (int)0;
data["primaryKeyName"] = "";
data["dbName"] = dbname_;
@ -189,10 +178,10 @@ void create_model::createModelClassFromPG(
data["schema"] = schema;
}
std::vector<ColumnInfo> cols;
*client << "SELECT * "
"FROM information_schema.columns "
"WHERE table_schema = $1 "
"AND table_name = $2"
*client << "SELECT * \
FROM information_schema.columns \
WHERE table_schema = $1 \
AND table_name = $2"
<< schema << tableName << Mode::Blocking >>
[&](const Result &r) {
if (r.size() == 0)
@ -295,14 +284,14 @@ void create_model::createModelClassFromPG(
exit(1);
};
size_t pkNumber = 0;
*client << "SELECT "
"pg_constraint.conname AS pk_name,"
"pg_constraint.conkey AS pk_vector "
"FROM pg_constraint "
"INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid "
"WHERE "
"pg_class.relname = $1 "
"AND pg_constraint.contype = 'p'"
*client << "SELECT \
pg_constraint.conname AS pk_name,\
pg_constraint.conkey AS pk_vector \
FROM pg_constraint \
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \
WHERE \
pg_class.relname = $1 \
AND pg_constraint.contype = 'p'"
<< tableName << Mode::Blocking >>
[&](bool isNull,
const std::string &pkName,
@ -319,18 +308,16 @@ void create_model::createModelClassFromPG(
data["hasPrimaryKey"] = (int)pkNumber;
if (pkNumber == 1)
{
*client << "SELECT "
"pg_attribute.attname AS colname,"
"pg_type.typname AS typename,"
"pg_constraint.contype AS contype "
"FROM pg_constraint "
"INNER JOIN pg_class ON pg_constraint.conrelid = "
"pg_class.oid "
"INNER JOIN pg_attribute ON pg_attribute.attrelid = "
"pg_class.oid "
"AND pg_attribute.attnum = pg_constraint.conkey [ 1 ] "
"INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid "
"WHERE pg_class.relname = $1 and pg_constraint.contype='p'"
*client << "SELECT \
pg_attribute.attname AS colname,\
pg_type.typname AS typename,\
pg_constraint.contype AS contype \
FROM pg_constraint \
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \
INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid \
AND pg_attribute.attnum = pg_constraint.conkey [ 1 ] \
INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid \
WHERE pg_class.relname = $1 and pg_constraint.contype='p'"
<< tableName << Mode::Blocking >>
[&](bool isNull,
const std::string &colName,
@ -358,20 +345,16 @@ void create_model::createModelClassFromPG(
std::vector<std::string> pkNames, pkTypes, pkValNames;
for (size_t i = 1; i <= pkNumber; ++i)
{
*client << "SELECT "
"pg_attribute.attname AS colname,"
"pg_type.typname AS typename,"
"pg_constraint.contype AS contype "
"FROM pg_constraint "
"INNER JOIN pg_class ON pg_constraint.conrelid = "
"pg_class.oid "
"INNER JOIN pg_attribute ON pg_attribute.attrelid = "
"pg_class.oid "
"AND pg_attribute.attnum = pg_constraint.conkey [ $1 ] "
"INNER JOIN pg_type ON pg_type.oid = "
"pg_attribute.atttypid "
"WHERE pg_class.relname = $2 and "
"pg_constraint.contype='p'"
*client << "SELECT \
pg_attribute.attname AS colname,\
pg_type.typname AS typename,\
pg_constraint.contype AS contype \
FROM pg_constraint \
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \
INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid \
AND pg_attribute.attnum = pg_constraint.conkey [ $1 ] \
INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid \
WHERE pg_class.relname = $2 and pg_constraint.contype='p'"
<< (int)i << tableName << Mode::Blocking >>
[&](bool isNull, std::string colName, const std::string &type) {
if (isNull)
@ -471,7 +454,7 @@ void create_model::createModelClassFromMysql(
data["convertMethods"] = convertMethods;
std::vector<ColumnInfo> cols;
int i = 0;
*client << "desc `" + tableName + "`" << Mode::Blocking >>
*client << "desc " + tableName << Mode::Blocking >>
[&i, &cols](bool isNull,
const std::string &field,
const std::string &type,
@ -826,7 +809,6 @@ void create_model::createModel(const std::string &path,
auto restfulApiConfig = config["restful_api_controllers"];
auto relationships = getRelationships(config["relationships"]);
auto convertMethods = getConvertMethods(config["convert"]);
drogon::utils::createPath(path);
if (dbType == "postgresql")
{
#if USE_POSTGRESQL
@ -1174,9 +1156,7 @@ void create_model::createModel(const std::string &path,
try
{
infile >> configJsonRoot;
createModel(outputPath_.empty() ? path : outputPath_,
configJsonRoot,
singleModelName);
createModel(path, configJsonRoot, singleModelName);
}
catch (const std::exception &exception)
{
@ -1214,22 +1194,6 @@ void create_model::handleCommand(std::vector<std::string> &parameters)
break;
}
}
for (auto iter = parameters.begin(); iter != parameters.end();)
{
auto &file = *iter;
if (file == "-o" || file == "--output")
{
iter = parameters.erase(iter);
if (iter != parameters.end())
{
outputPath_ = *iter;
iter = parameters.erase(iter);
}
continue;
}
++iter;
}
for (auto const &path : parameters)
{
createModel(path, singleModelName);

View File

@ -78,9 +78,6 @@ inline std::string nameTransform(const std::string &origName, bool isType)
return ret;
}
std::string escapeIdentifier(const std::string &identifier,
const std::string &rdbms);
class PivotTable
{
public:
@ -429,6 +426,5 @@ class create_model : public DrObject<create_model>, public CommandHandler
const Json::Value &restfulApiConfig);
std::string dbname_;
bool forceOverwrite_{false};
std::string outputPath_;
};
} // namespace drogon_ctl

View File

@ -411,7 +411,6 @@ void create_view::newViewSourceFile(std::ofstream &file,
"automatically,don't modify it!\n";
file << "#include \"" << namespacePrefix << className << ".h\"\n";
file << "#include <drogon/utils/OStringStream.h>\n";
file << "#include <drogon/utils/Utilities.h>\n";
file << "#include <string>\n";
file << "#include <map>\n";
file << "#include <vector>\n";

View File

@ -19,10 +19,6 @@
#include <memory>
#include <iomanip>
#include <cstdlib>
#include <json/json.h>
#include <fstream>
#include <string>
#include <unordered_map>
#ifndef _WIN32
#include <unistd.h>
#endif
@ -37,10 +33,9 @@ std::string press::detail()
" -t num number of threads(default : 1)\n"
" -c num concurrent connections(default : 1)\n"
" -k disable SSL certificate validation(default: enable)\n"
" -f customize http request json file(default: disenable)\n"
" -q no progress indication(default: show)\n\n"
"example: drogon_ctl press -n 10000 -c 100 -t 4 -q "
"http://localhost:8080/index.html -f ./http_request.json\n";
"http://localhost:8080/index.html\n";
}
void outputErrorAndExit(const std::string_view &err)
@ -156,24 +151,6 @@ void press::handleCommand(std::vector<std::string> &parameters)
continue;
}
}
else if (param.find("-f") == 0)
{
if (param == "-f")
{
++iter;
if (iter == parameters.end())
{
outputErrorAndExit("No http request json file!");
}
httpRequestJsonFile_ = *iter;
continue;
}
else
{
httpRequestJsonFile_ = param.substr(2);
continue;
}
}
else if (param == "-k")
{
certValidation_ = false;
@ -213,118 +190,6 @@ void press::handleCommand(std::vector<std::string> &parameters)
path_ = url_.substr(posOfPath);
}
}
/*
http_request.json
{
"method": "POST",
"header": {
"token": "e2e9d0fe-dd14-4eaf-8ac1-0997730a805d"
},
"body": {
"passwd": "123456",
"account": "10001"
}
}
*/
if (!httpRequestJsonFile_.empty())
{
Json::Value httpRequestJson;
std::ifstream httpRequestFile(httpRequestJsonFile_,
std::ifstream::binary);
if (!httpRequestFile.is_open())
{
outputErrorAndExit(std::string{"No "} + httpRequestJsonFile_);
}
httpRequestFile >> httpRequestJson;
if (!httpRequestJson.isMember("method"))
{
outputErrorAndExit("No contain method");
}
auto methodStr = httpRequestJson["method"].asString();
std::transform(methodStr.begin(),
methodStr.end(),
methodStr.begin(),
::toupper);
auto toHttpMethod = [&]() -> drogon::HttpMethod {
if (methodStr == "GET")
{
return drogon::HttpMethod::Get;
}
else if (methodStr == "POST")
{
return drogon::HttpMethod::Post;
}
else if (methodStr == "HEAD")
{
return drogon::HttpMethod::Head;
}
else if (methodStr == "PUT")
{
return drogon::HttpMethod::Put;
}
else if (methodStr == "DELETE")
{
return drogon::HttpMethod::Delete;
}
else if (methodStr == "OPTIONS")
{
return drogon::HttpMethod::Options;
}
else if (methodStr == "PATCH")
{
return drogon::HttpMethod::Patch;
}
else
{
outputErrorAndExit("invalid method");
}
return drogon::HttpMethod::Get;
};
std::unordered_map<std::string, std::string> header;
if (httpRequestJson.isMember("header"))
{
auto &jsonValue = httpRequestJson["header"];
for (const auto &key : jsonValue.getMemberNames())
{
if (jsonValue[key].isString())
{
header[key] = jsonValue[key].asString();
}
else
{
header[key] = jsonValue[key].toStyledString();
}
}
}
std::string body;
if (httpRequestJson.isMember("body"))
{
Json::FastWriter fastWriter;
body = fastWriter.write(httpRequestJson["body"]);
}
createHttpRequestFunc_ = [this,
method = toHttpMethod(),
body = std::move(body),
header =
std::move(header)]() -> HttpRequestPtr {
auto request = HttpRequest::newHttpRequest();
request->setPath(path_);
request->setMethod(method);
for (const auto &[field, val] : header)
request->addHeader(field, val);
if (!body.empty())
request->setBody(body);
return request;
};
}
// std::cout << "host=" << host_ << std::endl;
// std::cout << "path=" << path_ << std::endl;
doTesting();
@ -367,19 +232,9 @@ void press::sendRequest(const HttpClientPtr &client)
{
return;
}
HttpRequestPtr request;
if (createHttpRequestFunc_)
{
request = createHttpRequestFunc_();
}
else
{
request = HttpRequest::newHttpRequest();
auto request = HttpRequest::newHttpRequest();
request->setPath(path_);
request->setMethod(Get);
}
// std::cout << "send!" << std::endl;
client->sendRequest(
request,

View File

@ -20,7 +20,6 @@
#include <drogon/HttpClient.h>
#include <trantor/utils/Date.h>
#include <trantor/net/EventLoopThreadPool.h>
#include <functional>
#include <string>
#include <atomic>
#include <memory>
@ -63,8 +62,6 @@ class press : public DrObject<press>, public CommandHandler
size_t numOfThreads_{1};
size_t numOfRequests_{1};
size_t numOfConnections_{1};
std::string httpRequestJsonFile_;
std::function<HttpRequestPtr()> createHttpRequestFunc_;
bool certValidation_{true};
bool processIndication_{true};
std::string url_;

View File

@ -39,7 +39,7 @@
"db_clients": [
{
//name: Name of the client,'default' by default
"name": "default",
//"name":"",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
"rdbms": "postgresql",
//filename: Sqlite3 db file name
@ -66,18 +66,15 @@
//timeout: -1.0 by default, in seconds, the timeout for executing a SQL query.
//zero or negative value means no timeout.
"timeout": -1.0,
//auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
//the wiki for more details.
//"auto_batch": this feature is only available for the PostgreSQL driver(version >= 14.0), see
// the wiki for more details.
"auto_batch": false
//connect_options: extra options for the connection. Only works for PostgreSQL now.
//For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
//"connect_options": { "statement_timeout": "1s" }
}
],
"redis_clients": [
{
//name: Name of the client,'default' by default
"name": "default",
//"name":"",
//host: Server IP, 127.0.0.1 by default
"host": "127.0.0.1",
//port: Server port, 6379 by default
@ -106,14 +103,14 @@
//enable_session: False by default
"enable_session": false,
"session_timeout": 0,
//string value of SameSite attribute of the Set-Cookie HTTP response header
//string value of SameSite attribute of the Set-Cookie HTTP respone header
//valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
"session_same_site" : "Null",
//session_cookie_key: The cookie key of the session, "JSESSIONID" by default
"session_cookie_key": "JSESSIONID",
//session_max_age: The max age of the session cookie, -1 by default
"session_max_age": -1,
//document_root: Root path of HTTP document, default path is ./
//document_root: Root path of HTTP document, defaut path is ./
"document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
@ -187,7 +184,7 @@
],
//max_connections: maximum number of connections, 100000 by default
"max_connections": 100000,
//max_connections_per_ip: maximum number of connections per client, 0 by default which means no limit
//max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit
"max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined
@ -218,6 +215,7 @@
//log: Set log output, drogon output logs to stdout by default
"log": {
//use_spdlog: Use spdlog library to log
//"use_spdlog": false
"use_spdlog": false,
//log_path: Log file path,empty by default,in which case,logs are output to the stdout
//"log_path": "./",
@ -310,10 +308,7 @@
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
// will be rejected.
"enabled_compressed_request": false,
// enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
// See the wiki for more details.
"enable_request_stream": false,
"enabled_compressed_request": false
},
//plugins: Define all plugins running in the application
"plugins": [

View File

@ -3,8 +3,8 @@
# ssl:The global SSL settings. "key" and "cert" are the path to the SSL key and certificate. While
# "conf" is an array of 1 or 2-element tuples that supplies file style options for `SSL_CONF_cmd`.
# ssl:
# cert: ../../trantor/trantor/tests/server.crt
# key: ../../trantor/trantor/tests/server.key
# cert: ../../trantor/trantor/tests/server.pem
# key: ../../trantor/trantor/tests/server.pem
# conf: [
# # [Options, -SessionTicket],
# # [Options, Compression]
@ -30,11 +30,11 @@
# ]
# db_clients:
# # name: Name of the client,'default' by default
# - name: default
# - name: ''
# # rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
# rdbms: postgresql
# # filename: Sqlite3 db file name
# # filename: ''
# # filename: '',
# # host: Server address,localhost by default
# host: 127.0.0.1
# # port: Server port, 5432 by default
@ -50,23 +50,19 @@
# is_fast: false
# # client_encoding: The character set used by the client. it is empty string by default which
# # means use the default character set.
# # client_encoding: ''
# # client_encoding: '',
# # number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
# # connections per IO thread, otherwise it is the total number of all connections.
# number_of_connections: 1
# # timeout: -1 by default, in seconds, the timeout for executing a SQL query.
# # zero or negative value means no timeout.
# timeout: -1
# # auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
# # "auto_batch": this feature is only available for the PostgreSQL driver(version >= 14.0), see
# # the wiki for more details.
# auto_batch: false
# # connect_options: extra options for the connection. Only works for PostgreSQL now.
# # For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
# # connect_options:
# # statement_timeout: '1s'
# redis_clients:
# # name: Name of the client,'default' by default
# - name: default
# - name: ''
# # host: Server IP, 127.0.0.1 by default
# host: 127.0.0.1
# # port: Server port, 6379 by default
@ -91,16 +87,16 @@ app:
# is the number of CPU cores
number_of_threads: 1
# enable_session: False by default
enable_session: false
enable_session: true
session_timeout: 0
# string value of SameSite attribute of the Set-Cookie HTTP response header
# string value of SameSite attribute of the Set-Cookie HTTP respone header
# valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
session_same_site: 'Null'
# session_cookie_key: The cookie key of the session, "JSESSIONID" by default
session_cookie_key: 'JSESSIONID'
# session_max_age: The max age of the session cookie, -1 by default
session_max_age: -1
# document_root: Root path of HTTP document, default path is ./
# document_root: Root path of HTTP document, defaut path is ./
document_root: ./
# home_page: Set the HTML file of the home page, the default value is "index.html"
# If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
@ -140,10 +136,8 @@ app:
# mime: A dictionary that extends the internal MIME type support. Maps extensions into new MIME types
# note: This option only adds MIME to the sever. `file_types` above have to be set for the server to serve them.
mime: {
# text/markdown: md
# text/gemini:
# - gmi
# - gemini
# text/markdown: md,
# text/gemini: [gmi, gemini]
}
# locations: An array of locations of static files for GET requests.
locations:
@ -167,7 +161,7 @@ app:
filters: []
# max_connections: maximum number of connections, 100000 by default
max_connections: 100000
# max_connections_per_ip: maximum number of connections per client, 0 by default which means no limit
# max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit
max_connections_per_ip: 0
# Load_dynamic_views: False by default, when set to true, drogon
# compiles and loads dynamically "CSP View Files" in directories defined
@ -196,7 +190,7 @@ app:
# log: Set log output, drogon output logs to stdout by default
log:
# use_spdlog: Use spdlog library to log
use_spdlog: false
use_spdlog: false,
# log_path: Log file path,empty by default,in which case,logs are output to the stdout
# log_path: ./
# logfile_base_name: Log file base name,empty by default which means drogon names logfile as
@ -205,9 +199,6 @@ app:
# log_size_limit: 100000000 bytes by default,
# When the log file size reaches "log_size_limit", the log file is switched.
log_size_limit: 100000000
# max_files: 0 by default,
# When the number of old log files exceeds "max_files", the oldest file will be deleted. 0 means never delete.
max_files: 0
# log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
# The TRACE level is only valid when built in DEBUG mode.
log_level: DEBUG
@ -230,14 +221,14 @@ app:
# 0 means cache forever, the negative value means no cache
static_files_cache_time: 5
# simple_controllers_map: Used to configure mapping from path to simple controller
# simple_controllers_map:
# - path: /path/name
# controller: controllerClassName
# http_methods:
# - get
# - post
# filters:
# - FilterClassName
simple_controllers_map:
- path: /path/name
controller: controllerClassName
http_methods:
- get
- post
filters:
- FilterClassName
# idle_connection_timeout: Defaults to 60 seconds, the lifetime
# of the connection without read or write
idle_connection_timeout: 60
@ -278,36 +269,28 @@ app:
client_max_websocket_message_size: 128K
# reuse_port: Defaults to false, users can run multiple processes listening on the same port at the same time.
reuse_port: false
# enabled_compressed_request: Defaults to false. If true the server will automatically decompress compressed request bodies.
# enabled_compresed_request: Defaults to false. If true the server will automatically decompress compressed request bodies.
# Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
# will be rejected.
enabled_compressed_request: false
# enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
# See the wiki for more details.
enable_request_stream: false
enabled_compresed_request: false
# plugins: Define all plugins running in the application
plugins:
# name: The class name of the plugin
- name: drogon::plugin::PromExporter
- name: '' # drogon::plugin::SecureSSLRedirector
# dependencies: Plugins that the plugin depends on. It can be commented out
dependencies: []
# config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
# It can be commented out
config:
path: /metrics
- name: drogon::plugin::AccessLogger
dependencies: []
config:
use_spdlog: false
log_path: ''
log_format: ''
log_file: access.log
log_size_limit: 0
use_local_time: true
log_index: 0
# show_microseconds: true
# custom_time_format: ''
# use_real_ip: false
ssl_redirect_exempt:
- .*\.jpg
secure_ssl_host: 'localhost:8849'
# custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
custom_config: {}
custom_config:
realm: drogonRealm
opaque: drogonOpaque
credentials:
- user: drogon
password: dr0g0n

View File

@ -76,7 +76,7 @@ else
<%c++for(auto col:cols){
%>
const std::string [[className]]::Cols::_{%col.colName_%} = "{%escapeIdentifier(col.colName_, rdbms)%}";
const std::string [[className]]::Cols::_{%col.colName_%} = "{%col.colName_%}";
<%c++
}%>
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
@ -102,7 +102,7 @@ if(!schema.empty())
{
$$<<schema<<".";
}
%>{%escapeIdentifier(@@.get<std::string>("tableName"), rdbms)%}";
%>[[tableName]]";
const std::vector<typename [[className]]::MetaData> [[className]]::metaData_={
<%c++for(size_t i=0;i<cols.size();i++){
@ -975,7 +975,7 @@ void [[className]]::updateByJson(const Json::Value &pJson) noexcept(false)
{
$$<<"const "<<col.colType_<<" &"<<className<<"::getValueOf"<<col.colTypeName_<<"() const noexcept\n";
$$<<"{\n";
$$<<" static const "<<col.colType_<<" defaultValue = "<<col.colType_<<"();\n";
$$<<" const static "<<col.colType_<<" defaultValue = "<<col.colType_<<"();\n";
$$<<" if("<<col.colValName_<<"_)\n";
$$<<" return *"<<col.colValName_<<"_;\n";
$$<<" return defaultValue;\n";
@ -984,7 +984,7 @@ void [[className]]::updateByJson(const Json::Value &pJson) noexcept(false)
{
$$<<"std::string "<<className<<"::getValueOf"<<col.colTypeName_<<"AsString() const noexcept\n";
$$<<"{\n";
$$<<" static const std::string defaultValue = std::string();\n";
$$<<" const static std::string defaultValue = std::string();\n";
$$<<" if("<<col.colValName_<<"_)\n";
$$<<" return std::string("<<col.colValName_<<"_->data(),"<<col.colValName_<<"_->size());\n";
$$<<" return defaultValue;\n";
@ -1534,7 +1534,8 @@ if(!col.notNull_){%>
if(col.colType_ == "std::string" && col.colLength_>0)
{
%>
if(pJson.isString() && std::strlen(pJson.asCString()) > {%col.colLength_%})
// asString().length() creates a string object, is there any better way to validate the length?
if(pJson.isString() && pJson.asString().length() > {%col.colLength_%})
{
err="String length exceeds limit for the " +
fieldName +
@ -1583,7 +1584,7 @@ for(auto &relationship : relationships)
{
%>
{%relationshipClassName%} [[className]]::get{%alias%}(const DbClientPtr &clientPtr) const {
static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
if(rdbms=="postgresql")
{
$$<<"$1";
@ -1614,7 +1615,7 @@ void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%alind%} const std::function<void({%relationshipClassName%})> &rcb,
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
{
static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
if(rdbms=="postgresql")
{
$$<<"$1";
@ -1647,7 +1648,7 @@ void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{
%>
std::vector<{%relationshipClassName%}> [[className]]::get{%alias%}(const DbClientPtr &clientPtr) const {
static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
if(rdbms=="postgresql")
{
$$<<"$1";
@ -1676,7 +1677,7 @@ void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
{
static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
if(rdbms=="postgresql")
{
$$<<"$1";
@ -1708,7 +1709,7 @@ void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
auto &pivotTargetKey=relationship.pivotTable().targetKey();
%>
std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> [[className]]::get{%alias%}(const DbClientPtr &clientPtr) const {
static const std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
const static std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
if(rdbms=="postgresql")
{
$$<<"$1";
@ -1738,7 +1739,7 @@ void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
{
static const std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
const static std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
if(rdbms=="postgresql")
{
$$<<"$1";

View File

@ -86,11 +86,11 @@ auto cols=@@.get<std::vector<ColumnInfo>>("columns");
%>
};
static const int primaryKeyNumber;
static const std::string tableName;
static const bool hasPrimaryKey;
const static int primaryKeyNumber;
const static std::string tableName;
const static bool hasPrimaryKey;
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
static const std::string primaryKeyName;
const static std::string primaryKeyName;
<%c++if(!@@.get<std::string>("primaryKeyType").empty()){%>
using PrimaryKeyType = [[primaryKeyType]];
const PrimaryKeyType &getPrimaryKey() const;
@ -108,7 +108,7 @@ auto cols=@@.get<std::vector<ColumnInfo>>("columns");
typelist += ",";
}
%>
static const std::vector<std::string> primaryKeyName;
const static std::vector<std::string> primaryKeyName;
using PrimaryKeyType = std::tuple<{%typelist%}>;//<%c++
auto pkName=@@.get<std::vector<std::string>>("primaryKeyName");
for(size_t i=0;i<pkName.size();i++)

View File

@ -31,10 +31,7 @@ add_executable(redis_simple redis/main.cc
add_executable(redis_chat redis_chat/main.cc
redis_chat/controllers/Chat.cc)
add_executable(async_stream async_stream/main.cc
async_stream/RequestStreamExampleCtrl.cc)
add_executable(cors cors/main.cc)
add_executable(async_stream async_stream/main.cc)
set(example_targets
benchmark
@ -47,8 +44,7 @@ set(example_targets
jsonstore
redis_simple
redis_chat
async_stream
cors)
async_stream)
# Add warnings for our example targets--some warnings (such as -Wunused-parameter) only appear
# when the templated functions are instantiated at their point of use.

View File

@ -2,21 +2,19 @@
The following examples can help you understand how to use Drogon:
1. [helloworld](https://github.com/drogonframework/drogon/tree/master/examples/helloworld) - The multiple ways of "Hello, World!"
2. [client_example](https://github.com/drogonframework/drogon/tree/master/examples/client_example/main.cc) - A client example
3. [websocket_client](https://github.com/drogonframework/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client
4. [login_session](https://github.com/drogonframework/drogon/tree/master/examples/login_session) - How to use the built-in session system to handle login and out
5. [file_upload](https://github.com/drogonframework/drogon/tree/master/examples/file_upload) - How to handle file uploads in Drogon
6. [simple_reverse_proxy](https://github.com/drogonframework/drogon/tree/master/examples/simple_reverse_proxy) - An example showing how to use Drogon as a HTTP reverse
proxy with a simple round robin
7. [benchmark](https://github.com/drogonframework/drogon/tree/master/examples/benchmark) - Basic benchmark(https://github.com/drogonframework/drogon/wiki/13-Benchmarks) example
8. [jsonstore](https://github.com/drogonframework/drogon/tree/master/examples/jsonstore) - Implementation of a [jsonstore](https://github.com/bluzi/jsonstore)-like storage service that is concurrent and stores in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon
9. [redis](https://github.com/drogonframework/drogon/tree/master/examples/redis) - A simple example of Redis
10. [websocket_server](https://github.com/drogonframework/drogon/tree/master/examples/websocket_server) - A example websocket chat room server
11. [redis_cache](https://github.com/drogonframework/drogon/tree/master/examples/redis_cache) - An example for using coroutines of Redis clients
12. [redis_chat](https://github.com/drogonframework/drogon/tree/master/examples/redis_chat) - A chatroom server built with websocket and Redis pub/sub service
13. [prometheus_example](https://github.com/drogonframework/drogon/tree/master/examples/prometheus_example) - An example of how to use the Prometheus exporter in Drogon
14. [cors](https://github.com/drogonframework/drogon/tree/master/examples/cors) - An example demonstrating how to implement CORS (Cross-Origin Resource Sharing) support in Drogon
1. [helloworld](https://github.com/an-tao/drogon/tree/master/examples/helloworld) - The multiple ways of "Hello, World!"
2. [client_example](https://github.com/an-tao/drogon/tree/master/examples/client_example/main.cc) - A client example.
3. [websocket_client](https://github.com/an-tao/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client
4. [login_session](https://github.com/an-tao/drogon/tree/master/examples/login_session) - How to use the built-in session system to handle login and out
5. [file_upload](https://github.com/an-tao/drogon/tree/master/examples/file_upload) - How to handle file uploads in Drogon
6. [simple_reverse_proxy](https://github.com/an-tao/drogon/tree/master/examples/simple_reverse_proxy) - A Example showing how to use drogon as a http reverse
proxy with a simple round robin.
7. [benchmark](https://github.com/an-tao/drogon/tree/master/examples/benchmark) - Basic benchmark example. see [wiki benchmarks](https://github.com/an-tao/drogon/wiki/13-Benchmarks)
8. [jsonstore](https://github.com/an-tao/drogon/tree/master/examples/jsonstore) - Implementation of a [jsonstore](https://github.com/bluzi/jsonstore)-like storage service that is concurrent and stores in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon.
9. [redis](https://github.com/an-tao/drogon/tree/master/examples/redis) - A simple example of Redis
10. [websocket_server](https://github.com/drogonframework/drogon/tree/master/examples/websocket_server) - Example WebSocker chat room server
11. [redis_cache](https://github.com/an-tao/drogon/tree/master/examples/redis_cache) - An example for using coroutines of redis clients
12. [redis_chat](https://github.com/an-tao/drogon/tree/master/examples/redis_chat) - A chatroom server built with websocket and redis pub/sub service.
### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite
@ -24,4 +22,4 @@ I created a benchmark suite for the `tfb`, see [here](https://github.com/TechEmp
### Another test suite
I also created a test suite for another web frameworks benchmark repository, see [here](https://github.com/the-benchmarker/web-frameworks/tree/master/cpp/drogon). In this project, Drogon is used as a sub-module (locally include in the project).
I also created a test suite for another web frameworks benchmark repository, see [here](https://github.com/the-benchmarker/web-frameworks/tree/master/cpp/drogon), in this project, drogon is used as a sub-module (locally include in the project).

View File

@ -1,167 +0,0 @@
#include <drogon/drogon.h>
#include <drogon/HttpController.h>
#include <drogon/HttpRequest.h>
#include <fstream>
using namespace drogon;
class StreamEchoReader : public RequestStreamReader
{
public:
StreamEchoReader(ResponseStreamPtr respStream)
: respStream_(std::move(respStream))
{
}
void onStreamData(const char *data, size_t length) override
{
LOG_INFO << "onStreamData[" << length << "]";
respStream_->send({data, length});
}
void onStreamFinish(std::exception_ptr ptr) override
{
if (ptr)
{
try
{
std::rethrow_exception(ptr);
}
catch (const std::exception &e)
{
LOG_ERROR << "onStreamError: " << e.what();
}
}
else
{
LOG_INFO << "onStreamFinish";
}
respStream_->close();
}
private:
ResponseStreamPtr respStream_;
};
class RequestStreamExampleCtrl : public HttpController<RequestStreamExampleCtrl>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(RequestStreamExampleCtrl::stream_echo, "/stream_echo", Post);
ADD_METHOD_TO(RequestStreamExampleCtrl::stream_upload,
"/stream_upload",
Post);
METHOD_LIST_END
void stream_echo(
const HttpRequestPtr &,
RequestStreamPtr &&stream,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
auto resp = drogon::HttpResponse::newAsyncStreamResponse(
[stream](ResponseStreamPtr respStream) {
stream->setStreamReader(
std::make_shared<StreamEchoReader>(std::move(respStream)));
});
callback(resp);
}
void stream_upload(
const HttpRequestPtr &req,
RequestStreamPtr &&stream,
std::function<void(const HttpResponsePtr &)> &&callback) const
{
struct Entry
{
MultipartHeader header;
std::string tmpName;
std::ofstream file;
};
auto files = std::make_shared<std::vector<Entry>>();
auto reader = RequestStreamReader::newMultipartReader(
req,
[files](MultipartHeader &&header) {
LOG_INFO << "Multipart name: " << header.name
<< ", filename:" << header.filename
<< ", contentType:" << header.contentType;
files->push_back({std::move(header)});
auto tmpName = drogon::utils::genRandomString(40);
if (!files->back().header.filename.empty())
{
files->back().tmpName = tmpName;
files->back().file.open("uploads/" + tmpName,
std::ios::trunc);
}
},
[files](const char *data, size_t length) {
if (files->back().tmpName.empty())
{
return;
}
auto &currentFile = files->back().file;
if (length == 0)
{
LOG_INFO << "file finish";
if (currentFile.is_open())
{
currentFile.flush();
currentFile.close();
}
return;
}
LOG_INFO << "data[" << length << "]: ";
if (currentFile.is_open())
{
LOG_INFO << "write file";
currentFile.write(data, length);
}
else
{
LOG_ERROR << "file not open";
}
},
[files, callback = std::move(callback)](std::exception_ptr ex) {
if (ex)
{
try
{
std::rethrow_exception(std::move(ex));
}
catch (const StreamError &e)
{
LOG_ERROR << "stream error: " << e.what();
}
catch (const std::exception &e)
{
LOG_ERROR << "multipart error: " << e.what();
}
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
resp->setBody("error\n");
callback(resp);
}
else
{
LOG_INFO << "stream finish, received " << files->size()
<< " files";
Json::Value respJson;
for (const auto &item : *files)
{
if (item.tmpName.empty())
continue;
Json::Value entry;
entry["name"] = item.header.name;
entry["filename"] = item.header.filename;
entry["tmpName"] = item.tmpName;
respJson.append(entry);
}
auto resp = HttpResponse::newHttpJsonResponse(respJson);
callback(resp);
}
});
stream->setStreamReader(std::move(reader));
}
};

View File

@ -1,33 +1,15 @@
#include <drogon/drogon.h>
#include <chrono>
#include <functional>
#include <mutex>
#include <unordered_map>
#include <trantor/utils/Logger.h>
#include <trantor/net/callbacks.h>
#include <trantor/net/TcpConnection.h>
#include <memory>
using namespace drogon;
using namespace std::chrono_literals;
std::mutex mutex;
std::unordered_map<trantor::TcpConnectionPtr, std::function<void()>>
connMapping;
int main()
{
app().registerHandler(
"/stream",
[](const HttpRequestPtr &req,
[](const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&callback) {
const auto &weakConnPtr = req->getConnectionPtr();
if (auto connPtr = weakConnPtr.lock())
{
std::lock_guard lk(mutex);
connMapping.emplace(std::move(connPtr), [] {
LOG_INFO << "call stop or other options!!!!";
});
}
auto resp = drogon::HttpResponse::newAsyncStreamResponse(
[](drogon::ResponseStreamPtr stream) {
std::thread([stream =
@ -46,68 +28,6 @@ int main()
callback(resp);
});
// Example: register a stream-mode function handler
app().registerHandler(
"/stream_req",
[](const HttpRequestPtr &req,
RequestStreamPtr &&stream,
std::function<void(const HttpResponsePtr &)> &&callback) {
if (!stream)
{
LOG_INFO << "stream mode is not enabled";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
resp->setBody("no stream");
callback(resp);
return;
}
auto reader = RequestStreamReader::newReader(
[](const char *data, size_t length) {
LOG_INFO << "piece[" << length
<< "]: " << std::string_view{data, length};
},
[callback = std::move(callback)](std::exception_ptr ex) {
auto resp = HttpResponse::newHttpResponse();
if (ex)
{
try
{
std::rethrow_exception(std::move(ex));
}
catch (const std::exception &e)
{
LOG_ERROR << "stream error: " << e.what();
}
resp->setStatusCode(k400BadRequest);
resp->setBody("error\n");
callback(resp);
}
else
{
LOG_INFO << "stream finish";
resp->setBody("success\n");
callback(resp);
}
});
stream->setStreamReader(std::move(reader));
},
{Post});
LOG_INFO << "Server running on 127.0.0.1:8848";
app().enableRequestStream(); // This is for request stream.
app().setConnectionCallback([](const trantor::TcpConnectionPtr &conn) {
if (conn->disconnected())
{
std::lock_guard lk(mutex);
if (auto it = connMapping.find(conn); it != connMapping.end())
{
LOG_INFO << "disconnect";
connMapping[conn]();
connMapping.erase(conn);
}
}
});
app().addListener("127.0.0.1", 8848).run();
}

View File

@ -5,7 +5,7 @@ using namespace drogon;
class BenchmarkCtrl : public drogon::HttpSimpleController<BenchmarkCtrl>
{
public:
void asyncHandleHttpRequest(
virtual void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN

View File

@ -6,6 +6,6 @@ void JsonCtrl::asyncHandleHttpRequest(
{
Json::Value ret;
ret["message"] = "Hello, World!";
auto resp = HttpResponse::newHttpJsonResponse(std::move(ret));
auto resp = HttpResponse::newHttpJsonResponse(ret);
callback(resp);
}

View File

@ -5,7 +5,7 @@ using namespace drogon;
class JsonCtrl : public drogon::HttpSimpleController<JsonCtrl>
{
public:
void asyncHandleHttpRequest(
virtual void asyncHandleHttpRequest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN

View File

@ -72,8 +72,6 @@ int main()
std::cout << "count=" << nth_resp << std::endl;
});
}
std::cout << "requestsBufferSize:" << client->requestsBufferSize()
<< std::endl;
}
app().run();

View File

@ -1,153 +0,0 @@
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpResponse.h>
#include <drogon/drogon.h>
#include "trantor/utils/Logger.h"
using namespace drogon;
/// Configure Cross-Origin Resource Sharing (CORS) support.
///
/// This function registers both synchronous pre-processing advice for handling
/// OPTIONS preflight requests and post-handling advice to inject CORS headers
/// into all responses dynamically based on the incoming request headers.
void setupCors()
{
// Register sync advice to handle CORS preflight (OPTIONS) requests
drogon::app().registerSyncAdvice([](const drogon::HttpRequestPtr &req)
-> drogon::HttpResponsePtr {
if (req->method() == drogon::HttpMethod::Options)
{
auto resp = drogon::HttpResponse::newHttpResponse();
// Set Access-Control-Allow-Origin header based on the Origin
// request header
const auto &origin = req->getHeader("Origin");
if (!origin.empty())
{
resp->addHeader("Access-Control-Allow-Origin", origin);
}
// Set Access-Control-Allow-Methods based on the requested method
const auto &requestMethod =
req->getHeader("Access-Control-Request-Method");
if (!requestMethod.empty())
{
resp->addHeader("Access-Control-Allow-Methods", requestMethod);
}
// Allow credentials to be included in cross-origin requests
resp->addHeader("Access-Control-Allow-Credentials", "true");
// Set allowed headers from the Access-Control-Request-Headers
// header
const auto &requestHeaders =
req->getHeader("Access-Control-Request-Headers");
if (!requestHeaders.empty())
{
resp->addHeader("Access-Control-Allow-Headers", requestHeaders);
}
return std::move(resp);
}
return {};
});
// Register post-handling advice to add CORS headers to all responses
drogon::app().registerPostHandlingAdvice(
[](const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) -> void {
// Set Access-Control-Allow-Origin based on the Origin request
// header
const auto &origin = req->getHeader("Origin");
if (!origin.empty())
{
resp->addHeader("Access-Control-Allow-Origin", origin);
}
// Reflect the requested Access-Control-Request-Method back in the
// response
const auto &requestMethod =
req->getHeader("Access-Control-Request-Method");
if (!requestMethod.empty())
{
resp->addHeader("Access-Control-Allow-Methods", requestMethod);
}
// Allow credentials to be included in cross-origin requests
resp->addHeader("Access-Control-Allow-Credentials", "true");
// Reflect the requested Access-Control-Request-Headers back
const auto &requestHeaders =
req->getHeader("Access-Control-Request-Headers");
if (!requestHeaders.empty())
{
resp->addHeader("Access-Control-Allow-Headers", requestHeaders);
}
});
}
/**
* Main function to start the Drogon application with CORS-enabled routes.
* This example includes:
* - A simple GET endpoint `/hello` that returns a greeting message.
* - A POST endpoint `/echo` that echoes back the request body.
* You can test with curl to test the CORS support:
*
```
curl -i -X OPTIONS http://localhost:8000/echo \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type"
```
or
```
curl -i -X POST http://localhost:8000/echo \
-H "Origin: http://localhost:3000" \
-H "Content-Type: application/json" \
-d '{"key":"value"}'
```
*/
int main()
{
// Listen on port 8000 for all interfaces
app().addListener("0.0.0.0", 8000);
// Setup CORS support
setupCors();
// Register /hello route for GET and OPTIONS methods
app().registerHandler(
"/hello",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
auto resp = HttpResponse::newHttpResponse();
resp->setBody("Hello from Drogon!");
// Log client IP address
LOG_INFO << "Request to /hello from " << req->getPeerAddr().toIp();
callback(resp);
},
{Get, Options});
// Register /echo route for POST and OPTIONS methods
app().registerHandler(
"/echo",
[](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
auto resp = HttpResponse::newHttpResponse();
resp->setBody(std::string("Echo: ").append(req->getBody()));
// Log client IP and request body
LOG_INFO << "Request to /echo from " << req->getPeerAddr().toIp();
LOG_INFO << "Echo content: " << req->getBody();
callback(resp);
},
{Post, Options});
// Start the application main loop
app().run();
return 0;
}

View File

@ -1,11 +1,3 @@
#include <trantor/utils/Logger.h>
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <netinet/tcp.h>
#include <sys/socket.h>
#endif
#include <drogon/drogon.h>
using namespace drogon;
@ -16,10 +8,8 @@ int main()
// sent to Drogon
app().registerHandler(
"/",
[](const HttpRequestPtr &request,
[](const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&callback) {
LOG_INFO << "connected:"
<< (request->connected() ? "true" : "false");
auto resp = HttpResponse::newHttpResponse();
resp->setBody("Hello, World!");
callback(resp);
@ -71,23 +61,6 @@ int main()
},
{Get});
app()
.setBeforeListenSockOptCallback([](int fd) {
LOG_INFO << "setBeforeListenSockOptCallback:" << fd;
#ifdef _WIN32
#elif __linux__
int enable = 1;
if (setsockopt(
fd, IPPROTO_TCP, TCP_FASTOPEN, &enable, sizeof(enable)) ==
-1)
{
LOG_INFO << "setsockopt TCP_FASTOPEN failed";
}
#else
#endif
})
.setAfterAcceptSockOptCallback([](int) {});
// Ask Drogon to listen on 127.0.0.1 port 8848. Drogon supports listening
// on multiple IP addresses by adding multiple listeners. For example, if
// you want the server also listen on 127.0.0.1 port 5555. Just add another

View File

@ -1,561 +0,0 @@
# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,visualstudio,visualstudiocode,cmake,c,c++
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,visualstudio,visualstudiocode,cmake,c,c++
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
### C++ ###
# Prerequisites
# Compiled Object files
*.slo
# Precompiled Headers
# Linker files
# Debugger Files
# Compiled Dynamic libraries
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
# Executables
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
CMakeUserPresets.json
### CMake Patch ###
# External projects
*-prefix/
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
# Sonarlint plugin
.idea/sonarlint
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.meta
*.iobj
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*[.json, .xml, .info]
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
### VisualStudio Patch ###
# Additional files built by Visual Studio
*.tlog
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,visualstudio,visualstudiocode,cmake,c,c++

View File

@ -1,75 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(prometheus_example CXX)
include(CheckIncludeFileCXX)
check_include_file_cxx(any HAS_ANY)
check_include_file_cxx(string_view HAS_STRING_VIEW)
check_include_file_cxx(coroutine HAS_COROUTINE)
if (NOT "${CMAKE_CXX_STANDARD}" STREQUAL "")
# Do nothing
elseif (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
set(CMAKE_CXX_STANDARD 20)
elseif (HAS_ANY AND HAS_STRING_VIEW)
set(CMAKE_CXX_STANDARD 17)
else ()
set(CMAKE_CXX_STANDARD 14)
endif ()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_executable(${PROJECT_NAME} main.cc)
# ##############################################################################
# If you include the drogon source code locally in your project, use this method
# to add drogon
# add_subdirectory(drogon)
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
#
# and comment out the following lines
find_package(Drogon CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
# ##############################################################################
if (CMAKE_CXX_STANDARD LESS 17)
message(FATAL_ERROR "c++17 or higher is required")
elseif (CMAKE_CXX_STANDARD LESS 20)
message(STATUS "use c++17")
else ()
message(STATUS "use c++20")
endif ()
aux_source_directory(controllers CTL_SRC)
aux_source_directory(filters FILTER_SRC)
aux_source_directory(plugins PLUGIN_SRC)
aux_source_directory(models MODEL_SRC)
drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
${CMAKE_CURRENT_BINARY_DIR})
# use the following line to create views with namespaces.
# drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
# ${CMAKE_CURRENT_BINARY_DIR} TRUE)
# use the following line to create views with namespace CHANGE_ME prefixed
# and path namespaces.
# drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
# ${CMAKE_CURRENT_BINARY_DIR} TRUE CHANGE_ME)
target_include_directories(${PROJECT_NAME}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/models)
target_sources(${PROJECT_NAME}
PRIVATE
${SRC_DIR}
${CTL_SRC}
${FILTER_SRC}
${PLUGIN_SRC}
${MODEL_SRC})
# ##############################################################################
# uncomment the following line for dynamically loading views
# set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)
# ##############################################################################
add_subdirectory(test)

View File

@ -1,33 +0,0 @@
{
"listeners": [
{
"address": "0.0.0.0",
"port": 5555,
"https": false
}
],
"plugins": [
{
"name": "drogon::plugin::PromExporter",
"dependencies": [],
"config": {
"path": "/metrics",
"collectors":[
{
"name": "http_requests_total",
"help": "The total number of http requests",
"type": "counter",
"labels": ["method", "path"]
},
{
"name": "http_request_duration_seconds",
"help": "The processing time of http requests, in seconds",
"type": "histogram",
"labels": ["method", "path"]
}
]
}
}
]
}

View File

@ -1,27 +0,0 @@
#include "PromTestCtrl.h"
using namespace drogon;
void PromTestCtrl::fast(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto resp = HttpResponse::newHttpResponse();
resp->setBody("Hello, world!");
callback(resp);
}
drogon::AsyncTask PromTestCtrl::slow(
const HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback)
{
// sleep for a random time between 1 and 3 seconds
static std::once_flag flag;
std::call_once(flag, []() { srand(time(nullptr)); });
auto duration = 1 + (rand() % 3);
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
co_await drogon::sleepCoro(loop, std::chrono::seconds(duration));
auto resp = HttpResponse::newHttpResponse();
resp->setBody("Hello, world!");
callback(resp);
co_return;
}

View File

@ -1,21 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
#include <drogon/utils/coroutine.h>
using namespace drogon;
class PromTestCtrl : public drogon::HttpController<PromTestCtrl>
{
public:
METHOD_LIST_BEGIN
ADD_METHOD_TO(PromTestCtrl::fast, "/fast", "PromStat");
ADD_METHOD_TO(PromTestCtrl::slow, "/slow", "PromStat");
METHOD_LIST_END
void fast(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
drogon::AsyncTask slow(
const HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback);
};

View File

@ -1,52 +0,0 @@
/**
*
* PromStat.cc
*
*/
#include "PromStat.h"
#include <drogon/plugins/PromExporter.h>
#include <drogon/utils/monitoring/Counter.h>
#include <drogon/utils/monitoring/Histogram.h>
#include <drogon/HttpAppFramework.h>
#include <chrono>
using namespace std::literals::chrono_literals;
using namespace drogon;
Task<HttpResponsePtr> PromStat::invoke(const HttpRequestPtr &req,
MiddlewareNextAwaiter &&next)
{
std::string path{req->matchedPathPattern()};
auto method = req->methodString();
auto promExporter = app().getPlugin<drogon::plugin::PromExporter>();
if (promExporter)
{
auto collector =
promExporter->getCollector<drogon::monitoring::Counter>(
"http_requests_total");
if (collector)
{
collector->metric({method, path})->increment();
}
}
auto start = trantor::Date::date();
auto resp = co_await next;
if (promExporter)
{
auto collector =
promExporter->getCollector<drogon::monitoring::Histogram>(
"http_request_duration_seconds");
if (collector)
{
static const std::vector<double> boundaries{
0.0001, 0.001, 0.01, 0.1, 0.5, 1, 2, 3};
auto end = trantor::Date::date();
auto duration =
end.microSecondsSinceEpoch() - start.microSecondsSinceEpoch();
collector->metric({method, path}, boundaries, 1h, 6)
->observe((double)duration / 1000000);
}
}
co_return resp;
}

View File

@ -1,27 +0,0 @@
/**
*
* PromStat.h
*
*/
#pragma once
#include <drogon/HttpMiddleware.h>
using namespace drogon;
class PromStat : public HttpCoroMiddleware<PromStat>
{
public:
PromStat()
{
void(0);
}
virtual ~PromStat()
{
}
Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
MiddlewareNextAwaiter &&next) override;
};

View File

@ -1,7 +0,0 @@
#include <drogon/drogon.h>
int main()
{
drogon::app().loadConfigFile("../config.json").run();
return 0;
}

View File

@ -1,14 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(prometheus_example_test CXX)
add_executable(${PROJECT_NAME} test_main.cc)
# ##############################################################################
# If you include the drogon source code locally in your project, use this method
# to add drogon
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
#
# and comment out the following lines
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
ParseAndAddDrogonTests(${PROJECT_NAME})

View File

@ -1,32 +0,0 @@
#define DROGON_TEST_MAIN
#include <drogon/drogon_test.h>
#include <drogon/drogon.h>
DROGON_TEST(BasicTest)
{
// Add your tests here
}
int main(int argc, char **argv)
{
using namespace drogon;
std::promise<void> p1;
std::future<void> f1 = p1.get_future();
// Start the main loop on another thread
std::thread thr([&]() {
// Queues the promise to be fulfilled after starting the loop
app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
app().run();
});
// The future is only satisfied after the event loop started
f1.get();
int status = test::run(argc, argv);
// Ask the event loop to shutdown and wait
app().getLoop()->queueInLoop([]() { app().quit(); });
thr.join();
return status;
}

View File

@ -7,12 +7,13 @@ using namespace drogon;
class WsClient : public drogon::WebSocketController<WsClient>
{
public:
void handleNewMessage(const WebSocketConnectionPtr &,
virtual void handleNewMessage(const WebSocketConnectionPtr &,
std::string &&,
const WebSocketMessageType &) override;
void handleNewConnection(const HttpRequestPtr &,
virtual void handleNewConnection(const HttpRequestPtr &,
const WebSocketConnectionPtr &) override;
virtual void handleConnectionClosed(
const WebSocketConnectionPtr &) override;
void handleConnectionClosed(const WebSocketConnectionPtr &) override;
WS_PATH_LIST_BEGIN
WS_PATH_ADD("/sub");
WS_PATH_ADD("/pub");

View File

@ -10,7 +10,7 @@ using namespace drogon;
class TimeFilter : public drogon::HttpFilter<TimeFilter>
{
public:
void doFilter(const HttpRequestPtr &req,
virtual void doFilter(const HttpRequestPtr &req,
FilterCallback &&cb,
FilterChainCallback &&ccb) override;

View File

@ -7,12 +7,13 @@ using namespace drogon;
class Chat : public drogon::WebSocketController<Chat>
{
public:
void handleNewMessage(const WebSocketConnectionPtr &,
virtual void handleNewMessage(const WebSocketConnectionPtr &,
std::string &&,
const WebSocketMessageType &) override;
void handleNewConnection(const HttpRequestPtr &,
virtual void handleNewConnection(const HttpRequestPtr &,
const WebSocketConnectionPtr &) override;
virtual void handleConnectionClosed(
const WebSocketConnectionPtr &) override;
void handleConnectionClosed(const WebSocketConnectionPtr &) override;
WS_PATH_LIST_BEGIN
WS_PATH_ADD("/chat");
WS_PATH_LIST_END

View File

@ -6,15 +6,15 @@ using namespace drogon;
class WebSocketChat : public drogon::WebSocketController<WebSocketChat>
{
public:
void handleNewMessage(const WebSocketConnectionPtr &,
virtual void handleNewMessage(const WebSocketConnectionPtr &,
std::string &&,
const WebSocketMessageType &) override;
void handleConnectionClosed(const WebSocketConnectionPtr &) override;
void handleNewConnection(const HttpRequestPtr &,
virtual void handleConnectionClosed(
const WebSocketConnectionPtr &) override;
virtual void handleNewConnection(const HttpRequestPtr &,
const WebSocketConnectionPtr &) override;
WS_PATH_LIST_BEGIN
WS_PATH_ADD("/chat", Get);
WS_ADD_PATH_VIA_REGEX("/[^/]*", Get);
WS_PATH_LIST_END
private:
PubSubService<std::string> chatRooms_;

View File

@ -3,10 +3,10 @@
# You can customize the clang-format path by setting the CLANG_FORMAT environment variable
CLANG_FORMAT=${CLANG_FORMAT:-clang-format}
# Check if clang-format version is 17 to avoid inconsistent formatting
# Check if clang-format version is 14 to avoid inconsistent formatting
$CLANG_FORMAT --version
if [[ ! $($CLANG_FORMAT --version) =~ "version 17" ]]; then
echo "Error: clang-format version must be 17"
if [[ ! $($CLANG_FORMAT --version) =~ "version 14" ]]; then
echo "Error: clang-format version must be 14"
exit 1
fi

View File

@ -39,7 +39,7 @@ class Attributes
template <typename T>
const T &get(const std::string &key) const
{
static const T nullVal = T();
const static T nullVal = T();
auto it = attributesMap_.find(key);
if (it != attributesMap_.end())
{

View File

@ -36,7 +36,12 @@ class DROGON_EXPORT Cookie
* @param key key of the cookie
* @param value value of the cookie
*/
Cookie(std::string key, std::string value)
Cookie(const std::string &key, const std::string &value)
: key_(key), value_(value)
{
}
Cookie(std::string &&key, std::string &&value)
: key_(std::move(key)), value_(std::move(value))
{
}
@ -84,9 +89,6 @@ class DROGON_EXPORT Cookie
domain_ = domain;
}
/**
* @brief Set the domain of the cookie.
*/
void setDomain(std::string &&domain)
{
domain_ = std::move(domain);
@ -100,9 +102,6 @@ class DROGON_EXPORT Cookie
path_ = path;
}
/**
* @brief Set the path of the cookie.
*/
void setPath(std::string &&path)
{
path_ = std::move(path);
@ -116,9 +115,6 @@ class DROGON_EXPORT Cookie
key_ = key;
}
/**
* @brief Set the key of the cookie.
*/
void setKey(std::string &&key)
{
key_ = std::move(key);
@ -132,9 +128,6 @@ class DROGON_EXPORT Cookie
value_ = value;
}
/**
* @brief Set the value of the cookie.
*/
void setValue(std::string &&value)
{
value_ = std::move(value);
@ -156,18 +149,6 @@ class DROGON_EXPORT Cookie
sameSite_ = sameSite;
}
/**
* @brief Set the partitioned status of the cookie
*/
void setPartitioned(bool partitioned)
{
partitioned_ = partitioned;
if (partitioned)
{
setSecure(true);
}
}
/**
* @brief Get the string value of the cookie
*/
@ -294,17 +275,6 @@ class DROGON_EXPORT Cookie
return secure_;
}
/**
* @brief Check if the cookie is partitioned.
*
* @return true means the cookie is partitioned.
* @return false means the cookie is not partitioned.
*/
bool isPartitioned() const
{
return partitioned_;
}
/**
* @brief Get the max-age of the cookie
*/
@ -373,15 +343,21 @@ class DROGON_EXPORT Cookie
* @brief Converts a string value to its associated enum class SameSite
* value
*/
static SameSite convertString2SameSite(std::string_view sameSite)
static SameSite convertString2SameSite(const std::string_view &sameSite)
{
if (stricmp(sameSite, "lax"))
{
return Cookie::SameSite::kLax;
if (stricmp(sameSite, "strict"))
}
else if (stricmp(sameSite, "strict"))
{
return Cookie::SameSite::kStrict;
if (stricmp(sameSite, "none"))
}
else if (stricmp(sameSite, "none"))
{
return Cookie::SameSite::kNone;
if (!stricmp(sameSite, "null"))
}
else if (!stricmp(sameSite, "null"))
{
LOG_WARN
<< "'" << sameSite
@ -396,20 +372,34 @@ class DROGON_EXPORT Cookie
* @brief Converts an enum class SameSite value to its associated string
* value
*/
static std::string_view convertSameSite2String(SameSite sameSite)
static const std::string_view &convertSameSite2String(SameSite sameSite)
{
switch (sameSite)
{
case SameSite::kLax:
return "Lax";
{
static std::string_view sv{"Lax"};
return sv;
}
case SameSite::kStrict:
return "Strict";
{
static std::string_view sv{"Strict"};
return sv;
}
case SameSite::kNone:
return "None";
{
static std::string_view sv{"None"};
return sv;
}
case SameSite::kNull:
return "Null";
default:
return "UNDEFINED";
{
static std::string_view sv{"Null"};
return sv;
}
}
{
static std::string_view sv{"UNDEFINED"};
return sv;
}
}
@ -417,7 +407,6 @@ class DROGON_EXPORT Cookie
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
bool httpOnly_{true};
bool secure_{false};
bool partitioned_{false};
std::string domain_;
std::string path_;
std::string key_;

View File

@ -138,7 +138,7 @@ class DROGON_EXPORT DrClassMap
protected:
static std::unordered_map<std::string,
std::pair<DrAllocFunc, DrSharedAllocFunc>> &
getMap();
std::pair<DrAllocFunc, DrSharedAllocFunc>>
&getMap();
};
} // namespace drogon

View File

@ -61,8 +61,8 @@ template <typename T>
struct isAutoCreationClass
{
template <class C>
static constexpr auto check(C *) -> std::enable_if_t<
std::is_same_v<decltype(C::isAutoCreation), const bool>,
static constexpr auto check(C *)
-> std::enable_if_t<std::is_same_v<decltype(C::isAutoCreation), bool>,
bool>
{
return C::isAutoCreation;

View File

@ -30,7 +30,6 @@
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/orm/DbClient.h>
#include <drogon/orm/DbConfig.h>
#include <drogon/nosql/RedisClient.h>
#include <drogon/Cookie.h>
#include <trantor/net/Resolver.h>
@ -43,16 +42,6 @@
#include <vector>
#include <chrono>
#if defined(__APPLE__) && defined(__MACH__) && \
(defined(__ENVIRONMENT_IPHONE_OS__) || \
defined(__IPHONE_OS_VERSION_MIN_REQUIRED))
// iOS
#define TARGET_OS_IOS 1
#else
// not iOS
#define TARGET_OS_IOS 0
#endif
namespace drogon
{
// the drogon banner
@ -205,8 +194,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* returned.
*/
virtual const std::function<HttpResponsePtr(HttpStatusCode,
const HttpRequestPtr &req)> &
getCustomErrorHandler() const = 0;
const HttpRequestPtr &req)>
&getCustomErrorHandler() const = 0;
/// Get the plugin object registered in the framework
/**
@ -333,7 +322,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
[Check Method]---------------->[405]----------->+
| |
v |
[Filters/Middlewares]------>[Filter callback]------>+
[Filters]------->[Filter callback]----------->+
| |
v Y |
[Is OPTIONS method?]------------->[200]----------->+
@ -346,9 +335,6 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
| |
v |
Post-handling join point o---------------------------------------->+
| |
v |
[Middlewares post logic]--->[Middleware callback]--->+
@endcode
*
@ -359,7 +345,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Register an advice called before routing
/**
* @param advice is called after all the synchronous advice return
* @param advice is called after all the synchronous advices return
* nullptr and before the request is routed to any handler. The parameters
* of the advice are same as those of the doFilter method of the Filter
* class.
@ -382,7 +368,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Register an advice called after routing
/**
* @param advice is called immediately after the request matches a handler
* path and before any filters/middlewares applies. The parameters
* path and before any 'doFilter' method of filters applies. The parameters
* of the advice are same as those of the doFilter method of the Filter
* class.
*/
@ -404,8 +390,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Register an advice called before the request is handled
/**
* @param advice is called immediately after the request is approved by all
* filters/middlewares and before it is handled. The parameters of the
* advice are same as those of the doFilter method of the Filter class.
* filters and before it is handled. The parameters of the advice are
* same as those of the doFilter method of the Filter class.
*/
virtual HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
@ -486,8 +472,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* called.
* @param ctrlName is the name of the controller. It includes the namespace
* to which the controller belongs.
* @param constraints is a vector containing Http methods or middleware
names
* @param filtersAndMethods is a vector containing Http methods or filter
* name constraints.
*
* Example:
* @code
@ -501,7 +487,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &registerHttpSimpleController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints = {}) = 0;
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{}) = 0;
/// Register a handler into the framework.
/**
@ -509,7 +496,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* pathPattern, the handler indicated by the function parameter is called.
* @param function indicates any type of callable object with a valid
* processing interface.
* @param constraints is the same as the third parameter in the above
* @param filtersAndMethods is the same as the third parameter in the above
* method.
*
* Example:
@ -535,7 +522,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
HttpAppFramework &registerHandler(
const std::string &pathPattern,
FUNCTION &&function,
const std::vector<internal::HttpConstraint> &constraints = {},
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "")
{
LOG_TRACE << "pathPattern:" << pathPattern;
@ -545,16 +533,17 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
getLoop()->queueInLoop([binder]() { binder->createHandlerInstance(); });
std::vector<HttpMethod> validMethods;
std::vector<std::string> middlewares;
for (auto const &constraint : constraints)
std::vector<std::string> filters;
for (auto const &filterOrMethod : filtersAndMethods)
{
if (constraint.type() == internal::ConstraintType::HttpMiddleware)
if (filterOrMethod.type() == internal::ConstraintType::HttpFilter)
{
middlewares.push_back(constraint.getMiddlewareName());
filters.push_back(filterOrMethod.getFilterName());
}
else if (constraint.type() == internal::ConstraintType::HttpMethod)
else if (filterOrMethod.type() ==
internal::ConstraintType::HttpMethod)
{
validMethods.push_back(constraint.getHttpMethod());
validMethods.push_back(filterOrMethod.getHttpMethod());
}
else
{
@ -563,7 +552,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
}
}
registerHttpController(
pathPattern, binder, validMethods, middlewares, handlerName);
pathPattern, binder, validMethods, filters, handlerName);
return *this;
}
@ -577,8 +566,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* subexpression is sequentially mapped to a handler parameter.
* @param function indicates any type of callable object with a valid
* processing interface.
* @param constraints is the same as the third parameter in the
* above method.
* @param filtersAndMethods is the same as the third parameter in the above
* method.
* @param handlerName a name for the handler.
* @return HttpAppFramework&
*/
@ -586,7 +575,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
HttpAppFramework &registerHandlerViaRegex(
const std::string &regExp,
FUNCTION &&function,
const std::vector<internal::HttpConstraint> &constraints = {},
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "")
{
LOG_TRACE << "regex:" << regExp;
@ -596,16 +586,17 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
std::forward<FUNCTION>(function));
std::vector<HttpMethod> validMethods;
std::vector<std::string> middlewares;
for (auto const &constraint : constraints)
std::vector<std::string> filters;
for (auto const &filterOrMethod : filtersAndMethods)
{
if (constraint.type() == internal::ConstraintType::HttpMiddleware)
if (filterOrMethod.type() == internal::ConstraintType::HttpFilter)
{
middlewares.push_back(constraint.getMiddlewareName());
filters.push_back(filterOrMethod.getFilterName());
}
else if (constraint.type() == internal::ConstraintType::HttpMethod)
else if (filterOrMethod.type() ==
internal::ConstraintType::HttpMethod)
{
validMethods.push_back(constraint.getHttpMethod());
validMethods.push_back(filterOrMethod.getHttpMethod());
}
else
{
@ -614,7 +605,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
}
}
registerHttpControllerViaRegex(
regExp, binder, validMethods, middlewares, handlerName);
regExp, binder, validMethods, filters, handlerName);
return *this;
}
@ -626,18 +617,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &registerWebSocketController(
const std::string &pathName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints = {}) = 0;
/// Register a WebSocketController into the framework.
/**
* The parameters of this method are the same as those in the
* registerHttpSimpleController() method but using regular
* expression string for path.
*/
virtual HttpAppFramework &registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints =
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{}) = 0;
/// Register controller objects created and initialized by the user
@ -701,24 +681,6 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
return *this;
}
/// Register middleware objects created and initialized by the user
/**
* This method is similar to the above method.
*/
template <typename T>
HttpAppFramework &registerMiddleware(
const std::shared_ptr<T> &middlewarePtr)
{
static_assert(std::is_base_of<HttpMiddlewareBase, T>::value,
"Error! Only middleware objects can be registered here");
static_assert(!T::isAutoCreation,
"Middleware created and initialized "
"automatically by drogon cannot be "
"registered here");
DrClassMap::setSingleInstance(middlewarePtr);
return *this;
}
/// Register a default handler into the framework when no handler matches
/// the request. If set, it is executed if the static file router does
/// not find any file corresponding to the request. Thus it replaces
@ -816,15 +778,6 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0;
/// Reload the global cert file and private key file for https server
/// Note: The goal of this method is not to make the framework
/// use the new SSL path, but rather to reload the new content
/// from the old path while the framework is still running.
/// Typically, when our SSL is about to expire,
/// we need to reload the SSL. The purpose of this function
/// is to use the new SSL certificate without stopping the framework.
virtual HttpAppFramework &reloadSSLFiles() = 0;
/// Add plugins
/**
* @param configs The plugins array
@ -966,8 +919,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* extension can be accessed.
* @param isRecursive If it is set to false, files in sub directories can't
* be accessed.
* @param middlewareNames The list of middlewares which acting on the
* location.
* @param filters The list of filters which acting on the location.
* @return HttpAppFramework&
*/
virtual HttpAppFramework &addALocation(
@ -977,7 +929,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
bool isCaseSensitive = false,
bool allowAll = true,
bool isRecursive = true,
const std::vector<std::string> &middlewareNames = {}) = 0;
const std::vector<std::string> &filters = {}) = 0;
/// Set the path to store uploaded files.
/**
@ -1007,7 +959,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &setFileTypes(
const std::vector<std::string> &types) = 0;
#if !defined(_WIN32) && !TARGET_OS_IOS
#ifndef _WIN32
/// Enable supporting for dynamic views loading.
/**
*
@ -1469,8 +1421,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*
* @return std::pair<size_t, std::string>
*/
virtual const std::pair<unsigned int, std::string> &
getFloatPrecisionInJson() const noexcept = 0;
virtual const std::pair<unsigned int, std::string>
&getFloatPrecisionInJson() const noexcept = 0;
/// Create a database client
/**
* @param dbType The database type is one of
@ -1492,22 +1444,20 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* @note
* This operation can be performed by an option in the configuration file.
*/
[[deprecated("Use addDbClient() instead")]] virtual HttpAppFramework &
createDbClient(const std::string &dbType,
virtual HttpAppFramework &createDbClient(
const std::string &dbType,
const std::string &host,
unsigned short port,
const unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
size_t connectionNum = 1,
const size_t connectionNum = 1,
const std::string &filename = "",
const std::string &name = "default",
bool isFast = false,
const bool isFast = false,
const std::string &characterSet = "",
double timeout = -1.0,
bool autoBatch = false) = 0;
virtual HttpAppFramework &addDbClient(const orm::DbConfig &config) = 0;
const bool autoBatch = false) = 0;
/// Create a redis client
/**
@ -1609,46 +1559,18 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/
virtual int64_t getConnectionCount() const = 0;
/**
* @brief Set the before listen setsockopt callback.
*
* @param cb This callback will be called before the listen
*/
virtual HttpAppFramework &setBeforeListenSockOptCallback(
std::function<void(int)> cb) = 0;
/**
* @brief Set the after accept setsockopt callback.
*
* @param cb This callback will be called after accept
*/
virtual HttpAppFramework &setAfterAcceptSockOptCallback(
std::function<void(int)> cb) = 0;
/**
* @brief Set the client disconnect or connect callback.
*
* @param cb This callback will be called, when the client disconnect or
* connect
*/
virtual HttpAppFramework &setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb) = 0;
virtual HttpAppFramework &enableRequestStream(bool enable = true) = 0;
virtual bool isRequestStreamEnabled() const = 0;
private:
virtual void registerHttpController(
const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods = {},
const std::vector<std::string> &middlewareNames = {},
const std::vector<HttpMethod> &validMethods = std::vector<HttpMethod>(),
const std::vector<std::string> &filters = std::vector<std::string>(),
const std::string &handlerName = "") = 0;
virtual void registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &middlewareNames,
const std::vector<std::string> &filters,
const std::string &handlerName) = 0;
};

View File

@ -164,7 +164,6 @@ class HttpBinderBase
std::function<void(const HttpResponsePtr &)> &&callback) = 0;
virtual size_t paramCount() = 0;
virtual const std::string &handlerName() const = 0;
virtual bool isStreamHandler() = 0;
virtual ~HttpBinderBase()
{
@ -219,11 +218,6 @@ class HttpBinder : public HttpBinderBase
return traits::arity;
}
bool isStreamHandler() override
{
return traits::isStreamHandler;
}
HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
{
static_assert(traits::isHTTPFunction,
@ -272,7 +266,6 @@ class HttpBinder : public HttpBinderBase
template <typename... Values,
std::size_t Boundary = argument_count,
bool isStreamHandler = traits::isStreamHandler,
bool isCoroutine = traits::isCoroutine>
void run(std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
@ -351,18 +344,8 @@ class HttpBinder : public HttpBinderBase
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
if constexpr (isStreamHandler)
{
callFunction(req,
createRequestStream(req),
cb,
std::move(values)...);
}
else
{
callFunction(req, cb, std::move(values)...);
}
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
@ -376,7 +359,6 @@ class HttpBinder : public HttpBinderBase
#ifdef __cpp_impl_coroutine
else
{
static_assert(!isStreamHandler);
[this](HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
Values &&...values) -> AsyncTask {

View File

@ -21,7 +21,6 @@
#include <drogon/HttpRequest.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <cstddef>
#include <functional>
#include <memory>
#include <future>
@ -179,12 +178,6 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
*/
virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
/**
* @brief Return the number of unsent http requests in the current http
* client cache buffer
*/
virtual std::size_t requestsBufferSize() = 0;
/// Set the pipelining depth, which is the number of requests that are not
/// responding.
/**

View File

@ -66,7 +66,8 @@ class HttpController : public DrObject<T>, public HttpControllerBase
static void registerMethod(
FUNCTION &&function,
const std::string &pattern,
const std::vector<internal::HttpConstraint> &constraints = {},
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{},
bool classNameInPath = true,
const std::string &handlerName = "")
{
@ -87,12 +88,12 @@ class HttpController : public DrObject<T>, public HttpControllerBase
if (pattern.empty() || pattern[0] == '/')
app().registerHandler(path + pattern,
std::forward<FUNCTION>(function),
constraints,
filtersAndMethods,
handlerName);
else
app().registerHandler(path + "/" + pattern,
std::forward<FUNCTION>(function),
constraints,
filtersAndMethods,
handlerName);
}
else
@ -104,7 +105,7 @@ class HttpController : public DrObject<T>, public HttpControllerBase
}
app().registerHandler(path,
std::forward<FUNCTION>(function),
constraints,
filtersAndMethods,
handlerName);
}
}
@ -113,13 +114,13 @@ class HttpController : public DrObject<T>, public HttpControllerBase
static void registerMethodViaRegex(
FUNCTION &&function,
const std::string &regExp,
const std::vector<internal::HttpConstraint> &constraints =
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "")
{
app().registerHandlerViaRegex(regExp,
std::forward<FUNCTION>(function),
constraints,
filtersAndMethods,
handlerName);
}

View File

@ -18,7 +18,6 @@
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/HttpMiddleware.h>
#include <memory>
#ifdef __cpp_impl_coroutine
@ -31,8 +30,7 @@ namespace drogon
* @brief The abstract base class for filters
* For more details on the class, see the wiki site (the 'Filter' section)
*/
class DROGON_EXPORT HttpFilterBase : public virtual DrObjectBase,
public HttpMiddlewareBase
class DROGON_EXPORT HttpFilterBase : public virtual DrObjectBase
{
public:
/// This virtual function should be overridden in subclasses.
@ -50,24 +48,6 @@ class DROGON_EXPORT HttpFilterBase : public virtual DrObjectBase,
FilterCallback &&fcb,
FilterChainCallback &&fccb) = 0;
~HttpFilterBase() override = default;
private:
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) final
{
auto mcbPtr = std::make_shared<MiddlewareCallback>(std::move(mcb));
doFilter(
req,
[mcbPtr](const HttpResponsePtr &resp) {
(*mcbPtr)(resp);
}, // fcb, intercept the response
[nextCb = std::move(nextCb), mcbPtr]() mutable {
nextCb([mcbPtr = std::move(mcbPtr)](
const HttpResponsePtr &resp) { (*mcbPtr)(resp); });
} // fccb, call the next middleware
);
}
};
/**
@ -75,7 +55,7 @@ class DROGON_EXPORT HttpFilterBase : public virtual DrObjectBase,
*
* @tparam T The type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have non-default constructors.
* flag to false for classes that have nondefault constructors.
*/
template <typename T, bool AutoCreation = true>
class HttpFilter : public DrObject<T>, public HttpFilterBase
@ -85,6 +65,14 @@ class HttpFilter : public DrObject<T>, public HttpFilterBase
~HttpFilter() override = default;
};
namespace internal
{
DROGON_EXPORT void handleException(
const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
}
#ifdef __cpp_impl_coroutine
template <typename T, bool AutoCreation = true>
class HttpCoroFilter : public DrObject<T>, public HttpFilterBase

View File

@ -1,151 +0,0 @@
/**
*
* @file HttpMiddleware.h
* @author Nitromelon
*
* Copyright 2024, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include <drogon/drogon_callbacks.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <memory>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
/**
* @brief The abstract base class for middleware
*/
class DROGON_EXPORT HttpMiddlewareBase : public virtual DrObjectBase
{
public:
/**
* This virtual function should be overridden in subclasses.
*
* Example:
* @code
* void invoke(const HttpRequestPtr &req,
* MiddlewareNextCallback &&nextCb,
* MiddlewareCallback &&mcb) override
* {
* if (req->path() == "/some/path") {
* // intercept directly
* mcb(HttpResponse::newNotFoundResponse(req));
* return;
* }
* // Do something before calling the next middleware
* nextCb([mcb = std::move(mcb)](const HttpResponsePtr &resp) {
* // Do something after the next middleware returns
* mcb(resp);
* });
* }
* @endcode
*
*/
virtual void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) = 0;
~HttpMiddlewareBase() override = default;
};
/**
* @brief The reflection base class template for middlewares
*
* @tparam T The type of the implementation class
* @tparam AutoCreation The flag for automatically creating, user can set this
* flag to false for classes that have non-default constructors.
*/
template <typename T, bool AutoCreation = true>
class HttpMiddleware : public DrObject<T>, public HttpMiddlewareBase
{
public:
static constexpr bool isAutoCreation{AutoCreation};
~HttpMiddleware() override = default;
};
namespace internal
{
DROGON_EXPORT void handleException(
const std::exception &,
const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&);
}
#ifdef __cpp_impl_coroutine
struct [[nodiscard]] MiddlewareNextAwaiter
: public CallbackAwaiter<HttpResponsePtr>
{
public:
MiddlewareNextAwaiter(MiddlewareNextCallback &&nextCb)
: nextCb_(std::move(nextCb))
{
}
void await_suspend(std::coroutine_handle<> handle) noexcept
{
nextCb_([this, handle](const HttpResponsePtr &resp) {
setValue(resp);
handle.resume();
});
}
private:
MiddlewareNextCallback nextCb_;
};
template <typename T, bool AutoCreation = true>
class HttpCoroMiddleware : public DrObject<T>, public HttpMiddlewareBase
{
public:
static constexpr bool isAutoCreation{AutoCreation};
~HttpCoroMiddleware() override = default;
void invoke(const HttpRequestPtr &req,
MiddlewareNextCallback &&nextCb,
MiddlewareCallback &&mcb) final
{
drogon::async_run([this,
req,
nextCb = std::move(nextCb),
mcb = std::move(mcb)]() mutable -> drogon::Task<> {
HttpResponsePtr resp;
try
{
resp = co_await invoke(req, {std::move(nextCb)});
}
catch (const std::exception &ex)
{
internal::handleException(ex, req, std::move(mcb));
co_return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
co_return;
}
mcb(resp);
});
}
virtual Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
MiddlewareNextAwaiter &&next) = 0;
};
#endif
} // namespace drogon

View File

@ -30,7 +30,6 @@
#include <unordered_map>
#include <optional>
#include <string_view>
#include <trantor/net/TcpConnection.h>
namespace drogon
{
@ -163,34 +162,31 @@ class DROGON_EXPORT HttpRequest
virtual const std::string &getCookie(const std::string &field) const = 0;
/// Get all headers of the request
virtual const SafeStringMap<std::string> &headers() const = 0;
virtual const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&headers() const = 0;
/// Get all headers of the request
const SafeStringMap<std::string> &getHeaders() const
const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getHeaders() const
{
return headers();
}
/// Get all cookies of the request
virtual const SafeStringMap<std::string> &cookies() const = 0;
virtual const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&cookies() const = 0;
/// Get all cookies of the request
const SafeStringMap<std::string> &getCookies() const
const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getCookies() const
{
return cookies();
}
/**
* @brief Return content length parsed from the Content-Length header
* If no Content-Length header, return null.
*/
virtual size_t realContentLength() const = 0;
size_t getRealContentLength() const
{
return realContentLength();
}
/// Get the query string of the request.
/**
* The query string is the substring after the '?' in the URL string.
@ -304,10 +300,14 @@ class DROGON_EXPORT HttpRequest
}
/// Get parameters of the request.
virtual const SafeStringMap<std::string> &parameters() const = 0;
virtual const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&parameters() const = 0;
/// Get parameters of the request.
const SafeStringMap<std::string> &getParameters() const
const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getParameters() const
{
return parameters();
}
@ -450,7 +450,8 @@ class DROGON_EXPORT HttpRequest
virtual void setCustomContentTypeString(const std::string &type) = 0;
/// Add a cookie
virtual void addCookie(std::string key, std::string value) = 0;
virtual void addCookie(const std::string &key,
const std::string &value) = 0;
/**
* @brief Set the request object to the pass-through mode or not. It's not
@ -505,11 +506,6 @@ class DROGON_EXPORT HttpRequest
virtual void setContentTypeString(const char *typeString,
size_t typeStringLength) = 0;
virtual bool connected() const noexcept = 0;
virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept = 0;
virtual ~HttpRequest()
{
}

View File

@ -244,10 +244,14 @@ class DROGON_EXPORT HttpResponse
virtual void removeHeader(std::string key) = 0;
/// Get all headers of the response
virtual const SafeStringMap<std::string> &headers() const = 0;
virtual const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&headers() const = 0;
/// Get all headers of the response
const SafeStringMap<std::string> &getHeaders() const
const std::
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getHeaders() const
{
return headers();
}
@ -275,10 +279,14 @@ class DROGON_EXPORT HttpResponse
virtual const Cookie &getCookie(const std::string &key) const = 0;
/// Get all cookies.
virtual const SafeStringMap<Cookie> &cookies() const = 0;
virtual const std::
unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
&cookies() const = 0;
/// Get all cookies.
const SafeStringMap<Cookie> &getCookies() const
const std::
unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
&getCookies() const
{
return cookies();
}
@ -572,8 +580,8 @@ class DROGON_EXPORT HttpResponse
* newStreamResponse) returns the callback function. Otherwise a
* null function.
*/
virtual const std::function<std::size_t(char *, std::size_t)> &
streamCallback() const = 0;
virtual const std::function<std::size_t(char *, std::size_t)>
&streamCallback() const = 0;
/**
* @brief If the response is a async stream response (i.e. created by

View File

@ -76,7 +76,7 @@ class HttpSimpleController : public DrObject<T>, public HttpSimpleControllerBase
static void registerSelf__(
const std::string &path,
const std::vector<internal::HttpConstraint> &constraints)
const std::vector<internal::HttpConstraint> &filtersAndMethods)
{
LOG_TRACE << "register simple controller("
<< HttpSimpleController<T, AutoCreation>::classTypeName()
@ -84,7 +84,7 @@ class HttpSimpleController : public DrObject<T>, public HttpSimpleControllerBase
app().registerHttpSimpleController(
path,
HttpSimpleController<T, AutoCreation>::classTypeName(),
constraints);
filtersAndMethods);
}
private:

View File

@ -106,71 +106,26 @@ enum ContentType
CT_APPLICATION_X_JAVASCRIPT [[deprecated("use CT_TEXT_JAVASCRIPT")]],
CT_TEXT_JAVASCRIPT,
CT_TEXT_CSS,
CT_TEXT_CSV,
CT_TEXT_XML, // suggests human readable xml
CT_APPLICATION_XML, // suggest machine-to-machine xml
CT_TEXT_XML,
CT_APPLICATION_XML,
CT_TEXT_XSL,
CT_APPLICATION_WASM,
CT_APPLICATION_OCTET_STREAM,
CT_APPLICATION_FONT_WOFF,
CT_APPLICATION_FONT_WOFF2,
CT_APPLICATION_GZIP,
CT_APPLICATION_JAVA_ARCHIVE,
CT_APPLICATION_PDF,
CT_APPLICATION_MSWORD,
CT_APPLICATION_MSWORDX,
CT_APPLICATION_VND_MS_FONTOBJ,
CT_APPLICATION_VND_RAR,
CT_APPLICATION_XHTML,
CT_APPLICATION_X_7Z,
CT_APPLICATION_X_BZIP,
CT_APPLICATION_X_BZIP2,
CT_APPLICATION_X_HTTPD_PHP,
CT_APPLICATION_X_FONT_TRUETYPE,
CT_APPLICATION_X_FONT_OPENTYPE,
CT_APPLICATION_X_TAR,
CT_APPLICATION_X_TGZ,
CT_APPLICATION_X_XZ,
CT_APPLICATION_ZIP,
CT_AUDIO_AAC,
CT_AUDIO_AC3,
CT_AUDIO_AIFF,
CT_AUDIO_FLAC,
CT_AUDIO_MATROSKA,
CT_AUDIO_MPEG,
CT_AUDIO_MPEG4,
CT_AUDIO_OGG,
CT_AUDIO_WAVE,
CT_AUDIO_WEBM,
CT_AUDIO_X_APE,
CT_AUDIO_X_MS_WMA,
CT_AUDIO_X_TTA,
CT_AUDIO_X_WAVPACK,
CT_IMAGE_APNG,
CT_IMAGE_AVIF,
CT_IMAGE_BMP,
CT_IMAGE_GIF,
CT_IMAGE_ICNS,
CT_IMAGE_JPG,
CT_IMAGE_JP2,
CT_IMAGE_PNG,
CT_APPLICATION_FONT_WOFF,
CT_APPLICATION_FONT_WOFF2,
CT_APPLICATION_VND_MS_FONTOBJ,
CT_APPLICATION_PDF,
CT_IMAGE_SVG_XML,
CT_IMAGE_TIFF,
CT_IMAGE_PNG,
CT_IMAGE_WEBP,
CT_IMAGE_X_MNG,
CT_IMAGE_X_TGA,
CT_IMAGE_AVIF,
CT_IMAGE_JPG,
CT_IMAGE_GIF,
CT_IMAGE_XICON,
CT_VIDEO_APG,
CT_VIDEO_AV1,
CT_VIDEO_QUICKTIME,
CT_VIDEO_MATROSKA,
CT_VIDEO_MP4,
CT_VIDEO_MPEG,
CT_VIDEO_MPEG2TS,
CT_VIDEO_OGG,
CT_VIDEO_WEBM,
CT_VIDEO_X_M4V,
CT_VIDEO_X_MSVIDEO,
CT_IMAGE_ICNS,
CT_IMAGE_BMP,
CT_MULTIPART_FORM_DATA,
CT_CUSTOM
};

View File

@ -37,7 +37,7 @@ class DROGON_EXPORT HttpViewData
template <typename T>
const T &get(const std::string &key) const
{
static const T nullVal = T();
const static T nullVal = T();
auto it = viewData_.find(key);
if (it != viewData_.end())
{

View File

@ -29,7 +29,7 @@ class DROGON_EXPORT IntranetIpFilter : public HttpFilter<IntranetIpFilter>
{
}
void doFilter(const HttpRequestPtr &req,
virtual void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};

View File

@ -14,9 +14,9 @@
#pragma once
#include "drogon/utils/Utilities.h"
#include <drogon/exports.h>
#include <drogon/HttpRequest.h>
#include <map>
#include <unordered_map>
#include <string>
#include <vector>
@ -123,8 +123,6 @@ class DROGON_EXPORT MultiPartParser
{
public:
MultiPartParser(){};
MultiPartParser(const MultiPartParser &other) = default; // Copyable
MultiPartParser(MultiPartParser &&other) = default; // Movable
~MultiPartParser(){};
/// Get files, This method should be called after calling the parse()
/// method.
@ -135,54 +133,19 @@ class DROGON_EXPORT MultiPartParser
/// Get parameters, This method should be called after calling the parse ()
/// method.
const SafeStringMap<std::string> &getParameters() const;
/// Get the value of an optional parameter
/// This method should be called after calling the parse() method.
template <typename T>
std::optional<T> getOptionalParameter(const std::string &key)
{
auto &params = getParameters();
auto it = params.find(key);
if (it != params.end())
{
try
{
return std::optional<T>(utils::fromString<T>(it->second));
}
catch (const std::exception &e)
{
LOG_ERROR << e.what();
return std::optional<T>{};
}
}
else
{
return std::optional<T>{};
}
}
/// Get the value of a parameter
/// This method should be called after calling the parse() method.
/// Note: returns a default T object if the parameter is missing
template <typename T>
T getParameter(const std::string &key)
{
return getOptionalParameter<T>(key).value_or(T{});
}
const std::map<std::string, std::string> &getParameters() const;
/// Parse the http request stream to get files and parameters.
int parse(const HttpRequestPtr &req);
protected:
std::vector<HttpFile> files_;
SafeStringMap<std::string> parameters_;
std::map<std::string, std::string> parameters_;
int parse(const HttpRequestPtr &req,
const char *boundaryData,
size_t boundaryLen);
int parseEntity(const HttpRequestPtr &req,
const char *begin,
const char *end);
int parseEntity(const char *begin, const char *end);
HttpRequestPtr requestPtr_;
};
/// In order to be compatible with old interfaces

View File

@ -1,117 +0,0 @@
/**
*
* @file RequestStream.h
* @author Nitromelon
*
* Copyright 2024, Nitromelon. All rights reserved.
* https://github.com/drogonframework/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/exports.h>
#include <string>
#include <functional>
#include <memory>
#include <exception>
namespace drogon
{
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
class RequestStreamReader;
using RequestStreamReaderPtr = std::shared_ptr<RequestStreamReader>;
struct MultipartHeader
{
std::string name;
std::string filename;
std::string contentType;
};
class DROGON_EXPORT RequestStream
{
public:
virtual ~RequestStream() = default;
virtual void setStreamReader(RequestStreamReaderPtr reader) = 0;
};
using RequestStreamPtr = std::shared_ptr<RequestStream>;
namespace internal
{
DROGON_EXPORT RequestStreamPtr createRequestStream(const HttpRequestPtr &req);
}
enum class StreamErrorCode
{
kNone = 0,
kBadRequest,
kConnectionBroken
};
class StreamError final : public std::exception
{
public:
const char *what() const noexcept override
{
return message_.data();
}
StreamErrorCode code() const
{
return code_;
}
StreamError(StreamErrorCode code, const std::string &message)
: message_(message), code_(code)
{
}
StreamError(StreamErrorCode code, std::string &&message)
: message_(std::move(message)), code_(code)
{
}
StreamError() = delete;
private:
std::string message_;
StreamErrorCode code_;
};
/**
* An interface for stream request reading.
* User should create an implementation class, or use built-in handlers
*/
class DROGON_EXPORT RequestStreamReader
{
public:
virtual ~RequestStreamReader() = default;
virtual void onStreamData(const char *, size_t) = 0;
virtual void onStreamFinish(std::exception_ptr) = 0;
using StreamDataCallback = std::function<void(const char *, size_t)>;
using StreamFinishCallback = std::function<void(std::exception_ptr)>;
// Create a handler with default implementation
static RequestStreamReaderPtr newReader(StreamDataCallback dataCb,
StreamFinishCallback finishCb);
// A handler that drops all data
static RequestStreamReaderPtr newNullReader();
using MultipartHeaderCallback = std::function<void(MultipartHeader header)>;
static RequestStreamReaderPtr newMultipartReader(
const HttpRequestPtr &req,
MultipartHeaderCallback headerCb,
StreamDataCallback dataCb,
StreamFinishCallback finishCb);
};
} // namespace drogon

View File

@ -90,31 +90,6 @@ class DROGON_EXPORT WebSocketClient
virtual void connectToServer(const HttpRequestPtr &request,
const WebSocketRequestCallback &callback) = 0;
/**
* @brief Set the client certificate used by the HTTP connection
*
* @param cert Path to the certificate
* @param key Path to the certificate's private key
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
*/
virtual void setCertPath(const std::string &cert,
const std::string &key) = 0;
/**
* @brief Supplies command style options for `SSL_CONF_cmd`
*
* @param sslConfCmds options for SSL_CONF_cmd
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
* @code
addSSLConfigs({{"-dhparam", "/path/to/dhparam"}, {"-strict", ""}});
* @endcode
*/
virtual void addSSLConfigs(
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0;
#ifdef __cpp_impl_coroutine
/**
* @brief Set messages handler. When a message is received from the server,

View File

@ -14,11 +14,9 @@
#pragma once
#include <json/value.h>
#include <memory>
#include <string>
#include <drogon/HttpTypes.h>
#include <string_view>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/NonCopyable.h>
@ -114,17 +112,7 @@ class WebSocketConnection
* @param type The message type.
*/
virtual void send(
std::string_view msg,
const WebSocketMessageType type = WebSocketMessageType::Text) = 0;
/**
* @brief Send a message to the peer
*
* @param json The JSON message to be sent.
* @param type The message type.
*/
virtual void sendJson(
const Json::Value &json,
const std::string &msg,
const WebSocketMessageType type = WebSocketMessageType::Text) = 0;
/// Return the local IP address and port number of the connection

View File

@ -28,8 +28,6 @@
static void initPathRouting() \
{
#define WS_PATH_ADD(path, ...) registerSelf__(path, {__VA_ARGS__})
#define WS_ADD_PATH_VIA_REGEX(regExp, ...) \
registerSelfRegex__(regExp, {__VA_ARGS__})
#define WS_PATH_LIST_END }
namespace drogon
@ -85,7 +83,7 @@ class WebSocketController : public DrObject<T>, public WebSocketControllerBase
static void registerSelf__(
const std::string &path,
const std::vector<internal::HttpConstraint> &constraints)
const std::vector<internal::HttpConstraint> &filtersAndMethods)
{
LOG_TRACE << "register websocket controller("
<< WebSocketController<T, AutoCreation>::classTypeName()
@ -93,20 +91,7 @@ class WebSocketController : public DrObject<T>, public WebSocketControllerBase
app().registerWebSocketController(
path,
WebSocketController<T, AutoCreation>::classTypeName(),
constraints);
}
static void registerSelfRegex__(
const std::string &regExp,
const std::vector<internal::HttpConstraint> &constraints)
{
LOG_TRACE << "register websocket controller("
<< WebSocketController<T, AutoCreation>::classTypeName()
<< ") on regExp:" << regExp;
app().registerWebSocketControllerRegex(
regExp,
WebSocketController<T, AutoCreation>::classTypeName(),
constraints);
filtersAndMethods);
}
private:

View File

@ -31,9 +31,4 @@ using AdviceDestroySessionCallback = std::function<void(const std::string &)>;
using FilterCallback = std::function<void(const HttpResponsePtr &)>;
using FilterChainCallback = std::function<void()>;
using HttpReqCallback = std::function<void(ReqResult, const HttpResponsePtr &)>;
using MiddlewareCallback = std::function<void(const HttpResponsePtr &)>;
using MiddlewareNextCallback =
std::function<void(std::function<void(const HttpResponsePtr &)> &&)>;
} // namespace drogon

View File

@ -36,7 +36,6 @@ namespace plugin
// "show_microseconds": true,
// "custom_time_format": "",
// "use_real_ip": false
// "path_exempt": ""
}
}
@endcode
@ -99,10 +98,6 @@ namespace plugin
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
*
* path_exempt: must be a string or a string array, present a regular expression
* (for matching the path of a request) or a regular expression list for URLs
* that don't have to be logged.
*
*/
class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
{
@ -122,8 +117,6 @@ class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
bool useCustomTimeFormat_{false};
std::string timeFormat_;
static bool useRealIp_;
std::regex exemptRegex_;
bool regexFlag_{false};
using LogFunction = std::function<void(trantor::LogStream &,
const drogon::HttpRequestPtr &,

View File

@ -71,9 +71,7 @@ IPs or users. the default value is 600.
"ip_capacity": 0,
"user_capacity": 0
},...
],
// Trusted proxy ip or cidr
"trust_ips": ["127.0.0.1", "172.16.0.0/12"],
]
}
}
@endcode
@ -139,14 +137,12 @@ class DROGON_EXPORT Hodor : public drogon::Plugin<Hodor>
std::function<HttpResponsePtr(const drogon::HttpRequestPtr &)>
rejectResponseFactory_;
RealIpResolver::CIDRs trustCIDRs_;
void onHttpRequest(const drogon::HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&);
bool checkLimit(const drogon::HttpRequestPtr &req,
const LimitStrategy &strategy,
const trantor::InetAddress &ip,
const std::string &ip,
const std::optional<std::string> &userId);
HttpResponsePtr rejectResponse_;
};

View File

@ -57,6 +57,7 @@ class DROGON_EXPORT RealIpResolver : public drogon::Plugin<RealIpResolver>
private:
const trantor::InetAddress &getRealAddr(
const drogon::HttpRequestPtr &req) const;
bool matchCidr(const trantor::InetAddress &addr) const;
struct CIDR
{
@ -65,12 +66,7 @@ class DROGON_EXPORT RealIpResolver : public drogon::Plugin<RealIpResolver>
in_addr_t mask_{32};
};
using CIDRs = std::vector<CIDR>;
static bool matchCidr(const trantor::InetAddress &addr,
const CIDRs &trustCIDRs);
friend class Hodor;
CIDRs trustCIDRs_;
std::vector<CIDR> trustCIDRs_;
std::string fromHeader_;
std::string attributeKey_;
bool useXForwardedFor_{false};

View File

@ -15,7 +15,6 @@
#pragma once
#include <drogon/DrObject.h>
#include <drogon/RequestStream.h>
#include <functional>
#include <memory>
#include <tuple>
@ -47,84 +46,7 @@ struct resumable_type : std::false_type
template <typename>
struct FunctionTraits;
//
// Basic match, inherited by all other matches
//
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (*)(Arguments...)>
{
using result_type = ReturnType;
template <std::size_t Index>
using argument =
typename std::tuple_element_t<Index, std::tuple<Arguments...>>;
static const std::size_t arity = sizeof...(Arguments);
using class_type = void;
using return_type = ReturnType;
static const bool isHTTPFunction = false;
static const bool isClassFunction = false;
static const bool isStreamHandler = false;
static const bool isDrObjectClass = false;
static const bool isCoroutine = false;
static const std::string name()
{
return std::string("Normal or Static Function");
}
};
//
// Match normal functions
//
// normal function for HTTP handling
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = ReturnType;
};
// normal function with custom request object
template <typename T, typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(T &&customReq,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
using class_type = void;
using first_param_type = T;
using return_type = ReturnType;
};
// normal function with stream handler
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(const HttpRequestPtr &req,
RequestStreamPtr &&streamCtx,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
static const bool isStreamHandler = true;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = ReturnType;
};
//
// Match functor,lambda,std::function... inherits normal function matches
//
// functor,lambda,std::function...
template <typename Function>
struct FunctionTraits
: public FunctionTraits<
@ -140,11 +62,7 @@ struct FunctionTraits
}
};
//
// Match class functions, inherits normal function matches
//
// class const method
// class instance method of const object
template <typename ClassType, typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
: FunctionTraits<ReturnType (*)(Arguments...)>
@ -160,7 +78,7 @@ struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
}
};
// class non-const method
// class instance method of non-const object
template <typename ClassType, typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...)>
: FunctionTraits<ReturnType (*)(Arguments...)>
@ -176,9 +94,30 @@ struct FunctionTraits<ReturnType (ClassType::*)(Arguments...)>
}
};
//
// Match coroutine functions
//
// normal function for HTTP handling
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
using class_type = void;
using first_param_type = HttpRequestPtr;
using return_type = ReturnType;
};
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = false;
using class_type = void;
};
#ifdef __cpp_impl_coroutine
template <typename... Arguments>
struct FunctionTraits<
@ -219,20 +158,6 @@ struct FunctionTraits<Task<HttpResponsePtr> (*)(HttpRequestPtr req,
};
#endif
//
// Bad matches
//
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = false;
using class_type = void;
};
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(HttpRequestPtr &&req,
@ -243,5 +168,43 @@ struct FunctionTraits<
using class_type = void;
};
// normal function for HTTP handling
template <typename T, typename ReturnType, typename... Arguments>
struct FunctionTraits<
ReturnType (*)(T &&customReq,
std::function<void(const HttpResponsePtr &)> &&callback,
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
{
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
static const bool isCoroutine = false;
using class_type = void;
using first_param_type = T;
using return_type = ReturnType;
};
// normal function
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (*)(Arguments...)>
{
using result_type = ReturnType;
template <std::size_t Index>
using argument =
typename std::tuple_element_t<Index, std::tuple<Arguments...>>;
static const std::size_t arity = sizeof...(Arguments);
using class_type = void;
using return_type = ReturnType;
static const bool isHTTPFunction = false;
static const bool isClassFunction = false;
static const bool isDrObjectClass = false;
static const bool isCoroutine = false;
static const std::string name()
{
return std::string("Normal or Static Function");
}
};
} // namespace internal
} // namespace drogon

View File

@ -25,7 +25,7 @@ enum class ConstraintType
{
None,
HttpMethod,
HttpMiddleware
HttpFilter
};
class HttpConstraint
@ -36,14 +36,13 @@ class HttpConstraint
{
}
HttpConstraint(std::string middlewareName)
: type_(ConstraintType::HttpMiddleware),
middlewareName_(std::move(middlewareName))
HttpConstraint(const std::string &filterName)
: type_(ConstraintType::HttpFilter), filterName_(filterName)
{
}
HttpConstraint(const char *middlewareName)
: type_(ConstraintType::HttpMiddleware), middlewareName_(middlewareName)
HttpConstraint(const char *filterName)
: type_(ConstraintType::HttpFilter), filterName_(filterName)
{
}
@ -57,15 +56,15 @@ class HttpConstraint
return method_;
}
const std::string &getMiddlewareName() const
const std::string &getFilterName() const
{
return middlewareName_;
return filterName_;
}
private:
ConstraintType type_{ConstraintType::None};
HttpMethod method_{HttpMethod::Invalid};
std::string middlewareName_;
std::string filterName_;
};
} // namespace internal
} // namespace drogon

View File

@ -21,16 +21,23 @@ namespace drogon
{
namespace internal
{
template <typename T, typename = void>
struct CanConvertToString : std::false_type
{
};
template <typename T>
struct CanConvertToString<
T,
std::void_t<decltype(std::to_string(std::declval<T>()))>> : std::true_type
struct CanConvertToString
{
using Type = std::remove_reference_t<std::remove_cv_t<T>>;
private:
using yes = std::true_type;
using no = std::false_type;
template <typename U>
static auto test(int) -> decltype(std::to_string(U{}), yes());
template <typename>
static no test(...);
public:
static constexpr bool value = std::is_same_v<decltype(test<Type>(0)), yes>;
};
} // namespace internal

View File

@ -28,8 +28,6 @@
#include <algorithm>
#include <filesystem>
#include <string_view>
#include <unordered_map>
#include <type_traits>
#ifdef _WIN32
#include <time.h>
DROGON_EXPORT char *strptime(const char *s, const char *f, struct tm *tm);
@ -39,40 +37,61 @@ namespace drogon
{
namespace internal
{
template <typename T, typename = void>
struct CanConvertFromStringStream : std::false_type
template <typename T>
struct CanConvertFromStringStream
{
private:
using yes = std::true_type;
using no = std::false_type;
template <typename U>
static auto test(U *p, std::stringstream &&ss)
-> decltype((ss >> *p), yes());
template <typename>
static no test(...);
public:
static constexpr bool value =
std::is_same_v<decltype(test<T>(nullptr, std::stringstream())), yes>;
};
template <typename T>
struct CanConvertFromStringStream<
T,
std::void_t<decltype(std::declval<std::stringstream &>() >>
std::declval<T &>())>> : std::true_type
struct CanConstructFromString
{
private:
using yes = std::true_type;
using no = std::false_type;
template <typename U>
static auto test(U *p) -> decltype(U(std::string{}), yes());
template <typename>
static no test(...);
public:
static constexpr bool value =
std::is_same_v<decltype(test<T>(nullptr)), yes>;
};
template <typename T>
struct CanConstructFromString : std::is_constructible<T, std::string>
struct CanConvertFromString
{
};
private:
using yes = std::true_type;
using no = std::false_type;
template <class U>
static auto test(U *p) -> decltype(*p = std::string(), yes());
template <class>
static no test(...);
template <typename T>
struct CanConvertFromString : std::is_assignable<T &, std::string>
{
public:
static constexpr bool value =
std::is_same_v<decltype(test<T>(nullptr)), yes>;
};
} // namespace internal
/**
* @brief Get the HTTP messages corresponding to the HTTP status codes
*
* @param code HTTP status code
*
* @return the corresponding message
*/
DROGON_EXPORT const std::string_view &statusCodeToString(int code);
namespace utils
{
/// Determine if the string is an integer
@ -134,85 +153,47 @@ constexpr size_t base64EncodedLength(size_t in_len, bool padded = true)
}
/// Encode the string to base64 format.
DROGON_EXPORT void base64Encode(const unsigned char *bytesToEncode,
size_t inLen,
unsigned char *outputBuffer,
bool urlSafe = false,
DROGON_EXPORT std::string base64Encode(const unsigned char *bytes_to_encode,
size_t in_len,
bool url_safe = false,
bool padded = true);
/// Encode the string to base64 format.
inline std::string base64Encode(const unsigned char *bytesToEncode,
size_t inLen,
bool urlSafe = false,
bool padded = true)
{
std::string ret;
ret.resize(base64EncodedLength(inLen, padded));
base64Encode(
bytesToEncode, inLen, (unsigned char *)ret.data(), urlSafe, padded);
return ret;
}
/// Encode the string to base64 format.
inline std::string base64Encode(std::string_view data,
bool urlSafe = false,
bool url_safe = false,
bool padded = true)
{
return base64Encode((unsigned char *)data.data(),
data.size(),
urlSafe,
url_safe,
padded);
}
/// Encode the string to base64 format with no padding.
inline void base64EncodeUnpadded(const unsigned char *bytesToEncode,
size_t inLen,
unsigned char *outputBuffer,
bool urlSafe = false)
inline std::string base64EncodeUnpadded(const unsigned char *bytes_to_encode,
size_t in_len,
bool url_safe = false)
{
base64Encode(bytesToEncode, inLen, outputBuffer, urlSafe, false);
}
/// Encode the string to base64 format with no padding.
inline std::string base64EncodeUnpadded(const unsigned char *bytesToEncode,
size_t inLen,
bool urlSafe = false)
{
return base64Encode(bytesToEncode, inLen, urlSafe, false);
return base64Encode(bytes_to_encode, in_len, url_safe, false);
}
/// Encode the string to base64 format with no padding.
inline std::string base64EncodeUnpadded(std::string_view data,
bool urlSafe = false)
bool url_safe = false)
{
return base64Encode(data, urlSafe, false);
return base64Encode(data, url_safe, false);
}
/// Get the decoded length of base64.
constexpr size_t base64DecodedLength(size_t inLen)
constexpr size_t base64DecodedLength(size_t in_len)
{
return (inLen * 3) / 4;
return (in_len * 3) / 4;
}
/// Decode the base64 format string.
/// Return the number of bytes written.
DROGON_EXPORT size_t base64Decode(const char *encodedString,
size_t inLen,
unsigned char *outputBuffer);
/// Decode the base64 format string.
inline std::string base64Decode(std::string_view encodedString)
{
auto inLen = encodedString.size();
std::string ret;
ret.resize(base64DecodedLength(inLen));
ret.resize(
base64Decode(encodedString.data(), inLen, (unsigned char *)ret.data()));
return ret;
}
DROGON_EXPORT std::string base64Decode(std::string_view encoded_string);
DROGON_EXPORT std::vector<char> base64DecodeToVector(
std::string_view encodedString);
std::string_view encoded_string);
/// Check if the string need decoding
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);
@ -301,12 +282,6 @@ DROGON_EXPORT std::string brotliDecompress(const char *data,
DROGON_EXPORT char *getHttpFullDate(
const trantor::Date &date = trantor::Date::now());
DROGON_EXPORT const std::string &getHttpFullDateStr(
const trantor::Date &date = trantor::Date::now());
DROGON_EXPORT void dateToCustomFormattedString(const std::string &fmtStr,
std::string &str,
const trantor::Date &date);
/// Get the trantor::Date object according to the http full date string
/**
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
@ -462,61 +437,13 @@ DROGON_EXPORT std::string secureRandomString(size_t size);
template <typename T>
T fromString(const std::string &p) noexcept(false)
{
if constexpr (std::is_integral<T>::value && std::is_signed<T>::value)
{
std::size_t pos;
auto v = std::stoll(p, &pos);
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (pos != p.size())
throw std::invalid_argument("Invalid value");
if ((v < static_cast<long long>((std::numeric_limits<T>::min)())) ||
(v > static_cast<long long>((std::numeric_limits<T>::max)())))
throw std::out_of_range("Value out of range");
return static_cast<T>(v);
}
else if constexpr (std::is_integral<T>::value &&
(!std::is_signed<T>::value))
{
std::size_t pos;
auto v = std::stoull(p, &pos);
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (pos != p.size())
throw std::invalid_argument("Invalid value");
if (v >
static_cast<unsigned long long>((std::numeric_limits<T>::max)()))
throw std::out_of_range("Value out of range");
return static_cast<T>(v);
}
else if constexpr (std::is_floating_point<T>::value)
{
std::size_t pos;
auto v = std::stold(p, &pos);
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (pos != p.size())
throw std::invalid_argument("Invalid value");
if ((v < static_cast<long double>((std::numeric_limits<T>::min)())) ||
(v > static_cast<long double>((std::numeric_limits<T>::max)())))
throw std::out_of_range("Value out of range");
return static_cast<T>(v);
}
else if constexpr (internal::CanConvertFromStringStream<T>::value)
if constexpr (internal::CanConvertFromStringStream<T>::value)
{
T value{};
if (!p.empty())
{
std::stringstream ss(p);
// must except in case of invalid value, not return a default value
// (else it returns 0 for integers if the string is empty or
// non-numeric)
ss.exceptions(std::ios_base::failbit);
ss >> value;
// throw if the whole string could not be parsed
// ("1a" should not return 1)
if (!ss.eof())
std::runtime_error("Bad type conversion");
}
return value;
}
@ -532,13 +459,67 @@ inline std::string fromString<std::string>(const std::string &p) noexcept(false)
return p;
}
template <>
inline int fromString<int>(const std::string &p) noexcept(false)
{
return std::stoi(p);
}
template <>
inline long fromString<long>(const std::string &p) noexcept(false)
{
return std::stol(p);
}
template <>
inline long long fromString<long long>(const std::string &p) noexcept(false)
{
return std::stoll(p);
}
template <>
inline unsigned long fromString<unsigned long>(const std::string &p) noexcept(
false)
{
return std::stoul(p);
}
template <>
inline unsigned long long fromString<unsigned long long>(
const std::string &p) noexcept(false)
{
return std::stoull(p);
}
template <>
inline float fromString<float>(const std::string &p) noexcept(false)
{
return std::stof(p);
}
template <>
inline double fromString<double>(const std::string &p) noexcept(false)
{
return std::stod(p);
}
template <>
inline long double fromString<long double>(const std::string &p) noexcept(false)
{
return std::stold(p);
}
template <>
inline bool fromString<bool>(const std::string &p) noexcept(false)
{
if (!p.empty() && std::all_of(p.begin(), p.end(), [](unsigned char c) {
return std::isdigit(c);
}))
return (std::stoll(p) != 0);
if (p == "1")
{
return true;
}
if (p == "0")
{
return false;
}
std::string l{p};
std::transform(p.begin(), p.end(), l.begin(), [](unsigned char c) {
return (char)tolower(c);
@ -574,17 +555,12 @@ struct SafeStringHash
};
} // namespace internal
} // namespace utils
template <typename T>
using SafeStringMap =
std::unordered_map<std::string, T, utils::internal::SafeStringHash>;
} // namespace drogon
namespace trantor
{
inline LogStream &operator<<(LogStream &ls, const std::string_view &v)
{
if (!v.empty())
ls.append(v.data(), v.length());
return ls;
}

View File

@ -245,7 +245,7 @@ struct [[nodiscard]] Task
std::optional<T> value;
std::exception_ptr exception_;
std::coroutine_handle<> continuation_{std::noop_coroutine()};
std::coroutine_handle<> continuation_;
};
auto operator co_await() const noexcept
@ -332,7 +332,7 @@ struct [[nodiscard]] Task<void>
}
std::exception_ptr exception_;
std::coroutine_handle<> continuation_{std::noop_coroutine()};
std::coroutine_handle<> continuation_;
};
auto operator co_await() const noexcept
@ -811,180 +811,4 @@ inline internal::EventLoopAwaiter<T> queueInLoopCoro(trantor::EventLoop *loop,
return internal::EventLoopAwaiter<T>(std::move(task), loop);
}
class Mutex final
{
class ScopedCoroMutexAwaiter;
class CoroMutexAwaiter;
public:
Mutex() noexcept : state_(unlockedValue()), waiters_(nullptr)
{
}
Mutex(const Mutex &) = delete;
Mutex(Mutex &&) = delete;
Mutex &operator=(const Mutex &) = delete;
Mutex &operator=(Mutex &&) = delete;
~Mutex()
{
[[maybe_unused]] auto state = state_.load(std::memory_order_relaxed);
assert(state == unlockedValue() || state == nullptr);
assert(waiters_ == nullptr);
}
bool try_lock() noexcept
{
void *oldValue = unlockedValue();
return state_.compare_exchange_strong(oldValue,
nullptr,
std::memory_order_acquire,
std::memory_order_relaxed);
}
[[nodiscard]] ScopedCoroMutexAwaiter scoped_lock(
trantor::EventLoop *loop =
trantor::EventLoop::getEventLoopOfCurrentThread()) noexcept
{
return ScopedCoroMutexAwaiter(*this, loop);
}
[[nodiscard]] CoroMutexAwaiter lock(
trantor::EventLoop *loop =
trantor::EventLoop::getEventLoopOfCurrentThread()) noexcept
{
return CoroMutexAwaiter(*this, loop);
}
void unlock() noexcept
{
assert(state_.load(std::memory_order_relaxed) != unlockedValue());
auto *waitersHead = waiters_;
if (waitersHead == nullptr)
{
void *currentState = state_.load(std::memory_order_relaxed);
if (currentState == nullptr)
{
const bool releasedLock =
state_.compare_exchange_strong(currentState,
unlockedValue(),
std::memory_order_release,
std::memory_order_relaxed);
if (releasedLock)
{
return;
}
}
currentState = state_.exchange(nullptr, std::memory_order_acquire);
assert(currentState != unlockedValue());
assert(currentState != nullptr);
auto *waiter = static_cast<CoroMutexAwaiter *>(currentState);
do
{
auto *temp = waiter->next_;
waiter->next_ = waitersHead;
waitersHead = waiter;
waiter = temp;
} while (waiter != nullptr);
}
assert(waitersHead != nullptr);
waiters_ = waitersHead->next_;
if (waitersHead->loop_)
{
auto handle = waitersHead->handle_;
waitersHead->loop_->runInLoop([handle] { handle.resume(); });
}
else
{
waitersHead->handle_.resume();
}
}
private:
class CoroMutexAwaiter
{
public:
CoroMutexAwaiter(Mutex &mutex, trantor::EventLoop *loop) noexcept
: mutex_(mutex), loop_(loop)
{
}
bool await_ready() noexcept
{
return mutex_.try_lock();
}
bool await_suspend(std::coroutine_handle<> handle) noexcept
{
handle_ = handle;
return mutex_.asynclockImpl(this);
}
void await_resume() noexcept
{
}
private:
friend class Mutex;
Mutex &mutex_;
trantor::EventLoop *loop_;
std::coroutine_handle<> handle_;
CoroMutexAwaiter *next_;
};
class ScopedCoroMutexAwaiter : public CoroMutexAwaiter
{
public:
ScopedCoroMutexAwaiter(Mutex &mutex, trantor::EventLoop *loop)
: CoroMutexAwaiter(mutex, loop)
{
}
[[nodiscard]] auto await_resume() noexcept
{
return std::unique_lock<Mutex>{mutex_, std::adopt_lock};
}
};
bool asynclockImpl(CoroMutexAwaiter *awaiter)
{
void *oldValue = state_.load(std::memory_order_relaxed);
while (true)
{
if (oldValue == unlockedValue())
{
void *newValue = nullptr;
if (state_.compare_exchange_weak(oldValue,
newValue,
std::memory_order_acquire,
std::memory_order_relaxed))
{
return false;
}
}
else
{
void *newValue = awaiter;
awaiter->next_ = static_cast<CoroMutexAwaiter *>(oldValue);
if (state_.compare_exchange_weak(oldValue,
newValue,
std::memory_order_release,
std::memory_order_relaxed))
{
return true;
}
}
}
}
void *unlockedValue() noexcept
{
return this;
}
std::atomic<void *> state_;
CoroMutexAwaiter *waiters_;
};
} // namespace drogon

View File

@ -60,10 +60,8 @@ class Collector : public CollectorBase
{
}
template <typename... Arguments>
const std::shared_ptr<T> &metric(
const std::vector<std::string> &labelValues,
Arguments... args) noexcept(false)
const std::vector<std::string> &labelValues) noexcept(false)
{
if (labelValues.size() != labelsNames_.size())
{
@ -77,8 +75,7 @@ class Collector : public CollectorBase
{
return iter->second;
}
auto metric =
std::make_shared<T>(name_, labelsNames_, labelValues, args...);
auto metric = std::make_shared<T>(name_, labelsNames_, labelValues);
metrics_[labelValues] = metric;
return metrics_[labelValues];
}

View File

@ -91,7 +91,7 @@ class Gauge : public Metric
static std::string_view type()
{
return "gauge";
return "counter";
}
void setToCurrentTime()

View File

@ -68,7 +68,6 @@ class DROGON_EXPORT Histogram : public Metric
}
}
timeBuckets_.emplace_back();
timeBuckets_.back().buckets.resize(bucketBoundaries.size() + 1);
// check the bucket boundaries are sorted
for (size_t i = 1; i < bucketBoundaries.size(); i++)
{

View File

@ -11,7 +11,6 @@
* Drogon
*
*/
#pragma once
#include <chrono>
#include <functional>

View File

@ -20,10 +20,10 @@
namespace drogon
{
static void doAdviceChain(
static void doAdvicesChain(
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>> &adviceChain,
AdviceChainCallback &&)>> &advices,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
@ -88,7 +88,7 @@ void AopAdvice::passPreRoutingAdvices(
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
doAdvicesChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
@ -114,7 +114,7 @@ void AopAdvice::passPostRoutingAdvices(
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
doAdvicesChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
@ -140,7 +140,7 @@ void AopAdvice::passPreHandlingAdvices(
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doAdviceChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
doAdvicesChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
}
void AopAdvice::passPostHandlingAdvices(const HttpRequestImplPtr &req,
@ -161,32 +161,32 @@ void AopAdvice::passPreSendingAdvices(const HttpRequestImplPtr &req,
}
}
static void doAdviceChain(
static void doAdvicesChain(
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)>> &adviceChain,
AdviceChainCallback &&)>> &advices,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr)
{
if (index < adviceChain.size())
if (index < advices.size())
{
auto &advice = adviceChain[index];
auto &advice = advices[index];
advice(
req,
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
(*callbackPtr)(resp);
},
[index, req, callbackPtr, &adviceChain]() mutable {
[index, req, callbackPtr, &advices]() mutable {
auto ioLoop = req->getLoop();
if (ioLoop && !ioLoop->isInLoopThread())
{
ioLoop->queueInLoop([index,
req,
callbackPtr = std::move(callbackPtr),
&adviceChain]() mutable {
doAdviceChain(adviceChain,
&advices]() mutable {
doAdvicesChain(advices,
index + 1,
req,
std::move(callbackPtr));
@ -194,7 +194,7 @@ static void doAdviceChain(
}
else
{
doAdviceChain(adviceChain,
doAdvicesChain(advices,
index + 1,
req,
std::move(callbackPtr));

View File

@ -113,46 +113,6 @@ void AccessLogger::initAndStart(const Json::Value &config)
}
createLogFunctions(format);
auto logPath = config.get("log_path", "").asString();
if (config.isMember("path_exempt"))
{
if (config["path_exempt"].isArray())
{
const auto &exempts = config["path_exempt"];
size_t exemptsCount = exempts.size();
if (exemptsCount)
{
std::string regexString;
size_t len = 0;
for (const auto &exempt : exempts)
{
assert(exempt.isString());
len += exempt.size();
}
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
const auto last = --exempts.end();
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
regexString.append("(")
.append(exempt->asString())
.append(")|");
regexString.append("(").append(last->asString()).append(")");
exemptRegex_ = std::regex(regexString);
regexFlag_ = true;
}
}
else if (config["path_exempt"].isString())
{
exemptRegex_ = std::regex(config["path_exempt"].asString());
regexFlag_ = true;
}
else
{
LOG_ERROR << "path_exempt must be a string or string array!";
}
}
#ifdef DROGON_SPDLOG_SUPPORT
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
config.get("use_spdlog", false).asBool();
@ -268,17 +228,7 @@ void AccessLogger::initAndStart(const Json::Value &config)
drogon::app().registerPreSendingAdvice(
[this](const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) {
if (regexFlag_)
{
if (!std::regex_match(req->path(), exemptRegex_))
{
logging(LOG_RAW_TO(logIndex_), req, resp);
}
}
else
{
logging(LOG_RAW_TO(logIndex_), req, resp);
}
});
}
@ -417,12 +367,12 @@ void AccessLogger::outputDate(trantor::LogStream &stream,
{
if (useLocalTime_)
{
stream << trantor::Date::now().toCustomFormattedStringLocal(
stream << trantor::Date::now().toCustomedFormattedStringLocal(
timeFormat_, showMicroseconds_);
}
else
{
stream << trantor::Date::now().toCustomFormattedString(
stream << trantor::Date::now().toCustomedFormattedString(
timeFormat_, showMicroseconds_);
}
}
@ -448,12 +398,12 @@ void AccessLogger::outputReqDate(trantor::LogStream &stream,
{
if (useLocalTime_)
{
stream << req->creationDate().toCustomFormattedStringLocal(
stream << req->creationDate().toCustomedFormattedStringLocal(
timeFormat_, showMicroseconds_);
}
else
{
stream << req->creationDate().toCustomFormattedString(
stream << req->creationDate().toCustomedFormattedString(
timeFormat_, showMicroseconds_);
}
}

View File

@ -290,9 +290,8 @@ static void loadApp(const Json::Value &app)
std::vector<std::pair<std::string, std::string>> headers;
for (auto &header : app["static_file_headers"])
{
headers.emplace_back(
std::make_pair(header["name"].asString(),
header["value"].asString()));
headers.emplace_back(std::make_pair<std::string, std::string>(
header["name"].asString(), header["value"].asString()));
}
drogon::app().setStaticFileHeaders(headers);
}
@ -383,7 +382,7 @@ static void loadApp(const Json::Value &app)
{
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
}
#if !defined(_WIN32) && !TARGET_OS_IOS
#ifndef _WIN32
// dynamic views
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
if (enableDynamicViews)
@ -524,9 +523,6 @@ static void loadApp(const Json::Value &app)
bool enableCompressedRequests =
app.get("enabled_compressed_request", false).asBool();
drogon::app().enableCompressedRequest(enableCompressedRequests);
drogon::app().enableRequestStream(
app.get("enable_request_stream", false).asBool());
}
static void loadDbClients(const Json::Value &dbClients)
@ -541,9 +537,9 @@ static void loadDbClients(const Json::Value &dbClients)
type.begin(),
[](unsigned char c) { return tolower(c); });
auto host = client.get("host", "127.0.0.1").asString();
unsigned short port = client.get("port", 5432).asUInt();
auto port = client.get("port", 5432).asUInt();
auto dbname = client.get("dbname", "").asString();
if (dbname.empty() && type != "sqlite3")
if (dbname == "" && type != "sqlite3")
{
throw std::runtime_error(
"Please configure dbname in the configuration file");
@ -567,22 +563,11 @@ static void loadDbClients(const Json::Value &dbClients)
{
characterSet = client.get("client_encoding", "").asString();
}
auto connectOptions = client.get("connect_options", Json::Value());
auto timeout = client.get("timeout", -1.0).asDouble();
auto autoBatch = client.get("auto_batch", false).asBool();
std::unordered_map<std::string, std::string> options;
if (connectOptions.isObject() && !connectOptions.empty())
{
for (const auto &key : connectOptions.getMemberNames())
{
options[key] = connectOptions[key].asString();
}
}
HttpAppFrameworkImpl::instance().addDbClient(type,
drogon::app().createDbClient(type,
host,
port,
(unsigned short)port,
dbname,
user,
password,
@ -592,8 +577,7 @@ static void loadDbClients(const Json::Value &dbClients)
isFast,
characterSet,
timeout,
autoBatch,
std::move(options));
autoBatch);
}
}

View File

@ -23,7 +23,7 @@
namespace drogon
{
class HttpMiddlewareBase;
class HttpFilterBase;
/**
* @brief A component to associate router class and controller class
@ -31,8 +31,8 @@ class HttpMiddlewareBase;
struct ControllerBinderBase
{
std::string handlerName_;
std::vector<std::string> middlewareNames_;
std::vector<std::shared_ptr<HttpMiddlewareBase>> middlewares_;
std::vector<std::string> filterNames_;
std::vector<std::shared_ptr<HttpFilterBase>> filters_;
IOThreadStorage<HttpResponsePtr> responseCache_;
std::shared_ptr<std::string> corsMethods_;
bool isCORS_{false};
@ -41,11 +41,6 @@ struct ControllerBinderBase
virtual void handleRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) const = 0;
virtual bool isStreamHandler() const
{
return false;
}
};
struct RouteResult

View File

@ -19,18 +19,16 @@ using namespace drogon;
std::string Cookie::cookieString() const
{
constexpr std::string_view prefix = "Set-Cookie: ";
std::string ret;
std::string ret = "Set-Cookie: ";
// reserve space to reduce frequency allocation
ret.reserve(prefix.size() + key_.size() + value_.size() + 30);
ret = prefix;
ret.reserve(ret.size() + key_.size() + value_.size() + 30);
ret.append(key_).append("=").append(value_).append("; ");
if (expiresDate_.microSecondsSinceEpoch() !=
(std::numeric_limits<int64_t>::max)() &&
expiresDate_.microSecondsSinceEpoch() >= 0)
{
ret.append("Expires=")
.append(utils::getHttpFullDateStr(expiresDate_))
.append(utils::getHttpFullDate(expiresDate_))
.append("; ");
}
if (maxAge_.has_value())
@ -69,7 +67,7 @@ std::string Cookie::cookieString() const
ret.append("SameSite=Lax; ");
}
}
if ((secure_ && sameSite_ != SameSite::kNone) || partitioned_)
if (secure_ && sameSite_ != SameSite::kNone)
{
ret.append("Secure; ");
}
@ -77,10 +75,6 @@ std::string Cookie::cookieString() const
{
ret.append("HttpOnly; ");
}
if (partitioned_)
{
ret.append("Partitioned; ");
}
ret.resize(ret.length() - 2); // delete last semicolon
ret.append("\r\n");
return ret;

View File

@ -15,7 +15,6 @@
#pragma once
#include <drogon/orm/DbClient.h>
#include <drogon/orm/DbConfig.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/IOThreadStorage.h>
#include <trantor/utils/NonCopyable.h>
@ -30,7 +29,7 @@ namespace orm
class DbClientManager : public trantor::NonCopyable
{
public:
void createDbClients(const std::vector<trantor::EventLoop *> &ioLoops);
void createDbClients(const std::vector<trantor::EventLoop *> &ioloops);
DbClientPtr getDbClient(const std::string &name)
{
@ -47,7 +46,19 @@ class DbClientManager : public trantor::NonCopyable
return iter->second.getThreadData();
}
void addDbClient(const DbConfig &config);
void createDbClient(const std::string &dbType,
const std::string &host,
const unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
const size_t connectionNum,
const std::string &filename,
const std::string &name,
const bool isFast,
const std::string &characterSet,
double timeout,
const bool autoBatch);
bool areAllDbClientsAvailable() const noexcept;
private:
@ -55,8 +66,13 @@ class DbClientManager : public trantor::NonCopyable
struct DbInfo
{
std::string name_;
std::string connectionInfo_;
DbConfig config_;
ClientType dbType_;
bool isFast_;
size_t connectionNumber_;
double timeout_;
bool autoBatch_;
};
std::vector<DbInfo> dbInfos_;

View File

@ -13,6 +13,8 @@
*/
#include "DbClientManager.h"
#include <drogon/config.h>
#include <drogon/utils/Utilities.h>
#include <algorithm>
#include <stdlib.h>
@ -20,12 +22,24 @@ using namespace drogon::orm;
using namespace drogon;
void DbClientManager::createDbClients(
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
const std::vector<trantor::EventLoop *> & /*ioloops*/)
{
return;
}
void DbClientManager::addDbClient(const DbConfig &)
void DbClientManager::createDbClient(const std::string & /*dbType*/,
const std::string & /*host*/,
const unsigned short /*port*/,
const std::string & /*databaseName*/,
const std::string & /*userName*/,
const std::string & /*password*/,
const size_t /*connectionNum*/,
const std::string & /*filename*/,
const std::string & /*name*/,
const bool /*isFast*/,
const std::string & /*characterSet*/,
double /*timeout*/,
const bool /*autoBatch*/)
{
LOG_FATAL << "No database is supported by drogon, please install the "
"database development library first.";

View File

@ -22,8 +22,8 @@ namespace drogon
{
namespace internal
{
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>> &
getObjsMap()
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>>
&getObjsMap()
{
static std::unordered_map<std::string, std::shared_ptr<DrObjectBase>>
singleInstanceMap;
@ -112,8 +112,8 @@ std::vector<std::string> DrClassMap::getAllClassName()
return ret;
}
std::unordered_map<std::string, std::pair<DrAllocFunc, DrSharedAllocFunc>> &
DrClassMap::getMap()
std::unordered_map<std::string, std::pair<DrAllocFunc, DrSharedAllocFunc>>
&DrClassMap::getMap()
{
static std::unordered_map<std::string,
std::pair<DrAllocFunc, DrSharedAllocFunc>>

100
lib/src/FiltersFunction.cc Normal file
View File

@ -0,0 +1,100 @@
/**
*
* @file FiltersFunction.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "FiltersFunction.h"
#include "HttpRequestImpl.h"
#include "HttpResponseImpl.h"
#include "HttpAppFrameworkImpl.h"
#include <drogon/HttpFilter.h>
#include <queue>
namespace drogon
{
namespace filters_function
{
static void doFilterChains(
const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
size_t index,
const HttpRequestImplPtr &req,
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
&&callbackPtr)
{
if (index < filters.size())
{
auto &filter = filters[index];
filter->doFilter(
req,
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
(*callbackPtr)(resp);
},
[index, req, callbackPtr, &filters]() mutable {
auto ioLoop = req->getLoop();
if (ioLoop && !ioLoop->isInLoopThread())
{
ioLoop->queueInLoop(
[&filters,
index,
req,
callbackPtr = std::move(callbackPtr)]() mutable {
doFilterChains(filters,
index + 1,
req,
std::move(callbackPtr));
});
}
else
{
doFilterChains(filters,
index + 1,
req,
std::move(callbackPtr));
}
});
}
else
{
(*callbackPtr)(nullptr);
}
}
std::vector<std::shared_ptr<HttpFilterBase>> createFilters(
const std::vector<std::string> &filterNames)
{
std::vector<std::shared_ptr<HttpFilterBase>> filters;
for (auto const &filter : filterNames)
{
auto object_ = DrClassMap::getSingleInstance(filter);
auto filter_ = std::dynamic_pointer_cast<HttpFilterBase>(object_);
if (filter_)
filters.push_back(filter_);
else
{
LOG_ERROR << "filter " << filter << " not found";
}
}
return filters;
}
void doFilters(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
auto callbackPtr =
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
doFilterChains(filters, 0, req, std::move(callbackPtr));
}
} // namespace filters_function
} // namespace drogon

33
lib/src/FiltersFunction.h Normal file
View File

@ -0,0 +1,33 @@
/**
*
* FiltersFunction.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "impl_forwards.h"
#include <memory>
#include <string>
#include <vector>
namespace drogon
{
namespace filters_function
{
std::vector<std::shared_ptr<HttpFilterBase>> createFilters(
const std::vector<std::string> &filterNames);
void doFilters(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
} // namespace filters_function
} // namespace drogon

View File

@ -1,7 +1,7 @@
#include <drogon/plugins/GlobalFilters.h>
#include <drogon/DrClassMap.h>
#include <drogon/HttpAppFramework.h>
#include "MiddlewaresFunction.h"
#include "FiltersFunction.h"
#include "HttpRequestImpl.h"
#include "HttpAppFrameworkImpl.h"
@ -85,7 +85,7 @@ void GlobalFilters::initAndStart(const Json::Value &config)
}
}
drogon::middlewares_function::doFilters(
drogon::filters_function::doFilters(
thisPtr->filters_,
std::static_pointer_cast<HttpRequestImpl>(req),
[acb = std::move(acb),

View File

@ -105,17 +105,6 @@ void Hodor::initAndStart(const Json::Value &config)
limitStrategies_.emplace_back(makeLimitStrategy(subLimit));
}
}
const Json::Value &trustIps = config["trust_ips"];
if (!trustIps.isNull() && !trustIps.isArray())
{
throw std::runtime_error("Invalid trusted_ips. Should be array.");
}
for (const auto &ipOrCidr : trustIps)
{
trustCIDRs_.emplace_back(ipOrCidr.asString());
}
app().registerPreHandlingAdvice([this](const drogon::HttpRequestPtr &req,
AdviceCallback &&acb,
AdviceChainCallback &&accb) {
@ -130,13 +119,9 @@ void Hodor::shutdown()
bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
const LimitStrategy &strategy,
const trantor::InetAddress &ip,
const std::string &ip,
const std::optional<std::string> &userId)
{
if (RealIpResolver::matchCidr(ip, trustCIDRs_))
{
return true;
}
if (strategy.regexFlag)
{
if (!std::regex_match(req->path(), strategy.urlsRegex))
@ -155,7 +140,7 @@ bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
{
RateLimiterPtr limiterPtr;
strategy.ipLimiterMapPtr->modify(
ip.toIpNetEndian(),
ip,
[this, &limiterPtr, &strategy](RateLimiterPtr &ptr) {
if (!ptr)
{
@ -222,9 +207,10 @@ void Hodor::onHttpRequest(const drogon::HttpRequestPtr &req,
drogon::AdviceCallback &&adviceCallback,
drogon::AdviceChainCallback &&chainCallback)
{
const trantor::InetAddress &ip =
useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
: req->peerAddr();
auto ip =
(useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
: req->peerAddr())
.toIpNetEndian();
std::optional<std::string> userId;
if (userIdGetter_)
{

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