Compare commits

...

38 Commits

Author SHA1 Message Date
an-tao
a22956b82b Bump version to 1.9.11 2025-06-20 16:51:27 +08:00
Heran Yang
3c5749bbc2
Fix compile warning (#2337) 2025-06-18 17:43:25 +08:00
Heran Yang
7cd1ae8940
chore(workflow): upgrade Windows image and re-enable tests on Windows (#2336)
* chore(workflow): upgrade Windows image to 2022

* chore(test): re-enable tests on Windows
2025-06-18 15:47:28 +08:00
LordMZTE
c3f9192541
Add RawParameter API to pass raw SQL parameters to the database directly (#2335) 2025-06-17 23:15:47 +08:00
KEBE Mouhamad
e46e05e94a
dg_ctl: fix segfault when using --output option (#2330) 2025-06-05 13:52:33 +08:00
dm
26e7c6913c
Add support for continuation frame in WebSocketMessageParser (#2320)
Co-authored-by: antao <antao2002@gmail.com>
2025-06-04 10:02:07 +08:00
曹梦轩
8d640bafb4
Add cors example to demonstrate cross-origin support in drogon (#2323) 2025-05-26 10:10:39 +08:00
Leonardo Monteiro
f6b5404dbb
Add a new overload for execSqlCoro (#2314) 2025-05-22 18:16:11 +08:00
程憨憨
46b5c9044d
Fix issue with precision loss of double-type parameters in ORM inputs (#2310) 2025-05-19 19:07:37 +08:00
an-tao
ac0d4d0f89 Update trantor 2025-05-16 11:10:24 +08:00
cjserio
5c4057331e
Support for iOS compiling (#2307) 2025-05-15 17:25:56 +08:00
an-tao
95a518e7f2 Add qrcode for WeChat official account​ to the README file 2025-05-13 11:15:48 +08:00
KEBE Mouhamad
c03a3df106
added -o|--output option to drogon_ctl create models (#2304)
Co-authored-by: Mouhamad Kebe <mouhamad.kebe@ses.com>
2025-05-10 21:20:52 +08:00
Alexey Gerasimchuck
d6a33f93c9
Added handleFatalError in handleClosed (#2291) 2025-04-21 11:15:00 +08:00
An Tao
59cd4366c7
Fix CI on MacOS (#2289) 2025-04-08 14:11:03 +08:00
Alexey Gerasimchuck
c92d146374
Improved Postgres connection stability (#2286) 2025-04-08 11:03:02 +08:00
Axel Svensson
3c7c66e310
fix: Do not write to source directory during build (#2288)
Fixes #2287
2025-04-08 10:23:43 +08:00
Tanglong3bf
1fb67d68be
fix: Fix a bug in isAutoCreationClass<T>. (#2277) 2025-03-22 15:46:41 +08:00
antao
cbf63f8fc4 Bump version to 1.9.10 2025-02-20 21:19:18 +08:00
an-tao
d68e8aa554 Fix the CI status badge 2025-02-20 14:16:30 +08:00
An Tao
41537a6e86
Fix ci: codespell (#2259) 2025-02-18 22:29:47 +08:00
TheEnigmist
a32dc67867
Added path_exempt in AccessLogger plugin config to exclude desired path from logging (#2258) 2025-02-18 10:28:57 +08:00
ereynalabs
e155df9f66
Fix the issue in view generation by including the missing header file drogon/utils/Utilities.h (#2248)
Co-authored-by: dlinten <david.linten@gmail.com>
2025-02-06 15:19:44 +08:00
Alexey Gerasimchuck
f5de41f5d7
Make quit function thread safe (#2247) 2025-02-02 22:22:17 +08:00
pan93412
a3b4779540
Improve the zh-TW README translation (#2239)
* Improve the zh-TW README translation

* Unify the "View" term in the zh-TW README
2025-01-14 20:08:30 +08:00
fantasy-peak
686f68a12f
Add setConnectionCallback (#2204) 2025-01-08 20:00:44 +08:00
An Tao
152a69f1e9
ORM:Avoid unnecessary copies when returning search results (#2237) 2025-01-02 22:44:09 +08:00
antao
38dd5fea31 Bump version to 1.9.9 2025-01-01 11:26:30 +08:00
Alexey Gerasimchuck
3a6268f7e9
Added emptiness check to the LogStream &operator<< with std::string_view (#2234) 2024-12-19 10:36:20 +08:00
antao
47f8af7ca1 Update README 2024-12-14 01:17:49 +08:00
Heran Yang
44f796b796
Chore(workflow/cmake.yml): upgrade macos runner (#2228) 2024-12-10 12:56:24 +08:00
Antonio Nesic
df7e83ae74
Added Partitioned flag for cookies (#2230)
Co-authored-by: Antonio Nesic <anesic@collectivemind.dev>
2024-12-10 12:55:16 +08:00
EasyMoney322
99e816283d
Fix: Removed dependency on locales being installed (#2227) 2024-12-09 10:53:15 +08:00
EasyMoney322
3e944d28d8
Fixed issues created by the date string being localized (#2217) 2024-12-05 18:08:24 +08:00
Eyal Niv
1765223755
Fix CMAKE issues mentioned in #2144 and a linking problem which manifest with gcc12.3 when building with shared libs (#2208) (#2213)
* Respect find_package QUIET

* Add policy_max to cmake_minimum_required
Avoid deprecation warning introduced by cmake 3.31

* Add missing DROGON_EXPORT
2024-11-26 22:53:09 +08:00
hanhuayin
71b6d57cae
Update FindFilesystem.cmake to check for GNU instead of GCC for CMAKE_CXX_COMPILER_ID (#2211)
This patch fixes a build issue encountered in our Amazon Linux 2 environment when integrating Drogon alongside other dependencies. The issue occurred because the filesystem check failed to compile when the compiler ID was set to "GNU", due to missing -std=c++17 flag. The existing CXX_FILESYSTEM_HAVE_FS check was looking for "GCC" instead of "GNU". "GNU" is the correct identifier for gcc according to CMake documentation (see https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html).

Interestingly, this issue only manifested when Drogon was integrated alongside other dependencies. When Drogon was the sole project added to our build, compilation proceeded without error.
2024-11-25 16:15:21 +08:00
Tanglong3bf
882c1d9ecd
fix a bug in plugin Redirector. (#2198) 2024-11-16 15:04:57 +08:00
dependabot[bot]
8541e67143
Bump docker/login-action from 1 to 3 (#2197)
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-02 09:48:46 +08:00
57 changed files with 1011 additions and 184 deletions

View File

@ -17,7 +17,7 @@ env:
jobs: jobs:
windows: windows:
name: windows/msvc - ${{ matrix.link }} name: windows/msvc - ${{ matrix.link }}
runs-on: windows-2019 runs-on: windows-2022
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -44,9 +44,6 @@ jobs:
- name: Create Build Environment & Configure Cmake - name: Create Build Environment & Configure Cmake
shell: bash shell: bash
working-directory: ./build working-directory: ./build
# For unknown reasons, we fail to create file in windows ci environment.
# So examples, drogon_ctl and integration tests can not be built in windows ci.
# We should try to enable them again in the future.
run: | run: |
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF" [[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
cmake .. \ cmake .. \
@ -54,8 +51,8 @@ jobs:
-DBUILD_TESTING=on \ -DBUILD_TESTING=on \
-DBUILD_SHARED_LIBS=$shared \ -DBUILD_SHARED_LIBS=$shared \
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \ -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \
-DBUILD_CTL=OFF \ -DBUILD_CTL=ON \
-DBUILD_EXAMPLES=OFF \ -DBUILD_EXAMPLES=ON \
-DUSE_SPDLOG=ON \ -DUSE_SPDLOG=ON \
-DCMAKE_INSTALL_PREFIX=../install \ -DCMAKE_INSTALL_PREFIX=../install \
-DCMAKE_POLICY_DEFAULT_CMP0091=NEW \ -DCMAKE_POLICY_DEFAULT_CMP0091=NEW \
@ -73,7 +70,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
osver: [12, 13] osver: [13, 14, 15]
steps: steps:
- name: Checkout Drogon source code - name: Checkout Drogon source code
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -102,7 +99,6 @@ jobs:
- name: Prepare for testing - name: Prepare for testing
run: | run: |
brew tap homebrew/services
brew services restart postgresql@14 brew services restart postgresql@14
brew services start mariadb brew services start mariadb
brew services start redis brew services start redis

View File

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

View File

@ -13,7 +13,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}

1
.gitignore vendored
View File

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

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5...3.31)
project(drogon) project(drogon)
@ -25,7 +25,7 @@ CMAKE_DEPENDENT_OPTION(USE_SPDLOG "Allow using the spdlog logging library" OFF "
set(DROGON_MAJOR_VERSION 1) set(DROGON_MAJOR_VERSION 1)
set(DROGON_MINOR_VERSION 9) set(DROGON_MINOR_VERSION 9)
set(DROGON_PATCH_VERSION 8) set(DROGON_PATCH_VERSION 11)
set(DROGON_VERSION set(DROGON_VERSION
${DROGON_MAJOR_VERSION}.${DROGON_MINOR_VERSION}.${DROGON_PATCH_VERSION}) ${DROGON_MAJOR_VERSION}.${DROGON_MINOR_VERSION}.${DROGON_PATCH_VERSION})
set(DROGON_VERSION_STRING "${DROGON_VERSION}") set(DROGON_VERSION_STRING "${DROGON_VERSION}")
@ -42,7 +42,7 @@ set(INSTALL_DROGON_CMAKE_DIR ${DEF_INSTALL_DROGON_CMAKE_DIR}
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
# Force MSVC to use UTF-8 because that's what we use. Otherwise it uses # 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. # the default of whatever Windows sets and causes encoding issues.
message(STATUS "You are using MSVC. Forceing to use UTF-8") message(STATUS "You are using MSVC. Forcing to use UTF-8")
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>") add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
if (MSVC_VERSION GREATER_EQUAL 1914) if (MSVC_VERSION GREATER_EQUAL 1914)
@ -121,6 +121,7 @@ endif()
target_include_directories( target_include_directories(
${PROJECT_NAME} ${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc> 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}/orm_lib/inc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}> $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
@ -342,21 +343,21 @@ set(private_headers
lib/src/ConfigAdapter.h lib/src/ConfigAdapter.h
lib/src/MultipartStreamParser.h) lib/src/MultipartStreamParser.h)
if (NOT WIN32) if (NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(DROGON_SOURCES set(DROGON_SOURCES
${DROGON_SOURCES} ${DROGON_SOURCES}
lib/src/SharedLibManager.cc) lib/src/SharedLibManager.cc)
set(private_headers set(private_headers
${private_headers} ${private_headers}
lib/src/SharedLibManager.h) lib/src/SharedLibManager.h)
else (NOT WIN32) elseif(WIN32)
set(DROGON_SOURCES set(DROGON_SOURCES
${DROGON_SOURCES} ${DROGON_SOURCES}
third_party/mman-win32/mman.c) third_party/mman-win32/mman.c)
set(private_headers set(private_headers
${private_headers} ${private_headers}
third_party/mman-win32/mman.h) third_party/mman-win32/mman.h)
endif (NOT WIN32) endif()
if (BUILD_POSTGRESQL) if (BUILD_POSTGRESQL)
# find postgres # find postgres
@ -510,7 +511,7 @@ execute_process(COMMAND "git" rev-parse HEAD
OUTPUT_VARIABLE GIT_SHA1 OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in" configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in"
"${PROJECT_SOURCE_DIR}/lib/inc/drogon/version.h" @ONLY) "${CMAKE_CURRENT_BINARY_DIR}/lib/inc/drogon/version.h" @ONLY)
if (DROGON_CXX_STANDARD EQUAL 20) if (DROGON_CXX_STANDARD EQUAL 20)
option(USE_COROUTINE "Enable C++20 coroutine support" ON) option(USE_COROUTINE "Enable C++20 coroutine support" ON)
@ -583,7 +584,7 @@ set(DROGON_HEADERS
lib/inc/drogon/WebSocketConnection.h lib/inc/drogon/WebSocketConnection.h
lib/inc/drogon/WebSocketController.h lib/inc/drogon/WebSocketController.h
lib/inc/drogon/drogon.h lib/inc/drogon/drogon.h
lib/inc/drogon/version.h ${CMAKE_CURRENT_BINARY_DIR}/lib/inc/drogon/version.h
lib/inc/drogon/drogon_callbacks.h lib/inc/drogon/drogon_callbacks.h
lib/inc/drogon/PubSubService.h lib/inc/drogon/PubSubService.h
lib/inc/drogon/drogon_test.h lib/inc/drogon/drogon_test.h

View File

@ -4,6 +4,89 @@ All notable changes to this project will be documented in this file.
## [Unreleased] ## [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 ## [1.9.8] - 2024-10-27
### API changes list ### API changes list
@ -437,7 +520,7 @@ All notable changes to this project will be documented in this file.
- Remove unused CI files and Jekyll config. - Remove unused CI files and Jekyll config.
- Ensure that all filters, AOP advices, and handlers are executed within the IO threads. - Ensure that all filters, AOP advice, and handlers are executed within the IO threads.
- Update test.sh and build.sh by appending prefix "X" to string variable comparisons. - Update test.sh and build.sh by appending prefix "X" to string variable comparisons.
@ -769,7 +852,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. - Check HTTP client is not sending requests in sync mode on the same event loop.
- Start listening after beginning advices. - Start listening after beginning advice.
- Allow using json_cpp in other sublibraries. - Allow using json_cpp in other sublibraries.
@ -1759,7 +1842,13 @@ All notable changes to this project will be documented in this file.
## [1.0.0-beta1] - 2019-06-11 ## [1.0.0-beta1] - 2019-06-11
[Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.8...HEAD [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.8]: https://github.com/an-tao/drogon/compare/v1.9.7...v1.9.8

View File

@ -1,6 +1,6 @@
![](https://github.com/an-tao/drogon/wiki/images/drogon-white17.jpg) ![](https://github.com/an-tao/drogon/wiki/images/drogon-white17.jpg)
[![Build Status](https://github.com/an-tao/drogon/workflows/Build%20Drogon/badge.svg?branch=master)](https://github.com/drogonframework/drogon/actions) [![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) [![Conan Center](https://img.shields.io/conan/v/drogon)](https://conan.io/center/recipes/drogon)
[![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 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) [![Join our Discord](https://dcbadge.vercel.app/api/server/3DvHY6Ewuj?style=flat)](https://discord.gg/3DvHY6Ewuj)
@ -8,7 +8,7 @@
English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md) English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md)
### Overview ### 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 in the American TV series "Game of Thrones" that I really like. **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 cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD, HaikuOS, and Windows. Its main features are as follows: 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. 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 [wiki](https://github.com/an-tao/drogon/wiki/ENG-01-Overview)** 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**.
## Cross-compilation ## Cross-compilation

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -826,6 +826,7 @@ void create_model::createModel(const std::string &path,
auto restfulApiConfig = config["restful_api_controllers"]; auto restfulApiConfig = config["restful_api_controllers"];
auto relationships = getRelationships(config["relationships"]); auto relationships = getRelationships(config["relationships"]);
auto convertMethods = getConvertMethods(config["convert"]); auto convertMethods = getConvertMethods(config["convert"]);
drogon::utils::createPath(path);
if (dbType == "postgresql") if (dbType == "postgresql")
{ {
#if USE_POSTGRESQL #if USE_POSTGRESQL
@ -1173,7 +1174,9 @@ void create_model::createModel(const std::string &path,
try try
{ {
infile >> configJsonRoot; infile >> configJsonRoot;
createModel(path, configJsonRoot, singleModelName); createModel(outputPath_.empty() ? path : outputPath_,
configJsonRoot,
singleModelName);
} }
catch (const std::exception &exception) catch (const std::exception &exception)
{ {
@ -1211,6 +1214,22 @@ void create_model::handleCommand(std::vector<std::string> &parameters)
break; 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) for (auto const &path : parameters)
{ {
createModel(path, singleModelName); createModel(path, singleModelName);

View File

@ -429,5 +429,6 @@ class create_model : public DrObject<create_model>, public CommandHandler
const Json::Value &restfulApiConfig); const Json::Value &restfulApiConfig);
std::string dbname_; std::string dbname_;
bool forceOverwrite_{false}; bool forceOverwrite_{false};
std::string outputPath_;
}; };
} // namespace drogon_ctl } // namespace drogon_ctl

View File

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

View File

@ -34,6 +34,8 @@ add_executable(redis_chat redis_chat/main.cc
add_executable(async_stream async_stream/main.cc add_executable(async_stream async_stream/main.cc
async_stream/RequestStreamExampleCtrl.cc) async_stream/RequestStreamExampleCtrl.cc)
add_executable(cors cors/main.cc)
set(example_targets set(example_targets
benchmark benchmark
client client
@ -45,7 +47,8 @@ set(example_targets
jsonstore jsonstore
redis_simple redis_simple
redis_chat redis_chat
async_stream) async_stream
cors)
# Add warnings for our example targets--some warnings (such as -Wunused-parameter) only appear # 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. # when the templated functions are instantiated at their point of use.

View File

@ -16,6 +16,7 @@ proxy with a simple round robin
11. [redis_cache](https://github.com/drogonframework/drogon/tree/master/examples/redis_cache) - An example for using coroutines of Redis clients 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 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 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
### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite ### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite

View File

@ -1,15 +1,33 @@
#include <drogon/drogon.h> #include <drogon/drogon.h>
#include <chrono> #include <chrono>
#include <functional>
#include <mutex>
#include <unordered_map>
#include <trantor/utils/Logger.h>
#include <trantor/net/callbacks.h>
#include <trantor/net/TcpConnection.h>
using namespace drogon; using namespace drogon;
using namespace std::chrono_literals; using namespace std::chrono_literals;
std::mutex mutex;
std::unordered_map<trantor::TcpConnectionPtr, std::function<void()>>
connMapping;
int main() int main()
{ {
app().registerHandler( app().registerHandler(
"/stream", "/stream",
[](const HttpRequestPtr &, [](const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) { 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( auto resp = drogon::HttpResponse::newAsyncStreamResponse(
[](drogon::ResponseStreamPtr stream) { [](drogon::ResponseStreamPtr stream) {
std::thread([stream = std::thread([stream =
@ -79,5 +97,17 @@ int main()
LOG_INFO << "Server running on 127.0.0.1:8848"; LOG_INFO << "Server running on 127.0.0.1:8848";
app().enableRequestStream(); // This is for request stream. 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(); app().addListener("127.0.0.1", 8848).run();
} }

153
examples/cors/main.cc Normal file
View File

@ -0,0 +1,153 @@
#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

@ -156,6 +156,18 @@ class DROGON_EXPORT Cookie
sameSite_ = sameSite; 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 * @brief Get the string value of the cookie
*/ */
@ -282,6 +294,17 @@ class DROGON_EXPORT Cookie
return secure_; 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 * @brief Get the max-age of the cookie
*/ */
@ -394,6 +417,7 @@ class DROGON_EXPORT Cookie
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()}; trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
bool httpOnly_{true}; bool httpOnly_{true};
bool secure_{false}; bool secure_{false};
bool partitioned_{false};
std::string domain_; std::string domain_;
std::string path_; std::string path_;
std::string key_; std::string key_;

View File

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

View File

@ -43,6 +43,16 @@
#include <vector> #include <vector>
#include <chrono> #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 namespace drogon
{ {
// the drogon banner // the drogon banner
@ -349,7 +359,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Register an advice called before routing /// Register an advice called before routing
/** /**
* @param advice is called after all the synchronous advices return * @param advice is called after all the synchronous advice return
* nullptr and before the request is routed to any handler. The parameters * 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 * of the advice are same as those of the doFilter method of the Filter
* class. * class.
@ -997,7 +1007,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &setFileTypes( virtual HttpAppFramework &setFileTypes(
const std::vector<std::string> &types) = 0; const std::vector<std::string> &types) = 0;
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
/// Enable supporting for dynamic views loading. /// Enable supporting for dynamic views loading.
/** /**
* *
@ -1615,6 +1625,15 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &setAfterAcceptSockOptCallback( virtual HttpAppFramework &setAfterAcceptSockOptCallback(
std::function<void(int)> cb) = 0; 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 HttpAppFramework &enableRequestStream(bool enable = true) = 0;
virtual bool isRequestStreamEnabled() const = 0; virtual bool isRequestStreamEnabled() const = 0;

View File

@ -30,6 +30,7 @@
#include <unordered_map> #include <unordered_map>
#include <optional> #include <optional>
#include <string_view> #include <string_view>
#include <trantor/net/TcpConnection.h>
namespace drogon namespace drogon
{ {
@ -506,6 +507,9 @@ class DROGON_EXPORT HttpRequest
virtual bool connected() const noexcept = 0; virtual bool connected() const noexcept = 0;
virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept = 0;
virtual ~HttpRequest() virtual ~HttpRequest()
{ {
} }

View File

@ -88,7 +88,7 @@ class StreamError final : public std::exception
* An interface for stream request reading. * An interface for stream request reading.
* User should create an implementation class, or use built-in handlers * User should create an implementation class, or use built-in handlers
*/ */
class RequestStreamReader class DROGON_EXPORT RequestStreamReader
{ {
public: public:
virtual ~RequestStreamReader() = default; virtual ~RequestStreamReader() = default;

View File

@ -36,6 +36,7 @@ namespace plugin
// "show_microseconds": true, // "show_microseconds": true,
// "custom_time_format": "", // "custom_time_format": "",
// "use_real_ip": false // "use_real_ip": false
// "path_exempt": ""
} }
} }
@endcode @endcode
@ -98,6 +99,10 @@ namespace plugin
* Enable the plugin by adding the configuration to the list of plugins in the * Enable the plugin by adding the configuration to the list of plugins in the
* configuration file. * 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> class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
{ {
@ -117,6 +122,8 @@ class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
bool useCustomTimeFormat_{false}; bool useCustomTimeFormat_{false};
std::string timeFormat_; std::string timeFormat_;
static bool useRealIp_; static bool useRealIp_;
std::regex exemptRegex_;
bool regexFlag_{false};
using LogFunction = std::function<void(trantor::LogStream &, using LogFunction = std::function<void(trantor::LogStream &,
const drogon::HttpRequestPtr &, const drogon::HttpRequestPtr &,

View File

@ -301,6 +301,12 @@ DROGON_EXPORT std::string brotliDecompress(const char *data,
DROGON_EXPORT char *getHttpFullDate( DROGON_EXPORT char *getHttpFullDate(
const trantor::Date &date = trantor::Date::now()); 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 /// Get the trantor::Date object according to the http full date string
/** /**
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure. * Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
@ -578,7 +584,8 @@ namespace trantor
{ {
inline LogStream &operator<<(LogStream &ls, const std::string_view &v) inline LogStream &operator<<(LogStream &ls, const std::string_view &v)
{ {
ls.append(v.data(), v.length()); if (!v.empty())
ls.append(v.data(), v.length());
return ls; return ls;
} }

View File

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

View File

@ -113,6 +113,46 @@ void AccessLogger::initAndStart(const Json::Value &config)
} }
createLogFunctions(format); createLogFunctions(format);
auto logPath = config.get("log_path", "").asString(); 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 #ifdef DROGON_SPDLOG_SUPPORT
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() && auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
config.get("use_spdlog", false).asBool(); config.get("use_spdlog", false).asBool();
@ -228,7 +268,17 @@ void AccessLogger::initAndStart(const Json::Value &config)
drogon::app().registerPreSendingAdvice( drogon::app().registerPreSendingAdvice(
[this](const drogon::HttpRequestPtr &req, [this](const drogon::HttpRequestPtr &req,
const drogon::HttpResponsePtr &resp) { const drogon::HttpResponsePtr &resp) {
logging(LOG_RAW_TO(logIndex_), req, 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);
}
}); });
} }

View File

@ -383,7 +383,7 @@ static void loadApp(const Json::Value &app)
{ {
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP); drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
} }
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
// dynamic views // dynamic views
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool(); auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
if (enableDynamicViews) if (enableDynamicViews)

View File

@ -30,7 +30,7 @@ std::string Cookie::cookieString() const
expiresDate_.microSecondsSinceEpoch() >= 0) expiresDate_.microSecondsSinceEpoch() >= 0)
{ {
ret.append("Expires=") ret.append("Expires=")
.append(utils::getHttpFullDate(expiresDate_)) .append(utils::getHttpFullDateStr(expiresDate_))
.append("; "); .append("; ");
} }
if (maxAge_.has_value()) if (maxAge_.has_value())
@ -69,7 +69,7 @@ std::string Cookie::cookieString() const
ret.append("SameSite=Lax; "); ret.append("SameSite=Lax; ");
} }
} }
if (secure_ && sameSite_ != SameSite::kNone) if ((secure_ && sameSite_ != SameSite::kNone) || partitioned_)
{ {
ret.append("Secure; "); ret.append("Secure; ");
} }
@ -77,6 +77,10 @@ std::string Cookie::cookieString() const
{ {
ret.append("HttpOnly; "); ret.append("HttpOnly; ");
} }
if (partitioned_)
{
ret.append("Partitioned; ");
}
ret.resize(ret.length() - 2); // delete last semicolon ret.resize(ret.length() - 2); // delete last semicolon
ret.append("\r\n"); ret.append("\r\n");
return ret; return ret;

View File

@ -184,7 +184,7 @@ static void TERMFunction(int sig)
HttpAppFrameworkImpl::~HttpAppFrameworkImpl() noexcept HttpAppFrameworkImpl::~HttpAppFrameworkImpl() noexcept
{ {
// Destroy the following objects before the loop destruction // Destroy the following objects before the loop destruction
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
sharedLibManagerPtr_.reset(); sharedLibManagerPtr_.reset();
#endif #endif
sessionManagerPtr_.reset(); sessionManagerPtr_.reset();
@ -236,7 +236,7 @@ const std::string &HttpAppFrameworkImpl::getImplicitPage() const
{ {
return StaticFileRouter::instance().getImplicitPage(); return StaticFileRouter::instance().getImplicitPage();
} }
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
HttpAppFramework &HttpAppFrameworkImpl::enableDynamicViewsLoading( HttpAppFramework &HttpAppFrameworkImpl::enableDynamicViewsLoading(
const std::vector<std::string> &libPaths, const std::vector<std::string> &libPaths,
const std::string &outputPath) const std::string &outputPath)
@ -599,7 +599,7 @@ void HttpAppFrameworkImpl::run()
LOG_INFO << "Start child process"; LOG_INFO << "Start child process";
} }
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
if (!libFilePaths_.empty()) if (!libFilePaths_.empty())
{ {
sharedLibManagerPtr_ = sharedLibManagerPtr_ =
@ -1033,7 +1033,7 @@ HttpAppFramework &HttpAppFrameworkImpl::createRedisClient(
void HttpAppFrameworkImpl::quit() void HttpAppFrameworkImpl::quit()
{ {
if (getLoop()->isRunning()) if (getLoop()->isRunning() && running_.exchange(false))
{ {
getLoop()->queueInLoop([this]() { getLoop()->queueInLoop([this]() {
// Release members in the reverse order of initialization // Release members in the reverse order of initialization
@ -1044,7 +1044,6 @@ void HttpAppFrameworkImpl::quit()
pluginsManagerPtr_.reset(); pluginsManagerPtr_.reset();
redisClientManagerPtr_.reset(); redisClientManagerPtr_.reset();
dbClientManagerPtr_.reset(); dbClientManagerPtr_.reset();
running_ = false;
getLoop()->quit(); getLoop()->quit();
for (trantor::EventLoop *loop : ioLoopThreadPool_->getLoops()) for (trantor::EventLoop *loop : ioLoopThreadPool_->getLoops())
{ {
@ -1367,3 +1366,10 @@ HttpAppFramework &HttpAppFrameworkImpl::setAfterAcceptSockOptCallback(
listenerManagerPtr_->setAfterAcceptSockOptCallback(std::move(cb)); listenerManagerPtr_->setAfterAcceptSockOptCallback(std::move(cb));
return *this; return *this;
} }
HttpAppFramework &HttpAppFrameworkImpl::setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb)
{
listenerManagerPtr_->setConnectionCallback(std::move(cb));
return *this;
}

View File

@ -267,7 +267,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
HttpAppFramework &setUploadPath(const std::string &uploadPath) override; HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
HttpAppFramework &setFileTypes( HttpAppFramework &setFileTypes(
const std::vector<std::string> &types) override; const std::vector<std::string> &types) override;
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
HttpAppFramework &enableDynamicViewsLoading( HttpAppFramework &enableDynamicViewsLoading(
const std::vector<std::string> &libPaths, const std::vector<std::string> &libPaths,
const std::string &outputPath) override; const std::string &outputPath) override;
@ -665,6 +665,8 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
std::function<void(int)> cb) override; std::function<void(int)> cb) override;
HttpAppFramework &setAfterAcceptSockOptCallback( HttpAppFramework &setAfterAcceptSockOptCallback(
std::function<void(int)> cb) override; std::function<void(int)> cb) override;
HttpAppFramework &setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb) override;
HttpAppFramework &enableRequestStream(bool enable) override; HttpAppFramework &enableRequestStream(bool enable) override;
bool isRequestStreamEnabled() const override; bool isRequestStreamEnabled() const override;
@ -707,7 +709,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
size_t threadNum_{1}; size_t threadNum_{1};
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_; std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
#ifndef _WIN32 #if !defined(_WIN32) && !TARGET_OS_IOS
std::vector<std::string> libFilePaths_; std::vector<std::string> libFilePaths_;
std::string libFileOutputPath_; std::string libFileOutputPath_;
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_; std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;

View File

@ -16,6 +16,7 @@
#include "HttpUtils.h" #include "HttpUtils.h"
#include "CacheFile.h" #include "CacheFile.h"
#include "impl_forwards.h"
#include <drogon/utils/Utilities.h> #include <drogon/utils/Utilities.h>
#include <drogon/HttpRequest.h> #include <drogon/HttpRequest.h>
#include <drogon/RequestStream.h> #include <drogon/RequestStream.h>
@ -572,6 +573,12 @@ class HttpRequestImpl : public HttpRequest
return false; return false;
} }
const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept override
{
return connPtr_;
}
bool isOnSecureConnection() const noexcept override bool isOnSecureConnection() const noexcept override
{ {
return isOnSecureConnection_; return isOnSecureConnection_;

View File

@ -632,8 +632,7 @@ void HttpResponseImpl::renderToBuffer(trantor::MsgBuffer &buffer)
drogon::HttpAppFrameworkImpl::instance().sendDateHeader()) drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
{ {
buffer.append("date: "); buffer.append("date: ");
buffer.append(utils::getHttpFullDate(trantor::Date::date()), buffer.append(utils::getHttpFullDateStr(trantor::Date::date()));
httpFullDateStringLength);
buffer.append("\r\n\r\n"); buffer.append("\r\n\r\n");
} }
else else
@ -706,8 +705,7 @@ std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::renderToBuffer()
{ {
httpString->append("date: "); httpString->append("date: ");
auto datePos = httpString->readableBytes(); auto datePos = httpString->readableBytes();
httpString->append(utils::getHttpFullDate(trantor::Date::date()), httpString->append(utils::getHttpFullDateStr(trantor::Date::date()));
httpFullDateStringLength);
httpString->append("\r\n\r\n"); httpString->append("\r\n\r\n");
datePos_ = datePos; datePos_ = datePos;
} }

View File

@ -30,6 +30,7 @@
#include "HttpControllersRouter.h" #include "HttpControllersRouter.h"
#include "StaticFileRouter.h" #include "StaticFileRouter.h"
#include "WebSocketConnectionImpl.h" #include "WebSocketConnectionImpl.h"
#include "impl_forwards.h"
#if COZ_PROFILING #if COZ_PROFILING
#include <coz.h> #include <coz.h>
@ -75,7 +76,12 @@ HttpServer::HttpServer(EventLoop *loop,
: server_(loop, listenAddr, std::move(name), true, app().reusePort()) : server_(loop, listenAddr, std::move(name), true, app().reusePort())
#endif #endif
{ {
server_.setConnectionCallback(onConnection); server_.setConnectionCallback(
[this](const trantor::TcpConnectionPtr &conn) {
onConnection(conn);
if (connectionCallback_)
connectionCallback_(conn);
});
server_.setRecvMessageCallback(onMessage); server_.setRecvMessageCallback(onMessage);
server_.kickoffIdleConnections( server_.kickoffIdleConnections(
HttpAppFrameworkImpl::instance().getIdleConnectionTimeout()); HttpAppFrameworkImpl::instance().getIdleConnectionTimeout());
@ -307,7 +313,7 @@ void HttpServer::onRequests(
return; return;
} }
// flush response for not passing sync advices // flush response for not passing sync advice
if (conn->connected() && !requestParser->getResponseBuffer().empty()) if (conn->connected() && !requestParser->getResponseBuffer().empty())
{ {
sendResponses(conn, sendResponses(conn,
@ -1196,7 +1202,7 @@ static inline HttpResponsePtr tryDecompressRequest(
* @brief Check request against each sync advice, generate response if request * @brief Check request against each sync advice, generate response if request
* is rejected by any one of them. * is rejected by any one of them.
* *
* @return true if all sync advices are passed. * @return true if all sync advice are passed.
* @return false if rejected by any sync advice. * @return false if rejected by any sync advice.
*/ */
static inline bool passSyncAdvices( static inline bool passSyncAdvices(

View File

@ -69,6 +69,12 @@ class HttpServer : trantor::NonCopyable
afterAcceptSetSockOptCallback_ = std::move(cb); afterAcceptSetSockOptCallback_ = std::move(cb);
} }
void setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb)
{
connectionCallback_ = std::move(cb);
}
private: private:
friend class HttpInternalForwardHelper; friend class HttpInternalForwardHelper;
@ -144,6 +150,7 @@ class HttpServer : trantor::NonCopyable
std::function<void(int)> beforeListenSetSockOptCallback_; std::function<void(int)> beforeListenSetSockOptCallback_;
std::function<void(int)> afterAcceptSetSockOptCallback_; std::function<void(int)> afterAcceptSetSockOptCallback_;
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
}; };
class HttpInternalForwardHelper class HttpInternalForwardHelper

View File

@ -121,6 +121,10 @@ void ListenerManager::createListeners(
serverPtr->setAfterAcceptSockOptCallback( serverPtr->setAfterAcceptSockOptCallback(
afterAcceptSetSockOptCallback_); afterAcceptSetSockOptCallback_);
} }
if (connectionCallback_)
{
serverPtr->setConnectionCallback(connectionCallback_);
}
if (listener.useSSL_ && utils::supportsTls()) if (listener.useSSL_ && utils::supportsTls())
{ {

View File

@ -61,6 +61,12 @@ class ListenerManager : public trantor::NonCopyable
afterAcceptSetSockOptCallback_ = std::move(cb); afterAcceptSetSockOptCallback_ = std::move(cb);
} }
void setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb)
{
connectionCallback_ = std::move(cb);
}
void reloadSSLFiles(); void reloadSSLFiles();
private: private:
@ -101,6 +107,7 @@ class ListenerManager : public trantor::NonCopyable
std::unique_ptr<trantor::EventLoopThread> listeningThread_; std::unique_ptr<trantor::EventLoopThread> listeningThread_;
std::function<void(int)> beforeListenSetSockOptCallback_; std::function<void(int)> beforeListenSetSockOptCallback_;
std::function<void(int)> afterAcceptSetSockOptCallback_; std::function<void(int)> afterAcceptSetSockOptCallback_;
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
}; };
} // namespace drogon } // namespace drogon

View File

@ -30,6 +30,10 @@ void Redirector::initAndStart(const Json::Value &config)
} }
std::string protocol, host; std::string protocol, host;
bool pathChanged{false}; bool pathChanged{false};
for (auto &handler : thisPtr->pathRewriteHandlers_)
{
pathChanged |= handler(req);
}
for (auto &handler : thisPtr->handlers_) for (auto &handler : thisPtr->handlers_)
{ {
if (!handler(req, protocol, host, pathChanged)) if (!handler(req, protocol, host, pathChanged))
@ -37,10 +41,6 @@ void Redirector::initAndStart(const Json::Value &config)
return HttpResponse::newNotFoundResponse(req); return HttpResponse::newNotFoundResponse(req);
} }
} }
for (auto &handler : thisPtr->pathRewriteHandlers_)
{
pathChanged |= handler(req);
}
if (!protocol.empty() || !host.empty() || pathChanged) if (!protocol.empty() || !host.empty() || pathChanged)
{ {
std::string url; std::string url;

View File

@ -44,7 +44,7 @@ void StaticFileRouter::init(const std::vector<trantor::EventLoop *> &ioLoops)
size_t i) { size_t i) {
assert(i == ioLoops[i]->index()); assert(i == ioLoops[i]->index());
mapPtr = std::make_unique<CacheMap<std::string, char>>(ioLoops[i], mapPtr = std::make_unique<CacheMap<std::string, char>>(ioLoops[i],
1.0, 1.0f,
4, 4,
50); 50);
}); });

View File

@ -1034,6 +1034,35 @@ char *getHttpFullDate(const trantor::Date &date)
return lastTimeString; return lastTimeString;
} }
void dateToCustomFormattedString(const std::string &fmtStr,
std::string &str,
const trantor::Date &date)
{
auto nowSecond = date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
time_t seconds = static_cast<time_t>(nowSecond);
struct tm tm_LValue = date.tmStruct();
std::stringstream Out;
Out.imbue(std::locale{"C"});
Out << std::put_time(&tm_LValue, fmtStr.c_str());
str = Out.str();
}
const std::string &getHttpFullDateStr(const trantor::Date &date)
{
static thread_local int64_t lastSecond = 0;
static thread_local std::string lastTimeString(128, 0);
auto nowSecond = date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
if (nowSecond == lastSecond)
{
return lastTimeString;
}
lastSecond = nowSecond;
dateToCustomFormattedString("%a, %d %b %Y %H:%M:%S GMT",
lastTimeString,
date);
return lastTimeString;
}
trantor::Date getHttpDate(const std::string &httpFullDateString) trantor::Date getHttpDate(const std::string &httpFullDateString)
{ {
static const std::array<const char *, 4> formats = { static const std::array<const char *, 4> formats = {

View File

@ -17,6 +17,7 @@
#include <json/value.h> #include <json/value.h>
#include <json/writer.h> #include <json/writer.h>
#include <thread> #include <thread>
#include <limits>
using namespace drogon; using namespace drogon;
@ -268,14 +269,14 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
{ {
// According to the rfc6455 // According to the rfc6455
gotAll_ = false; gotAll_ = false;
if (buffer->readableBytes() >= 2) while (buffer->readableBytes() >= 2)
{ {
unsigned char opcode = (*buffer)[0] & 0x0f; unsigned char opcode = (*buffer)[0] & 0x0f;
bool isControlFrame = false; bool isControlFrame = false;
switch (opcode) switch (opcode)
{ {
case 0: case 0:
// continuation frame LOG_TRACE << "continuation frame";
break; break;
case 1: case 1:
type_ = WebSocketMessageType::Text; type_ = WebSocketMessageType::Text;
@ -327,8 +328,13 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
{ {
indexFirstMask = 10; indexFirstMask = 10;
} }
if (indexFirstMask > 2 && buffer->readableBytes() >= indexFirstMask) if (indexFirstMask > 2)
{ {
if (buffer->readableBytes() < indexFirstMask)
{
// Not enough data yet, wait for more.
return true;
}
if (isControlFrame) if (isControlFrame)
{ {
// rfc6455-5.5 // rfc6455-5.5
@ -344,14 +350,17 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
} }
else if (indexFirstMask == 10) else if (indexFirstMask == 10)
{ {
length = (unsigned char)(*buffer)[2]; length = 0;
length = (length << 8) + (unsigned char)(*buffer)[3]; for (int i = 2; i <= 9; ++i)
length = (length << 8) + (unsigned char)(*buffer)[4]; {
length = (length << 8) + (unsigned char)(*buffer)[5]; if (length > ((std::numeric_limits<size_t>::max)() >> 8))
length = (length << 8) + (unsigned char)(*buffer)[6]; {
length = (length << 8) + (unsigned char)(*buffer)[7]; LOG_ERROR
length = (length << 8) + (unsigned char)(*buffer)[8]; << "Payload length too large to handle safely";
length = (length << 8) + (unsigned char)(*buffer)[9]; return false;
}
length = (length << 8) + (unsigned char)(*buffer)[i];
}
} }
else else
{ {
@ -380,9 +389,16 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
{ {
message_[oldLen + i] = (rawData[i] ^ masks[i % 4]); message_[oldLen + i] = (rawData[i] ^ masks[i % 4]);
} }
if (isFin)
gotAll_ = true;
buffer->retrieve(indexFirstMask + 4 + length); buffer->retrieve(indexFirstMask + 4 + length);
if (isFin)
{
gotAll_ = true;
return true;
}
}
else
{
// Not enough data yet, wait for more.
return true; return true;
} }
} }
@ -392,9 +408,16 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
{ {
auto rawData = buffer->peek() + indexFirstMask; auto rawData = buffer->peek() + indexFirstMask;
message_.append(rawData, length); message_.append(rawData, length);
if (isFin)
gotAll_ = true;
buffer->retrieve(indexFirstMask + length); buffer->retrieve(indexFirstMask + length);
if (isFin)
{
gotAll_ = true;
return true;
}
}
else
{
// Not enough data yet, wait for more.
return true; return true;
} }
} }

View File

@ -408,8 +408,7 @@ int main()
app().registerCustomExtensionMime("md", "text/markdown"); app().registerCustomExtensionMime("md", "text/markdown");
app().setFileTypes({"md", "html", "jpg", "cc", "txt"}); app().setFileTypes({"md", "html", "jpg", "cc", "txt"});
std::cout << "Date: " std::cout << "Date: "
<< std::string{drogon::utils::getHttpFullDate( << drogon::utils::getHttpFullDateStr(trantor::Date::now())
trantor::Date::now())}
<< std::endl; << std::endl;
app().registerBeginningAdvice( app().registerBeginningAdvice(

View File

@ -35,4 +35,29 @@ DROGON_TEST(CookieTest)
drogon::Cookie::convertString2SameSite("Strict")); drogon::Cookie::convertString2SameSite("Strict"));
CHECK(drogon::Cookie::SameSite::kNone == CHECK(drogon::Cookie::SameSite::kNone ==
drogon::Cookie::convertString2SameSite("None")); drogon::Cookie::convertString2SameSite("None"));
// Test for Partitioned attribute
drogon::Cookie cookie6("test", "6");
cookie6.setPartitioned(true);
CHECK(cookie6.cookieString() ==
"Set-Cookie: test=6; Secure; HttpOnly; Partitioned\r\n");
// Test that partitioned attribute automatically sets secure
drogon::Cookie cookie7("test", "7");
cookie7.setPartitioned(true);
CHECK(cookie7.isSecure() == true);
// Test other attributes
drogon::Cookie cookie8("test", "8");
cookie8.setPartitioned(true);
cookie8.setDomain("drogon.org");
cookie8.setMaxAge(3600);
CHECK(cookie8.cookieString() ==
"Set-Cookie: test=8; Max-Age=3600; Domain=drogon.org; Secure; "
"HttpOnly; Partitioned\r\n");
// Teset Partitioned and SameSite can coexist
drogon::Cookie cookie9("test", "9");
cookie9.setPartitioned(true);
cookie9.setSameSite(drogon::Cookie::SameSite::kLax);
CHECK(
cookie9.cookieString() ==
"Set-Cookie: test=9; SameSite=Lax; Secure; HttpOnly; Partitioned\r\n");
} }

View File

@ -1,5 +1,6 @@
#include <drogon/DrObject.h> #include <drogon/DrObject.h>
#include <drogon/drogon_test.h> #include <drogon/drogon_test.h>
#include <drogon/HttpController.h>
using namespace drogon; using namespace drogon;
@ -41,3 +42,45 @@ DROGON_TEST(DrObjectNamespaceTest)
CHECK(objPtr2.get() != nullptr); CHECK(objPtr2.get() != nullptr);
CHECK(objPtr == objPtr2); CHECK(objPtr == objPtr2);
} }
class TestC : public DrObject<TestC>
{
public:
static constexpr bool isAutoCreation = true;
};
class TestD : public DrObject<TestD>
{
public:
static constexpr bool isAutoCreation = false;
};
class TestE : public DrObject<TestE>
{
public:
static constexpr double isAutoCreation = 3.0;
};
class CtrlA : public HttpController<CtrlA>
{
public:
METHOD_LIST_BEGIN
METHOD_LIST_END
};
class CtrlB : public HttpController<CtrlB, false>
{
public:
METHOD_LIST_BEGIN
METHOD_LIST_END
};
DROGON_TEST(IsAutoCreationClassTest)
{
STATIC_REQUIRE(isAutoCreationClass<TestA>::value == false);
STATIC_REQUIRE(isAutoCreationClass<TestC>::value == true);
STATIC_REQUIRE(isAutoCreationClass<TestD>::value == false);
STATIC_REQUIRE(isAutoCreationClass<TestE>::value == false);
STATIC_REQUIRE(isAutoCreationClass<CtrlA>::value == true);
STATIC_REQUIRE(isAutoCreationClass<CtrlB>::value == false);
}

View File

@ -7,7 +7,7 @@ using namespace drogon;
DROGON_TEST(HttpFullDateTest) DROGON_TEST(HttpFullDateTest)
{ {
auto str = utils::getHttpFullDate(); auto str = utils::getHttpFullDateStr();
auto date = utils::getHttpDate(str); auto date = utils::getHttpDate(str);
CHECK(utils::getHttpFullDate(date) == str); CHECK(utils::getHttpFullDate(date) == str);
} }

View File

@ -214,6 +214,28 @@ class DROGON_EXPORT DbClient : public trantor::NonCopyable
(binder << std::forward<Arguments>(args), 0)...}; (binder << std::forward<Arguments>(args), 0)...};
return internal::SqlAwaiter(std::move(binder)); return internal::SqlAwaiter(std::move(binder));
} }
/**
* @brief Execute a SQL query asynchronously using coroutine support.
* This overload accepts a vector of arguments to bind to the query.
* @tparam T The type of the elements in the vector.
* @param sql The SQL query string to execute.
* @param args A vector of arguments to bind to the query.
* @return A SqlAwaiter object that can be co_awaited to retrieve the query
* result.
* @note This method is only available when coroutine support is enabled.
*/
template <typename T>
internal::SqlAwaiter execSqlCoro(const std::string &sql,
const std::vector<T> &args) noexcept
{
auto binder = *this << sql;
for (const auto &arg : args)
{
binder << arg;
}
return internal::SqlAwaiter(std::move(binder));
}
#endif #endif
/// Streaming-like method for sql execution. For more information, see the /// Streaming-like method for sql execution. For more information, see the

View File

@ -1053,9 +1053,9 @@ inline void Mapper<T>::findBy(const Criteria &criteria,
std::vector<T> ret; std::vector<T> ret;
for (auto const &row : r) for (auto const &row : r)
{ {
ret.push_back(T(row)); ret.emplace_back(row);
} }
rcb(ret); rcb(std::move(ret));
}; };
binder >> ecb; binder >> ecb;
} }

View File

@ -116,6 +116,14 @@ enum class Mode
Blocking Blocking
}; };
struct RawParameter
{
std::shared_ptr<void> obj;
const char *parameter;
int length;
int format;
};
namespace internal namespace internal
{ {
template <typename T> template <typename T>
@ -434,6 +442,15 @@ class DROGON_EXPORT SqlBinder : public trantor::NonCopyable
return *this; return *this;
} }
self &operator<<(const RawParameter &);
self &operator<<(RawParameter &param)
{
return operator<<((const RawParameter &)param);
}
self &operator<<(RawParameter &&);
// template <> // template <>
self &operator<<(const char str[]) self &operator<<(const char str[])
{ {

View File

@ -429,6 +429,8 @@ DbConnectionPtr DbClientImpl::newConnection(trantor::EventLoop *loop)
} }
// Reconnect after 1 second // Reconnect after 1 second
auto loop = closeConnPtr->loop(); auto loop = closeConnPtr->loop();
// closeConnPtr may be not valid. Close the connection file descriptor.
closeConnPtr->disconnect();
loop->runAfter(1, [weakPtr, loop, closeConnPtr] { loop->runAfter(1, [weakPtr, loop, closeConnPtr] {
auto thisPtr = weakPtr.lock(); auto thisPtr = weakPtr.lock();
if (!thisPtr) if (!thisPtr)

View File

@ -118,7 +118,7 @@ class DbConnection : public trantor::NonCopyable
virtual ~DbConnection() virtual ~DbConnection()
{ {
LOG_TRACE << "Destruct DbConn" << this; LOG_TRACE << "Destruct DbConn " << this;
} }
ConnectStatus status() const ConnectStatus status() const

View File

@ -18,6 +18,9 @@
#include <drogon/utils/Utilities.h> #include <drogon/utils/Utilities.h>
#include <future> #include <future>
#include <regex> #include <regex>
#if defined(__cpp_lib_format)
#include <format>
#endif
#if USE_MYSQL #if USE_MYSQL
#include <mysql.h> #include <mysql.h>
#endif #endif
@ -134,6 +137,26 @@ SqlBinder::~SqlBinder()
} }
} }
SqlBinder &SqlBinder::operator<<(const RawParameter &param)
{
objs_.push_back(param.obj);
parameters_.push_back(param.parameter);
lengths_.push_back(param.length);
formats_.push_back(param.format);
++parametersNumber_;
return *this;
}
SqlBinder &SqlBinder::operator<<(RawParameter &&param)
{
objs_.push_back(std::move(param.obj));
parameters_.push_back(std::move(param.parameter));
lengths_.push_back(std::move(param.length));
formats_.push_back(std::move(param.format));
++parametersNumber_;
return *this;
}
SqlBinder &SqlBinder::operator<<(const std::string_view &str) SqlBinder &SqlBinder::operator<<(const std::string_view &str)
{ {
auto obj = std::make_shared<std::string>(str.data(), str.length()); auto obj = std::make_shared<std::string>(str.data(), str.length());
@ -259,7 +282,14 @@ SqlBinder &SqlBinder::operator<<(double f)
parameters_.push_back((char *)(obj.get())); parameters_.push_back((char *)(obj.get()));
return *this; return *this;
} }
return operator<<(std::to_string(f));
#if defined(__cpp_lib_format)
return operator<<(std::format("{:.17g}", f));
#else
std::stringstream ss;
ss << std::setprecision(17) << f;
return operator<<(ss.str());
#endif
} }
SqlBinder &SqlBinder::operator<<(std::nullptr_t) SqlBinder &SqlBinder::operator<<(std::nullptr_t)

View File

@ -87,11 +87,14 @@ PgConnection::PgConnection(trantor::EventLoop *loop,
[](PGconn *conn) { PQfinish(conn); })), [](PGconn *conn) { PQfinish(conn); })),
channel_(loop, PQsocket(connectionPtr_.get())) channel_(loop, PQsocket(connectionPtr_.get()))
{ {
if (channel_.fd() < 0)
{
LOG_ERROR << "Failed to create Postgres connection";
}
} }
void PgConnection::init() void PgConnection::init()
{ {
PQsetnonblocking(connectionPtr_.get(), 1);
if (channel_.fd() < 0) if (channel_.fd() < 0)
{ {
LOG_ERROR << "Connection with Postgres could not be established"; LOG_ERROR << "Connection with Postgres could not be established";
@ -103,6 +106,8 @@ void PgConnection::init()
} }
return; return;
} }
PQsetnonblocking(connectionPtr_.get(), 1);
channel_.setReadCallback([this]() { channel_.setReadCallback([this]() {
if (status_ == ConnectStatus::Bad) if (status_ == ConnectStatus::Bad)
{ {
@ -165,8 +170,11 @@ void PgConnection::disconnect()
auto thisPtr = shared_from_this(); auto thisPtr = shared_from_this();
loop_->runInLoop([thisPtr, &pro]() { loop_->runInLoop([thisPtr, &pro]() {
thisPtr->status_ = ConnectStatus::Bad; thisPtr->status_ = ConnectStatus::Bad;
thisPtr->channel_.disableAll(); if (thisPtr->channel_.fd() >= 0)
thisPtr->channel_.remove(); {
thisPtr->channel_.disableAll();
thisPtr->channel_.remove();
}
thisPtr->connectionPtr_.reset(); thisPtr->connectionPtr_.reset();
pro.set_value(1); pro.set_value(1);
}); });
@ -522,11 +530,17 @@ void PgConnection::handleFatalError(bool clearAll, bool isAbortPipeline)
{ {
for (auto &cmd : batchCommandsForWaitingResults_) for (auto &cmd : batchCommandsForWaitingResults_)
{ {
cmd->exceptionCallback_(exceptPtr); if (cmd->exceptionCallback_)
{
cmd->exceptionCallback_(exceptPtr);
}
} }
for (auto &cmd : batchSqlCommands_) for (auto &cmd : batchSqlCommands_)
{ {
cmd->exceptionCallback_(exceptPtr); if (cmd->exceptionCallback_)
{
cmd->exceptionCallback_(exceptPtr);
}
} }
batchCommandsForWaitingResults_.clear(); batchCommandsForWaitingResults_.clear();
batchSqlCommands_.clear(); batchSqlCommands_.clear();
@ -536,13 +550,19 @@ void PgConnection::handleFatalError(bool clearAll, bool isAbortPipeline)
if (!batchSqlCommands_.empty() && if (!batchSqlCommands_.empty() &&
!batchSqlCommands_.front()->preparingStatement_.empty()) !batchSqlCommands_.front()->preparingStatement_.empty())
{ {
batchSqlCommands_.front()->exceptionCallback_(exceptPtr); if (batchSqlCommands_.front()->exceptionCallback_)
{
batchSqlCommands_.front()->exceptionCallback_(exceptPtr);
}
batchSqlCommands_.pop_front(); batchSqlCommands_.pop_front();
} }
else if (!batchCommandsForWaitingResults_.empty()) else if (!batchCommandsForWaitingResults_.empty())
{ {
auto &cmd = batchCommandsForWaitingResults_.front(); auto &cmd = batchCommandsForWaitingResults_.front();
cmd->exceptionCallback_(exceptPtr); if (cmd->exceptionCallback_)
{
cmd->exceptionCallback_(exceptPtr);
}
batchCommandsForWaitingResults_.pop_front(); batchCommandsForWaitingResults_.pop_front();
} }
else else

View File

@ -65,11 +65,14 @@ PgConnection::PgConnection(trantor::EventLoop *loop,
[](PGconn *conn) { PQfinish(conn); })), [](PGconn *conn) { PQfinish(conn); })),
channel_(loop, PQsocket(connectionPtr_.get())) channel_(loop, PQsocket(connectionPtr_.get()))
{ {
if (channel_.fd() < 0)
{
LOG_ERROR << "Failed to create Postgres connection";
}
} }
void PgConnection::init() void PgConnection::init()
{ {
PQsetnonblocking(connectionPtr_.get(), 1);
if (channel_.fd() < 0) if (channel_.fd() < 0)
{ {
LOG_ERROR << "Connection with Postgres could not be established"; LOG_ERROR << "Connection with Postgres could not be established";
@ -80,6 +83,8 @@ void PgConnection::init()
} }
return; return;
} }
PQsetnonblocking(connectionPtr_.get(), 1);
channel_.setReadCallback([this]() { channel_.setReadCallback([this]() {
if (status_ == ConnectStatus::Bad) if (status_ == ConnectStatus::Bad)
{ {
@ -128,6 +133,15 @@ void PgConnection::handleClosed()
if (status_ == ConnectStatus::Bad) if (status_ == ConnectStatus::Bad)
return; return;
status_ = ConnectStatus::Bad; status_ = ConnectStatus::Bad;
if (isWorking_)
{
// Connection was closed unexpectedly while isWorking_ was true.
isWorking_ = false;
handleFatalError();
callback_ = nullptr;
}
channel_.disableAll(); channel_.disableAll();
channel_.remove(); channel_.remove();
assert(closeCallback_); assert(closeCallback_);
@ -142,8 +156,11 @@ void PgConnection::disconnect()
auto thisPtr = shared_from_this(); auto thisPtr = shared_from_this();
loop_->runInLoop([thisPtr, &pro]() { loop_->runInLoop([thisPtr, &pro]() {
thisPtr->status_ = ConnectStatus::Bad; thisPtr->status_ = ConnectStatus::Bad;
thisPtr->channel_.disableAll(); if (thisPtr->channel_.fd() >= 0)
thisPtr->channel_.remove(); {
thisPtr->channel_.disableAll();
thisPtr->channel_.remove();
}
thisPtr->connectionPtr_.reset(); thisPtr->connectionPtr_.reset();
pro.set_value(1); pro.set_value(1);
}); });
@ -398,9 +415,13 @@ void PgConnection::doAfterPreparing()
void PgConnection::handleFatalError() void PgConnection::handleFatalError()
{ {
auto exceptPtr = if (exceptionCallback_)
std::make_exception_ptr(Failure(PQerrorMessage(connectionPtr_.get()))); {
exceptionCallback_(exceptPtr); auto exceptPtr = std::make_exception_ptr(
Failure(PQerrorMessage(connectionPtr_.get())));
exceptionCallback_(exceptPtr);
}
exceptionCallback_ = nullptr; exceptionCallback_ = nullptr;
} }

View File

@ -287,11 +287,24 @@ DROGON_TEST(PostgreTest)
FAULT("postgresql - DbClient streaming-type interface(8) what():", FAULT("postgresql - DbClient streaming-type interface(8) what():",
e.base().what()); e.base().what());
}; };
/// 1.10 clean up /// 1.10 query with raw parameter
auto rawParamData = std::make_shared<int>(htonl(3));
auto rawParam = RawParameter{rawParamData,
reinterpret_cast<char *>(rawParamData.get()),
sizeof(int),
1};
*clientPtr << "select * from users where length(user_id)=$1" << rawParam >>
[TEST_CTX](const Result &r) { MANDATE(r.size() == 1); } >>
[TEST_CTX](const DrogonDbException &e) {
FAULT("postgresql - DbClient streaming-type interface(9) what():",
e.base().what());
};
/// 1.11 clean up
*clientPtr << "truncate table users restart identity" >> *clientPtr << "truncate table users restart identity" >>
[TEST_CTX](const Result &r) { SUCCESS(); } >> [TEST_CTX](const Result &r) { SUCCESS(); } >>
[TEST_CTX](const DrogonDbException &e) { [TEST_CTX](const DrogonDbException &e) {
FAULT("postgresql - DbClient streaming-type interface(9) what():", FAULT("postgresql - DbClient streaming-type interface(10) what():",
e.base().what()); e.base().what());
}; };
/// Test asynchronous method /// Test asynchronous method
@ -379,12 +392,21 @@ DROGON_TEST(PostgreTest)
"postgresql1", "postgresql1",
"pg", "pg",
"postgresql"); "postgresql");
/// 2.6 clean up /// 2.6 query with raw parameter
clientPtr->execSqlAsync(
"select * from users where length(user_id)=$1",
[TEST_CTX](const Result &r) { MANDATE(r.size() == 1); },
[TEST_CTX](const DrogonDbException &e) {
FAULT("postgresql - DbClient asynchronous interface(7) what():",
e.base().what());
},
rawParam);
/// 2.7 clean up
clientPtr->execSqlAsync( clientPtr->execSqlAsync(
"truncate table users restart identity", "truncate table users restart identity",
[TEST_CTX](const Result &r) { SUCCESS(); }, [TEST_CTX](const Result &r) { SUCCESS(); },
[TEST_CTX](const DrogonDbException &e) { [TEST_CTX](const DrogonDbException &e) {
FAULT("postgresql - DbClient asynchronous interface(7) what():", FAULT("postgresql - DbClient asynchronous interface(8) what():",
e.base().what()); e.base().what());
}); });
@ -464,7 +486,19 @@ DROGON_TEST(PostgreTest)
{ {
SUCCESS(); SUCCESS();
} }
/// 3.6 clean up /// 3.6 query with raw parameter
try
{
auto r = clientPtr->execSqlSync(
"select * from users where length(user_id)=$1", rawParam);
MANDATE(r.size() == 1);
}
catch (const DrogonDbException &e)
{
FAULT("postgresql - DbClient asynchronous interface(4) what():",
e.base().what());
}
/// 3.7 clean up
try try
{ {
auto r = auto r =
@ -557,7 +591,20 @@ DROGON_TEST(PostgreTest)
{ {
SUCCESS(); SUCCESS();
} }
/// 4.6 clean up /// 4.6 query with raw parameter
f = clientPtr->execSqlAsyncFuture(
"select * from users where length(user_id)=$1", rawParam);
try
{
auto r = f.get();
MANDATE(r.size() == 1);
}
catch (const DrogonDbException &e)
{
FAULT("postgresql - DbClient future interface(4) what():",
e.base().what());
}
/// 4.7 clean up
f = clientPtr->execSqlAsyncFuture("truncate table users restart identity"); f = clientPtr->execSqlAsyncFuture("truncate table users restart identity");
try try
{ {
@ -1670,11 +1717,28 @@ DROGON_TEST(MySQLTest)
FAULT("mysql - DbClient streaming-type interface(8) what():", FAULT("mysql - DbClient streaming-type interface(8) what():",
e.base().what()); e.base().what());
}; };
/// 1.10 truncate /// 1.10 query with raw parameter
// MariaDB uses little-endian, so the opposite of network ordering :P
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
auto rawParamData = std::make_shared<int>(3);
#else
auto rawParamData = std::make_shared<int>(0x03000000); // byteswapped 3
#endif
auto rawParam = RawParameter{rawParamData,
reinterpret_cast<char *>(rawParamData.get()),
sizeof(int),
internal::MySqlLong};
*clientPtr << "select * from users where length(user_id)=?" << rawParam >>
[TEST_CTX](const Result &r) { MANDATE(r.size() == 1); } >>
[TEST_CTX](const DrogonDbException &e) {
FAULT("mysql - DbClient streaming-type interface(9) what():",
e.base().what());
};
/// 1.11 truncate
*clientPtr << "truncate table users" >> [TEST_CTX](const Result &r) { *clientPtr << "truncate table users" >> [TEST_CTX](const Result &r) {
SUCCESS(); SUCCESS();
} >> [TEST_CTX](const DrogonDbException &e) { } >> [TEST_CTX](const DrogonDbException &e) {
FAULT("mysql - DbClient streaming-type interface(9) what():", FAULT("mysql - DbClient streaming-type interface(10) what():",
e.base().what()); e.base().what());
}; };
/// Test asynchronous method /// Test asynchronous method
@ -1759,12 +1823,21 @@ DROGON_TEST(MySQLTest)
"postgresql1", "postgresql1",
"pg", "pg",
"postgresql"); "postgresql");
/// 2.6 truncate /// 2.6 query with raw parameter
clientPtr->execSqlAsync(
"select * from users where length(user_id)=?",
[TEST_CTX](const Result &r) { MANDATE(r.size() == 1); },
[TEST_CTX](const DrogonDbException &e) {
FAULT("mysql - DbClient asynchronous interface(7) what():",
e.base().what());
},
rawParam);
/// 2.7 truncate
clientPtr->execSqlAsync( clientPtr->execSqlAsync(
"truncate table users", "truncate table users",
[TEST_CTX](const Result &r) { SUCCESS(); }, [TEST_CTX](const Result &r) { SUCCESS(); },
[TEST_CTX](const DrogonDbException &e) { [TEST_CTX](const DrogonDbException &e) {
FAULT("mysql - DbClient asynchronous interface(7) what():", FAULT("mysql - DbClient asynchronous interface(9) what():",
e.base().what()); e.base().what());
}); });
@ -1845,7 +1918,19 @@ DROGON_TEST(MySQLTest)
{ {
SUCCESS(); SUCCESS();
} }
/// 3.6 truncate /// 3.6 query with raw parameter
try
{
auto r = clientPtr->execSqlSync(
"select * from users where length(user_id)=?", rawParam);
MANDATE(r.size() == 1);
}
catch (const DrogonDbException &e)
{
FAULT("mysql - DbClient asynchronous interface(4) what():",
e.base().what());
}
/// 3.7 truncate
try try
{ {
auto r = clientPtr->execSqlSync("truncate table users"); auto r = clientPtr->execSqlSync("truncate table users");
@ -1932,7 +2017,19 @@ DROGON_TEST(MySQLTest)
{ {
SUCCESS(); SUCCESS();
} }
/// 4.6 truncate /// 4.6. query with raw parameter
f = clientPtr->execSqlAsyncFuture(
"select * from users where length(user_id)=?", rawParam);
try
{
auto r = f.get();
MANDATE(r.size() == 1);
}
catch (const DrogonDbException &e)
{
FAULT("mysql - DbClient future interface(5) what():", e.base().what());
}
/// 4.7 truncate
f = clientPtr->execSqlAsyncFuture("truncate table users"); f = clientPtr->execSqlAsyncFuture("truncate table users");
try try
{ {
@ -1941,7 +2038,7 @@ DROGON_TEST(MySQLTest)
} }
catch (const DrogonDbException &e) catch (const DrogonDbException &e)
{ {
FAULT("mysql - DbClient future interface(5) what():", e.base().what()); FAULT("mysql - DbClient future interface(6) what():", e.base().what());
} }
/// 5 Test Result and Row exception throwing /// 5 Test Result and Row exception throwing
@ -2881,17 +2978,29 @@ DROGON_TEST(SQLite3Test)
FAULT("sqlite3 - DbClient streaming-type interface(8) what():", FAULT("sqlite3 - DbClient streaming-type interface(8) what():",
e.base().what()); e.base().what());
}; };
/// 1.10 clean up /// 1.10 query with raw parameter
auto rawParamData = std::make_shared<int>(3);
auto rawParam = RawParameter{rawParamData,
reinterpret_cast<char *>(rawParamData.get()),
0,
Sqlite3TypeInt};
*clientPtr << "select * from users where length(user_id) = ?" << rawParam >>
[TEST_CTX](const Result &r) { MANDATE(r.size() == 1); } >>
[TEST_CTX](const DrogonDbException &e) {
FAULT("sqlite3 - DbClient streaming-type interface(9) what():",
e.base().what());
};
/// 1.11 clean up
*clientPtr << "delete from users" >> [TEST_CTX](const Result &r) { *clientPtr << "delete from users" >> [TEST_CTX](const Result &r) {
SUCCESS(); SUCCESS();
} >> [TEST_CTX](const DrogonDbException &e) { } >> [TEST_CTX](const DrogonDbException &e) {
FAULT("sqlite3 - DbClient streaming-type interface(9.1) what():", FAULT("sqlite3 - DbClient streaming-type interface(10.1) what():",
e.base().what()); e.base().what());
}; };
*clientPtr << "UPDATE sqlite_sequence SET seq = 0" >> *clientPtr << "UPDATE sqlite_sequence SET seq = 0" >>
[TEST_CTX](const Result &r) { SUCCESS(); } >> [TEST_CTX](const Result &r) { SUCCESS(); } >>
[TEST_CTX](const DrogonDbException &e) { [TEST_CTX](const DrogonDbException &e) {
FAULT("sqlite3 - DbClient streaming-type interface(9.2) what():", FAULT("sqlite3 - DbClient streaming-type interface(10.2) what():",
e.base().what()); e.base().what());
}; };
/// Test asynchronous method /// Test asynchronous method
@ -2975,19 +3084,28 @@ DROGON_TEST(SQLite3Test)
"postgresql1", "postgresql1",
"pg", "pg",
"postgresql"); "postgresql");
/// 2.6 clean up /// 2.6 query with raw parameter
clientPtr->execSqlAsync(
"select * from users where length(user_id) = ?",
[TEST_CTX](const Result &r) { MANDATE(r.size() == 1); },
[TEST_CTX](const DrogonDbException &e) {
FAULT("sqlite3 - DbClient asynchronous interface(7) what():",
e.base().what());
},
rawParam);
/// 2.7 clean up
clientPtr->execSqlAsync( clientPtr->execSqlAsync(
"delete from users", "delete from users",
[TEST_CTX](const Result &r) { SUCCESS(); }, [TEST_CTX](const Result &r) { SUCCESS(); },
[TEST_CTX](const DrogonDbException &e) { [TEST_CTX](const DrogonDbException &e) {
FAULT("sqlite3 - DbClient asynchronous interface(7.1) what():", FAULT("sqlite3 - DbClient asynchronous interface(8.1) what():",
e.base().what()); e.base().what());
}); });
clientPtr->execSqlAsync( clientPtr->execSqlAsync(
"UPDATE sqlite_sequence SET seq = 0", "UPDATE sqlite_sequence SET seq = 0",
[TEST_CTX](const Result &r) { SUCCESS(); }, [TEST_CTX](const Result &r) { SUCCESS(); },
[TEST_CTX](const DrogonDbException &e) { [TEST_CTX](const DrogonDbException &e) {
FAULT("sqlite3 - DbClient asynchronous interface(7.2) what():", FAULT("sqlite3 - DbClient asynchronous interface(8.2) what():",
e.base().what()); e.base().what());
}); });
@ -3074,7 +3192,19 @@ DROGON_TEST(SQLite3Test)
{ {
SUCCESS(); SUCCESS();
} }
/// 3.6 clean up /// 3.6 query with raw parameter
try
{
auto r = clientPtr->execSqlSync(
"select * from users where length(user_id) = ?", rawParam);
MANDATE(r.size() == 1);
}
catch (const DrogonDbException &e)
{
FAULT("sqlite3 - DbClient asynchronous interface(4) what():",
e.base().what());
}
/// 3.7 clean up
try try
{ {
auto r = clientPtr->execSqlSync("delete from users"); auto r = clientPtr->execSqlSync("delete from users");
@ -3177,6 +3307,19 @@ DROGON_TEST(SQLite3Test)
{ {
SUCCESS(); SUCCESS();
} }
/// 4.6 query with raw parameter
f = clientPtr->execSqlAsyncFuture(
"select * from users where length(user_id)=?", rawParam);
try
{
auto r = f.get();
MANDATE(r.size() == 1);
}
catch (const DrogonDbException &e)
{
FAULT("sqlite3 - DbClient future interface(4) what():",
e.base().what());
}
/// 4.6 clean up /// 4.6 clean up
f = clientPtr->execSqlAsyncFuture("delete from users"); f = clientPtr->execSqlAsyncFuture("delete from users");
try try

@ -1 +1 @@
Subproject commit 8bf280ba043eb77c3eb02121065e0891a9a4f431 Subproject commit 43fd79b2dbac59608a819ebba167e8fe2c079d90