mirror of
https://github.com/drogonframework/drogon.git
synced 2025-06-23 00:00:30 -04:00
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a22956b82b | ||
|
3c5749bbc2 | ||
|
7cd1ae8940 | ||
|
c3f9192541 | ||
|
e46e05e94a | ||
|
26e7c6913c | ||
|
8d640bafb4 | ||
|
f6b5404dbb | ||
|
46b5c9044d | ||
|
ac0d4d0f89 | ||
|
5c4057331e | ||
|
95a518e7f2 | ||
|
c03a3df106 | ||
|
d6a33f93c9 | ||
|
59cd4366c7 | ||
|
c92d146374 | ||
|
3c7c66e310 | ||
|
1fb67d68be | ||
|
cbf63f8fc4 | ||
|
d68e8aa554 | ||
|
41537a6e86 | ||
|
a32dc67867 | ||
|
e155df9f66 | ||
|
f5de41f5d7 | ||
|
a3b4779540 | ||
|
686f68a12f | ||
|
152a69f1e9 |
10
.github/workflows/cmake.yml
vendored
10
.github/workflows/cmake.yml
vendored
@ -17,7 +17,7 @@ env:
|
||||
jobs:
|
||||
windows:
|
||||
name: windows/msvc - ${{ matrix.link }}
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -44,9 +44,6 @@ jobs:
|
||||
- name: Create Build Environment & Configure Cmake
|
||||
shell: bash
|
||||
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: |
|
||||
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
||||
cmake .. \
|
||||
@ -54,8 +51,8 @@ jobs:
|
||||
-DBUILD_TESTING=on \
|
||||
-DBUILD_SHARED_LIBS=$shared \
|
||||
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \
|
||||
-DBUILD_CTL=OFF \
|
||||
-DBUILD_EXAMPLES=OFF \
|
||||
-DBUILD_CTL=ON \
|
||||
-DBUILD_EXAMPLES=ON \
|
||||
-DUSE_SPDLOG=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=../install \
|
||||
-DCMAKE_POLICY_DEFAULT_CMP0091=NEW \
|
||||
@ -102,7 +99,6 @@ jobs:
|
||||
|
||||
- name: Prepare for testing
|
||||
run: |
|
||||
brew tap homebrew/services
|
||||
brew services restart postgresql@14
|
||||
brew services start mariadb
|
||||
brew services start redis
|
||||
|
4
.github/workflows/codespell.yml
vendored
4
.github/workflows/codespell.yml
vendored
@ -11,5 +11,5 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: pip install --user codespell[toml]
|
||||
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows,NotIn," --skip="*.csp"
|
||||
- run: sudo apt-get install -y codespell
|
||||
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows,NotIn,aNULL," --skip="*.csp"
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,7 +35,6 @@ build/
|
||||
cmake-build-debug/
|
||||
cmake-build-debug-visual-studio/
|
||||
.idea/
|
||||
lib/inc/drogon/version.h
|
||||
html/
|
||||
latex/
|
||||
.vscode
|
||||
|
@ -25,7 +25,7 @@ CMAKE_DEPENDENT_OPTION(USE_SPDLOG "Allow using the spdlog logging library" OFF "
|
||||
|
||||
set(DROGON_MAJOR_VERSION 1)
|
||||
set(DROGON_MINOR_VERSION 9)
|
||||
set(DROGON_PATCH_VERSION 9)
|
||||
set(DROGON_PATCH_VERSION 11)
|
||||
set(DROGON_VERSION
|
||||
${DROGON_MAJOR_VERSION}.${DROGON_MINOR_VERSION}.${DROGON_PATCH_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")
|
||||
# Force MSVC to use UTF-8 because that's what we use. Otherwise it uses
|
||||
# the default of whatever Windows sets and causes encoding issues.
|
||||
message(STATUS "You are using MSVC. 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("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||
if (MSVC_VERSION GREATER_EQUAL 1914)
|
||||
@ -121,6 +121,7 @@ endif()
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/lib/inc>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/orm_lib/inc>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc>
|
||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
|
||||
@ -342,21 +343,21 @@ set(private_headers
|
||||
lib/src/ConfigAdapter.h
|
||||
lib/src/MultipartStreamParser.h)
|
||||
|
||||
if (NOT WIN32)
|
||||
if (NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
set(DROGON_SOURCES
|
||||
${DROGON_SOURCES}
|
||||
lib/src/SharedLibManager.cc)
|
||||
set(private_headers
|
||||
${private_headers}
|
||||
lib/src/SharedLibManager.h)
|
||||
else (NOT WIN32)
|
||||
elseif(WIN32)
|
||||
set(DROGON_SOURCES
|
||||
${DROGON_SOURCES}
|
||||
third_party/mman-win32/mman.c)
|
||||
set(private_headers
|
||||
${private_headers}
|
||||
third_party/mman-win32/mman.h)
|
||||
endif (NOT WIN32)
|
||||
endif()
|
||||
|
||||
if (BUILD_POSTGRESQL)
|
||||
# find postgres
|
||||
@ -510,7 +511,7 @@ execute_process(COMMAND "git" rev-parse HEAD
|
||||
OUTPUT_VARIABLE GIT_SHA1
|
||||
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in"
|
||||
"${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)
|
||||
option(USE_COROUTINE "Enable C++20 coroutine support" ON)
|
||||
@ -583,7 +584,7 @@ set(DROGON_HEADERS
|
||||
lib/inc/drogon/WebSocketConnection.h
|
||||
lib/inc/drogon/WebSocketController.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/PubSubService.h
|
||||
lib/inc/drogon/drogon_test.h
|
||||
|
69
ChangeLog.md
69
ChangeLog.md
@ -4,6 +4,65 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.9.11] - 2025-06-20
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add a new overload for execSqlCoro.
|
||||
|
||||
### Changed
|
||||
|
||||
- Do not write to source directory during build.
|
||||
|
||||
- Improve Postgres connection stability.
|
||||
|
||||
- Add handleFatalError in handleClosed.
|
||||
|
||||
- Add -o|--output option to drogon_ctl create models.
|
||||
|
||||
- Add qrcode for WeChat official account to the README file.
|
||||
|
||||
- Support for iOS compiling.
|
||||
|
||||
- Add cors example to demonstrate cross-origin support in drogon.
|
||||
|
||||
- Add support for continuation frame in WebSocketMessageParser.
|
||||
|
||||
- Add RawParameter API to pass raw SQL parameters.
|
||||
|
||||
- Upgrade Windows image and re-enable tests on Windows.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug in isAutoCreationClass<T>.
|
||||
|
||||
- Fix CI on MacOS.
|
||||
|
||||
- Fix issue with precision loss of double-type parameters in ORM inputs.
|
||||
|
||||
|
||||
## [1.9.10] - 2025-02-20
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add setConnectionCallback.
|
||||
|
||||
### Changed
|
||||
|
||||
- ORM:Avoid unnecessary copies when returning search results.
|
||||
|
||||
- Improve the zh-TW README translation.
|
||||
|
||||
- Make quit function thread safe.
|
||||
|
||||
- Added path_exempt in AccessLogger plugin config to exclude desired paths.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix the issue in view generation by including the missing header file.
|
||||
|
||||
- Fix ci: codespell.
|
||||
|
||||
## [1.9.9] - 2025-01-01
|
||||
|
||||
### API changes list
|
||||
@ -461,7 +520,7 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
- Remove unused CI files and Jekyll config.
|
||||
|
||||
- Ensure that all filters, AOP 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.
|
||||
|
||||
@ -793,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.
|
||||
|
||||
- Start listening after beginning advices.
|
||||
- Start listening after beginning advice.
|
||||
|
||||
- Allow using json_cpp in other sublibraries.
|
||||
|
||||
@ -1783,7 +1842,11 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.0.0-beta1] - 2019-06-11
|
||||
|
||||
[Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.9...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,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://github.com/drogonframework/drogon/actions)
|
||||
[](https://github.com/drogonframework/drogon/actions)
|
||||
[](https://conan.io/center/recipes/drogon)
|
||||
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
||||
[](https://discord.gg/3DvHY6Ewuj)
|
||||
|
@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://github.com/drogonframework/drogon/actions)
|
||||
[](https://github.com/drogonframework/drogon/actions)
|
||||
[](https://conan.io/center/recipes/drogon)
|
||||
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
||||
[](https://discord.gg/3DvHY6Ewuj)
|
||||
@ -197,3 +197,9 @@ class User : public drogon::HttpController<User>
|
||||
## QQ交流群:1137909452
|
||||
|
||||
欢迎交流探讨。
|
||||
|
||||
## 微信公众号:
|
||||
|
||||

|
||||
|
||||
会不定期推送一些Drogon的使用技巧和更新信息,欢迎关注。
|
@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
[](https://github.com/drogonframework/drogon/actions)
|
||||
[](https://github.com/drogonframework/drogon/actions)
|
||||
[](https://conan.io/center/recipes/drogon)
|
||||
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
||||
[](https://discord.gg/3DvHY6Ewuj)
|
||||
@ -8,41 +8,42 @@
|
||||
|
||||
[English](./README.md) | [简体中文](./README.zh-CN.md) | 繁體中文
|
||||
|
||||
**Drogon**是一個基於C++17/20的Http應用框架,使用Drogon可以方便的使用C++構建各種類型的Web App伺服器程式。
|
||||
本版本庫是github上[Drogon](https://github.com/an-tao/drogon)的鏡像庫。 **Drogon**是作者非常喜歡的美劇《冰與火之歌:權力遊戲》中的一條龍的名字(漢譯作卓耿),和龍有關但並不是dragon的誤寫,為了不至於引起不必要的誤會這裡說明一下。
|
||||
**Drogon** 是一個基於 C++17/20 的 HTTP 應用程式框架,使用 Drogon 可以方便地用 C++ 建立各種類型的 Web App 伺服器端程式。
|
||||
|
||||
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);
|
||||
* 全異步程式設計;
|
||||
* 支援Http1.0/1.1(server端和client端);
|
||||
* 基於模板(template)實現了簡單的反射機制,使主程式框架、控制器(controller)和視圖(view)完全去耦;
|
||||
* 支援cookies和內建的session;
|
||||
* 支援後端渲染,把控制器生成的數據交給視圖生成Html頁面,視圖由CSP模板文件描述,通過CSP標籤把C++程式碼嵌入到Html頁面,由drogon的指令列工具在編譯階段自動生成C++程式碼並編譯;
|
||||
* 支援運行期的視圖頁面動態加載(動態編譯和載入so文件);
|
||||
* 非常方便靈活的路徑(path)到控制器處理函數(handler)的映射方案;
|
||||
* 支援過濾器(filter)鏈,方便在控制器之前執行統一的邏輯(如登錄驗證、Http Method約束驗證等);
|
||||
* 支援https(基於OpenSSL);
|
||||
* 支援websocket(server端和client端);
|
||||
* 支援Json格式的請求和回應, 方便開發Restful API;
|
||||
* 支援文件下載和上傳,支援sendfile系統呼叫;
|
||||
* 支援gzip/brotli壓縮傳輸;
|
||||
* 支援pipelining;
|
||||
* 提供一個輕量的指令列工具drogon_ctl,幫助簡化各種類的創造和視圖程式碼的生成過程;
|
||||
* 非同步的讀寫資料庫,目前支援PostgreSQL和MySQL(MariaDB)資料庫;
|
||||
* 支援異步讀寫Redis;
|
||||
* 基於執行序池實現sqlite3資料庫的異步讀寫,提供與上文資料庫相同的接口;
|
||||
* 支援ARM架構;
|
||||
* 方便的輕量級ORM實現,一般物件到資料庫的雙向映射;
|
||||
* 支援外掛,可通過設定文件在載入時動態載入;
|
||||
* 支援內建插入點的AOP
|
||||
* 支援C++ coroutine
|
||||
Drogon 是一個跨平台框架,支援 Linux、macOS、FreeBSD/OpenBSD、HaikuOS 和 Windows。主要特點如下:
|
||||
|
||||
* 網路層使用基於 epoll(macOS/FreeBSD 下是 kqueue)的非阻塞 IO 框架,提供高並行、高效能的網路 IO。詳細請見 [TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite);
|
||||
* 完全非同步的程式撰寫邏輯;
|
||||
* 支援 HTTP 1.0/1.1(伺服器端和用戶端);
|
||||
* 基於樣板(template)實作的簡單反射機制,使主程式框架、控制器(controller)和視圖(view)完全解耦;
|
||||
* 支援 cookies 和內建的 session;
|
||||
* 支援後端算繪,將控制器產生的資料交給視圖產生 HTML 頁面,視圖由 CSP 樣板檔案描述,透過 CSP 標籤將 C++ 程式碼嵌入 HTML 頁面,由 drogon 的命令列工具在編譯階段自動產生 C++ 程式碼並編譯;
|
||||
* 支援執行期的視圖頁面動態載入(動態編譯和載入 so 檔案);
|
||||
* 非常方便靈活的路徑(path)到控制器處理函式(handler)的對應方案;
|
||||
* 支援過濾器(filter)鏈,方便在控制器之前執行統一的邏輯(如登入驗證、HTTP Method 限制驗證等);
|
||||
* 支援 HTTPS(基於 OpenSSL);
|
||||
* 支援 WebSocket(伺服器端和用戶端);
|
||||
* 支援 JSON 格式的請求和回應,方便開發 RESTful API;
|
||||
* 支援檔案下載和上傳,支援 `sendfile` 系統呼叫;
|
||||
* 支援 Gzip/Brotli 壓縮傳輸;
|
||||
* 支援 pipelining;
|
||||
* 提供輕量的命令列工具 `drogon_ctl`,幫助簡化各種類別的建立和視圖程式碼的產生過程;
|
||||
* 非同步的讀寫資料庫,目前支援 PostgreSQL 和 MySQL(MariaDB)資料庫;
|
||||
* 支援非同步讀寫 Redis;
|
||||
* 基於執行緒池實作 sqlite3 資料庫的非同步讀寫,提供與上述資料庫相同的介面;
|
||||
* 支援 ARM 架構;
|
||||
* 方便的輕量級 ORM 實現,一般物件到資料庫的雙向對應;
|
||||
* 支援外掛,可透過設定檔案在載入時動態載入;
|
||||
* 支援內建插入點的 AOP;
|
||||
* 支援 C++ coroutine。
|
||||
|
||||
## 一個非常簡單的例子
|
||||
|
||||
不像大多數C++框架那樣,drogon的主程式可以非常簡單。 Drogon使用了一些小技巧使主程式和控制器去耦. 控制器的路由設定可以在控制器類別中定義或者設定文件中完成.
|
||||
不像大多數 C++ 框架,drogon 的主程式可以非常簡單。Drogon 使用了一些小技巧使主程式和控制器解耦。控制器的路由設定可以在控制器類別中定義或在設定檔案中完成。
|
||||
|
||||
下面是一個典型的主程式的樣子:
|
||||
下面是一個典型主程式的樣子:
|
||||
|
||||
```c++
|
||||
#include <drogon/drogon.h>
|
||||
@ -58,7 +59,7 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||
如果使用設定文件,可以進一步簡化成這樣:
|
||||
如果使用設定檔案,可以進一步簡化成:
|
||||
|
||||
```c++
|
||||
#include <drogon/drogon.h>
|
||||
@ -69,7 +70,7 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||
當然,Drogon也提供了一些函數,使使用者可以在main()函數中直接添加控制器邏輯,比如,使用者可以註冊一個lambda處理器到drogon框架中,如下所示:
|
||||
當然,Drogon 也提供了一些函式,讓使用者可以在 `main()` 函式中直接加入控制器邏輯,例如,使用者可以註冊一個 lambda 處理常式到 drogon 框架中,如下所示:
|
||||
|
||||
```c++
|
||||
app().registerHandler("/test?username={name}",
|
||||
@ -86,9 +87,7 @@ app().registerHandler("/test?username={name}",
|
||||
{Get,"LoginFilter"});
|
||||
```
|
||||
|
||||
|
||||
這看起來是很方便,但是這並不適用於復雜的場景,試想假如有數十個或者數百個處理函數要註冊進框架,main()函數將膨脹到不可讀的程度。顯然,讓每個包含處理函數的類在自己的定義中完成註冊是更好的選擇。所以,除非你的應用邏輯非常簡單,我們不推薦使用上述接口,更好的實踐是,我們可以創造一個HttpSimpleController類別,如下:
|
||||
|
||||
這看起來很方便,但不適用於複雜的場景,試想如果有數十個或數百個處理函式要註冊進框架,`main()` 函式將變得難以閱讀。顯然,讓每個包含處理函式的類別在自己的定義中完成註冊是更好的選擇。所以,除非你的應用邏輯非常簡單,我們不建議使用上述介面,更好的做法是建立一個 HttpSimpleController 類別,如下:
|
||||
|
||||
```c++
|
||||
/// 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++
|
||||
/// The header file
|
||||
@ -148,7 +147,7 @@ void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
|
||||
}
|
||||
```
|
||||
|
||||
讓我們更進一步,通過HttpController類別創造一個RESTful API的例子,如下所示(忽略了實做文件):
|
||||
讓我們更進一步,透過 HttpController 類別建立一個 RESTful API 的範例,如下所示(省略實作檔案):
|
||||
|
||||
```c++
|
||||
/// The header file
|
||||
@ -182,18 +181,18 @@ class User : public drogon::HttpController<User>
|
||||
} // namespace api
|
||||
```
|
||||
|
||||
如你所見,通過`HttpController`類別,使用者可以同時映射路徑和路徑參數,這對RESTful API應用來說非常方便。
|
||||
如你所見,透過 `HttpController` 類別,使用者可以同時對應路徑和路徑參數,這對 RESTful API 應用來說非常方便。
|
||||
|
||||
另外,你可以發現前面所有的處理函數接口都是異步的,處理器的回應是通過回調對象回傳的。這種設計是出於對高性能的考慮,因為在異步模式下,可以使用少量的執行序(比如和處理器核心數相等的執行序)處理大量的並發請求。
|
||||
另外,你可以發現前面所有的處理函式介面都是非同步的,處理器的回應是透過回呼物件回傳的。這種設計是考慮到效能,因為在非同步模式下,可以使用少量的執行緒(例如和處理器核心數相等的執行緒)處理大量的並行請求。
|
||||
|
||||
編譯上述的所有源文件後,我們得到了一個非常簡單的web應用程式,這是一個不錯的開始。 **請瀏覽GitHub上的[文檔](https://drogonframework.github.io/drogon-docs/#/CHN/CHN-01-%E6%A6%82%E8%BF%B0)**
|
||||
編譯上述所有原始檔案後,我們得到了一個非常簡單的網頁應用程式,這是一個不錯的開始。**請瀏覽 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>
|
||||
|
||||
## QQ交流群:1137909452
|
||||
## QQ 交流群:1137909452
|
||||
|
||||
歡迎交流探討。
|
||||
歡迎交流討論。
|
@ -42,7 +42,8 @@ std::string create::detail()
|
||||
"create a plugin named class_name\n\n"
|
||||
"drogon_ctl create project <project_name> //"
|
||||
"create a project named project_name\n\n"
|
||||
"drogon_ctl create model <model_path> [--table=<table_name>] [-f]//"
|
||||
"drogon_ctl create model <model_path> [-o <output path>] "
|
||||
"[--table=<table_name>] [-f]//"
|
||||
"create model classes in model_path\n";
|
||||
}
|
||||
|
||||
|
@ -826,6 +826,7 @@ void create_model::createModel(const std::string &path,
|
||||
auto restfulApiConfig = config["restful_api_controllers"];
|
||||
auto relationships = getRelationships(config["relationships"]);
|
||||
auto convertMethods = getConvertMethods(config["convert"]);
|
||||
drogon::utils::createPath(path);
|
||||
if (dbType == "postgresql")
|
||||
{
|
||||
#if USE_POSTGRESQL
|
||||
@ -1173,7 +1174,9 @@ void create_model::createModel(const std::string &path,
|
||||
try
|
||||
{
|
||||
infile >> configJsonRoot;
|
||||
createModel(path, configJsonRoot, singleModelName);
|
||||
createModel(outputPath_.empty() ? path : outputPath_,
|
||||
configJsonRoot,
|
||||
singleModelName);
|
||||
}
|
||||
catch (const std::exception &exception)
|
||||
{
|
||||
@ -1211,6 +1214,22 @@ void create_model::handleCommand(std::vector<std::string> ¶meters)
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto iter = parameters.begin(); iter != parameters.end();)
|
||||
{
|
||||
auto &file = *iter;
|
||||
if (file == "-o" || file == "--output")
|
||||
{
|
||||
iter = parameters.erase(iter);
|
||||
if (iter != parameters.end())
|
||||
{
|
||||
outputPath_ = *iter;
|
||||
iter = parameters.erase(iter);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
|
||||
for (auto const &path : parameters)
|
||||
{
|
||||
createModel(path, singleModelName);
|
||||
|
@ -429,5 +429,6 @@ class create_model : public DrObject<create_model>, public CommandHandler
|
||||
const Json::Value &restfulApiConfig);
|
||||
std::string dbname_;
|
||||
bool forceOverwrite_{false};
|
||||
std::string outputPath_;
|
||||
};
|
||||
} // namespace drogon_ctl
|
||||
|
@ -411,6 +411,7 @@ void create_view::newViewSourceFile(std::ofstream &file,
|
||||
"automatically,don't modify it!\n";
|
||||
file << "#include \"" << namespacePrefix << className << ".h\"\n";
|
||||
file << "#include <drogon/utils/OStringStream.h>\n";
|
||||
file << "#include <drogon/utils/Utilities.h>\n";
|
||||
file << "#include <string>\n";
|
||||
file << "#include <map>\n";
|
||||
file << "#include <vector>\n";
|
||||
|
@ -34,6 +34,8 @@ add_executable(redis_chat redis_chat/main.cc
|
||||
add_executable(async_stream async_stream/main.cc
|
||||
async_stream/RequestStreamExampleCtrl.cc)
|
||||
|
||||
add_executable(cors cors/main.cc)
|
||||
|
||||
set(example_targets
|
||||
benchmark
|
||||
client
|
||||
@ -45,7 +47,8 @@ set(example_targets
|
||||
jsonstore
|
||||
redis_simple
|
||||
redis_chat
|
||||
async_stream)
|
||||
async_stream
|
||||
cors)
|
||||
|
||||
# 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.
|
||||
|
@ -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
|
||||
12. [redis_chat](https://github.com/drogonframework/drogon/tree/master/examples/redis_chat) - A chatroom server built with websocket and Redis pub/sub service
|
||||
13. [prometheus_example](https://github.com/drogonframework/drogon/tree/master/examples/prometheus_example) - An example of how to use the Prometheus exporter in Drogon
|
||||
14. [cors](https://github.com/drogonframework/drogon/tree/master/examples/cors) - An example demonstrating how to implement CORS (Cross-Origin Resource Sharing) support in Drogon
|
||||
|
||||
### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite
|
||||
|
||||
|
@ -1,15 +1,33 @@
|
||||
#include <drogon/drogon.h>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
std::mutex mutex;
|
||||
std::unordered_map<trantor::TcpConnectionPtr, std::function<void()>>
|
||||
connMapping;
|
||||
|
||||
int main()
|
||||
{
|
||||
app().registerHandler(
|
||||
"/stream",
|
||||
[](const HttpRequestPtr &,
|
||||
[](const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
||||
const auto &weakConnPtr = req->getConnectionPtr();
|
||||
if (auto connPtr = weakConnPtr.lock())
|
||||
{
|
||||
std::lock_guard lk(mutex);
|
||||
connMapping.emplace(std::move(connPtr), [] {
|
||||
LOG_INFO << "call stop or other options!!!!";
|
||||
});
|
||||
}
|
||||
auto resp = drogon::HttpResponse::newAsyncStreamResponse(
|
||||
[](drogon::ResponseStreamPtr stream) {
|
||||
std::thread([stream =
|
||||
@ -79,5 +97,17 @@ int main()
|
||||
|
||||
LOG_INFO << "Server running on 127.0.0.1:8848";
|
||||
app().enableRequestStream(); // This is for request stream.
|
||||
app().setConnectionCallback([](const trantor::TcpConnectionPtr &conn) {
|
||||
if (conn->disconnected())
|
||||
{
|
||||
std::lock_guard lk(mutex);
|
||||
if (auto it = connMapping.find(conn); it != connMapping.end())
|
||||
{
|
||||
LOG_INFO << "disconnect";
|
||||
connMapping[conn]();
|
||||
connMapping.erase(conn);
|
||||
}
|
||||
}
|
||||
});
|
||||
app().addListener("127.0.0.1", 8848).run();
|
||||
}
|
||||
|
153
examples/cors/main.cc
Normal file
153
examples/cors/main.cc
Normal 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;
|
||||
}
|
@ -61,9 +61,9 @@ template <typename T>
|
||||
struct isAutoCreationClass
|
||||
{
|
||||
template <class C>
|
||||
static constexpr auto check(C *)
|
||||
-> std::enable_if_t<std::is_same_v<decltype(C::isAutoCreation), bool>,
|
||||
bool>
|
||||
static constexpr auto check(C *) -> std::enable_if_t<
|
||||
std::is_same_v<decltype(C::isAutoCreation), const bool>,
|
||||
bool>
|
||||
{
|
||||
return C::isAutoCreation;
|
||||
}
|
||||
|
@ -43,6 +43,16 @@
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__) && \
|
||||
(defined(__ENVIRONMENT_IPHONE_OS__) || \
|
||||
defined(__IPHONE_OS_VERSION_MIN_REQUIRED))
|
||||
// iOS
|
||||
#define TARGET_OS_IOS 1
|
||||
#else
|
||||
// not iOS
|
||||
#define TARGET_OS_IOS 0
|
||||
#endif
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
// the drogon banner
|
||||
@ -349,7 +359,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
||||
|
||||
/// 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
|
||||
* of the advice are same as those of the doFilter method of the Filter
|
||||
* class.
|
||||
@ -997,7 +1007,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
||||
virtual HttpAppFramework &setFileTypes(
|
||||
const std::vector<std::string> &types) = 0;
|
||||
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
/// Enable supporting for dynamic views loading.
|
||||
/**
|
||||
*
|
||||
@ -1615,6 +1625,15 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
||||
virtual HttpAppFramework &setAfterAcceptSockOptCallback(
|
||||
std::function<void(int)> cb) = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the client disconnect or connect callback.
|
||||
*
|
||||
* @param cb This callback will be called, when the client disconnect or
|
||||
* connect
|
||||
*/
|
||||
virtual HttpAppFramework &setConnectionCallback(
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> cb) = 0;
|
||||
|
||||
virtual HttpAppFramework &enableRequestStream(bool enable = true) = 0;
|
||||
virtual bool isRequestStreamEnabled() const = 0;
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
@ -506,6 +507,9 @@ class DROGON_EXPORT HttpRequest
|
||||
|
||||
virtual bool connected() const noexcept = 0;
|
||||
|
||||
virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
|
||||
const noexcept = 0;
|
||||
|
||||
virtual ~HttpRequest()
|
||||
{
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ namespace plugin
|
||||
// "show_microseconds": true,
|
||||
// "custom_time_format": "",
|
||||
// "use_real_ip": false
|
||||
// "path_exempt": ""
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
@ -98,6 +99,10 @@ namespace plugin
|
||||
* Enable the plugin by adding the configuration to the list of plugins in the
|
||||
* configuration file.
|
||||
*
|
||||
* path_exempt: must be a string or a string array, present a regular expression
|
||||
* (for matching the path of a request) or a regular expression list for URLs
|
||||
* that don't have to be logged.
|
||||
*
|
||||
*/
|
||||
class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
|
||||
{
|
||||
@ -117,6 +122,8 @@ class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
|
||||
bool useCustomTimeFormat_{false};
|
||||
std::string timeFormat_;
|
||||
static bool useRealIp_;
|
||||
std::regex exemptRegex_;
|
||||
bool regexFlag_{false};
|
||||
|
||||
using LogFunction = std::function<void(trantor::LogStream &,
|
||||
const drogon::HttpRequestPtr &,
|
||||
|
@ -20,10 +20,10 @@
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
static void doAdvicesChain(
|
||||
static void doAdviceChain(
|
||||
const std::vector<std::function<void(const HttpRequestPtr &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)>> &advices,
|
||||
AdviceChainCallback &&)>> &adviceChain,
|
||||
size_t index,
|
||||
const HttpRequestImplPtr &req,
|
||||
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
||||
@ -88,7 +88,7 @@ void AopAdvice::passPreRoutingAdvices(
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||
doAdvicesChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||
doAdviceChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||
}
|
||||
|
||||
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
|
||||
@ -114,7 +114,7 @@ void AopAdvice::passPostRoutingAdvices(
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||
doAdvicesChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||
doAdviceChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||
}
|
||||
|
||||
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
|
||||
@ -140,7 +140,7 @@ void AopAdvice::passPreHandlingAdvices(
|
||||
|
||||
auto callbackPtr =
|
||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||
doAdvicesChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
|
||||
doAdviceChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
|
||||
}
|
||||
|
||||
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 &,
|
||||
AdviceCallback &&,
|
||||
AdviceChainCallback &&)>> &advices,
|
||||
AdviceChainCallback &&)>> &adviceChain,
|
||||
size_t index,
|
||||
const HttpRequestImplPtr &req,
|
||||
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
||||
&&callbackPtr)
|
||||
{
|
||||
if (index < advices.size())
|
||||
if (index < adviceChain.size())
|
||||
{
|
||||
auto &advice = advices[index];
|
||||
auto &advice = adviceChain[index];
|
||||
advice(
|
||||
req,
|
||||
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
|
||||
(*callbackPtr)(resp);
|
||||
},
|
||||
[index, req, callbackPtr, &advices]() mutable {
|
||||
[index, req, callbackPtr, &adviceChain]() mutable {
|
||||
auto ioLoop = req->getLoop();
|
||||
if (ioLoop && !ioLoop->isInLoopThread())
|
||||
{
|
||||
ioLoop->queueInLoop([index,
|
||||
req,
|
||||
callbackPtr = std::move(callbackPtr),
|
||||
&advices]() mutable {
|
||||
doAdvicesChain(advices,
|
||||
index + 1,
|
||||
req,
|
||||
std::move(callbackPtr));
|
||||
&adviceChain]() mutable {
|
||||
doAdviceChain(adviceChain,
|
||||
index + 1,
|
||||
req,
|
||||
std::move(callbackPtr));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
doAdvicesChain(advices,
|
||||
index + 1,
|
||||
req,
|
||||
std::move(callbackPtr));
|
||||
doAdviceChain(adviceChain,
|
||||
index + 1,
|
||||
req,
|
||||
std::move(callbackPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -113,6 +113,46 @@ void AccessLogger::initAndStart(const Json::Value &config)
|
||||
}
|
||||
createLogFunctions(format);
|
||||
auto logPath = config.get("log_path", "").asString();
|
||||
|
||||
if (config.isMember("path_exempt"))
|
||||
{
|
||||
if (config["path_exempt"].isArray())
|
||||
{
|
||||
const auto &exempts = config["path_exempt"];
|
||||
size_t exemptsCount = exempts.size();
|
||||
if (exemptsCount)
|
||||
{
|
||||
std::string regexString;
|
||||
size_t len = 0;
|
||||
for (const auto &exempt : exempts)
|
||||
{
|
||||
assert(exempt.isString());
|
||||
len += exempt.size();
|
||||
}
|
||||
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
|
||||
|
||||
const auto last = --exempts.end();
|
||||
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
|
||||
regexString.append("(")
|
||||
.append(exempt->asString())
|
||||
.append(")|");
|
||||
regexString.append("(").append(last->asString()).append(")");
|
||||
|
||||
exemptRegex_ = std::regex(regexString);
|
||||
regexFlag_ = true;
|
||||
}
|
||||
}
|
||||
else if (config["path_exempt"].isString())
|
||||
{
|
||||
exemptRegex_ = std::regex(config["path_exempt"].asString());
|
||||
regexFlag_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "path_exempt must be a string or string array!";
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DROGON_SPDLOG_SUPPORT
|
||||
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
|
||||
config.get("use_spdlog", false).asBool();
|
||||
@ -228,7 +268,17 @@ void AccessLogger::initAndStart(const Json::Value &config)
|
||||
drogon::app().registerPreSendingAdvice(
|
||||
[this](const drogon::HttpRequestPtr &req,
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -383,7 +383,7 @@ static void loadApp(const Json::Value &app)
|
||||
{
|
||||
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
|
||||
}
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
// dynamic views
|
||||
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
|
||||
if (enableDynamicViews)
|
||||
|
@ -184,7 +184,7 @@ static void TERMFunction(int sig)
|
||||
HttpAppFrameworkImpl::~HttpAppFrameworkImpl() noexcept
|
||||
{
|
||||
// Destroy the following objects before the loop destruction
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
sharedLibManagerPtr_.reset();
|
||||
#endif
|
||||
sessionManagerPtr_.reset();
|
||||
@ -236,7 +236,7 @@ const std::string &HttpAppFrameworkImpl::getImplicitPage() const
|
||||
{
|
||||
return StaticFileRouter::instance().getImplicitPage();
|
||||
}
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
HttpAppFramework &HttpAppFrameworkImpl::enableDynamicViewsLoading(
|
||||
const std::vector<std::string> &libPaths,
|
||||
const std::string &outputPath)
|
||||
@ -599,7 +599,7 @@ void HttpAppFrameworkImpl::run()
|
||||
LOG_INFO << "Start child process";
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
if (!libFilePaths_.empty())
|
||||
{
|
||||
sharedLibManagerPtr_ =
|
||||
@ -1033,7 +1033,7 @@ HttpAppFramework &HttpAppFrameworkImpl::createRedisClient(
|
||||
|
||||
void HttpAppFrameworkImpl::quit()
|
||||
{
|
||||
if (getLoop()->isRunning())
|
||||
if (getLoop()->isRunning() && running_.exchange(false))
|
||||
{
|
||||
getLoop()->queueInLoop([this]() {
|
||||
// Release members in the reverse order of initialization
|
||||
@ -1044,7 +1044,6 @@ void HttpAppFrameworkImpl::quit()
|
||||
pluginsManagerPtr_.reset();
|
||||
redisClientManagerPtr_.reset();
|
||||
dbClientManagerPtr_.reset();
|
||||
running_ = false;
|
||||
getLoop()->quit();
|
||||
for (trantor::EventLoop *loop : ioLoopThreadPool_->getLoops())
|
||||
{
|
||||
@ -1367,3 +1366,10 @@ HttpAppFramework &HttpAppFrameworkImpl::setAfterAcceptSockOptCallback(
|
||||
listenerManagerPtr_->setAfterAcceptSockOptCallback(std::move(cb));
|
||||
return *this;
|
||||
}
|
||||
|
||||
HttpAppFramework &HttpAppFrameworkImpl::setConnectionCallback(
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> cb)
|
||||
{
|
||||
listenerManagerPtr_->setConnectionCallback(std::move(cb));
|
||||
return *this;
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
||||
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
|
||||
HttpAppFramework &setFileTypes(
|
||||
const std::vector<std::string> &types) override;
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
HttpAppFramework &enableDynamicViewsLoading(
|
||||
const std::vector<std::string> &libPaths,
|
||||
const std::string &outputPath) override;
|
||||
@ -665,6 +665,8 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
||||
std::function<void(int)> cb) override;
|
||||
HttpAppFramework &setAfterAcceptSockOptCallback(
|
||||
std::function<void(int)> cb) override;
|
||||
HttpAppFramework &setConnectionCallback(
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> cb) override;
|
||||
|
||||
HttpAppFramework &enableRequestStream(bool enable) override;
|
||||
bool isRequestStreamEnabled() const override;
|
||||
@ -707,7 +709,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
||||
size_t threadNum_{1};
|
||||
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
|
||||
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
||||
std::vector<std::string> libFilePaths_;
|
||||
std::string libFileOutputPath_;
|
||||
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include "HttpUtils.h"
|
||||
#include "CacheFile.h"
|
||||
#include "impl_forwards.h"
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
#include <drogon/RequestStream.h>
|
||||
@ -572,6 +573,12 @@ class HttpRequestImpl : public HttpRequest
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
|
||||
const noexcept override
|
||||
{
|
||||
return connPtr_;
|
||||
}
|
||||
|
||||
bool isOnSecureConnection() const noexcept override
|
||||
{
|
||||
return isOnSecureConnection_;
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "HttpControllersRouter.h"
|
||||
#include "StaticFileRouter.h"
|
||||
#include "WebSocketConnectionImpl.h"
|
||||
#include "impl_forwards.h"
|
||||
|
||||
#if COZ_PROFILING
|
||||
#include <coz.h>
|
||||
@ -75,7 +76,12 @@ HttpServer::HttpServer(EventLoop *loop,
|
||||
: server_(loop, listenAddr, std::move(name), true, app().reusePort())
|
||||
#endif
|
||||
{
|
||||
server_.setConnectionCallback(onConnection);
|
||||
server_.setConnectionCallback(
|
||||
[this](const trantor::TcpConnectionPtr &conn) {
|
||||
onConnection(conn);
|
||||
if (connectionCallback_)
|
||||
connectionCallback_(conn);
|
||||
});
|
||||
server_.setRecvMessageCallback(onMessage);
|
||||
server_.kickoffIdleConnections(
|
||||
HttpAppFrameworkImpl::instance().getIdleConnectionTimeout());
|
||||
@ -307,7 +313,7 @@ void HttpServer::onRequests(
|
||||
return;
|
||||
}
|
||||
|
||||
// flush response for not passing sync advices
|
||||
// flush response for not passing sync advice
|
||||
if (conn->connected() && !requestParser->getResponseBuffer().empty())
|
||||
{
|
||||
sendResponses(conn,
|
||||
@ -1196,7 +1202,7 @@ static inline HttpResponsePtr tryDecompressRequest(
|
||||
* @brief Check request against each sync advice, generate response if request
|
||||
* 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.
|
||||
*/
|
||||
static inline bool passSyncAdvices(
|
||||
|
@ -69,6 +69,12 @@ class HttpServer : trantor::NonCopyable
|
||||
afterAcceptSetSockOptCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
void setConnectionCallback(
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> cb)
|
||||
{
|
||||
connectionCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class HttpInternalForwardHelper;
|
||||
|
||||
@ -144,6 +150,7 @@ class HttpServer : trantor::NonCopyable
|
||||
|
||||
std::function<void(int)> beforeListenSetSockOptCallback_;
|
||||
std::function<void(int)> afterAcceptSetSockOptCallback_;
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
|
||||
};
|
||||
|
||||
class HttpInternalForwardHelper
|
||||
|
@ -121,6 +121,10 @@ void ListenerManager::createListeners(
|
||||
serverPtr->setAfterAcceptSockOptCallback(
|
||||
afterAcceptSetSockOptCallback_);
|
||||
}
|
||||
if (connectionCallback_)
|
||||
{
|
||||
serverPtr->setConnectionCallback(connectionCallback_);
|
||||
}
|
||||
|
||||
if (listener.useSSL_ && utils::supportsTls())
|
||||
{
|
||||
|
@ -61,6 +61,12 @@ class ListenerManager : public trantor::NonCopyable
|
||||
afterAcceptSetSockOptCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
void setConnectionCallback(
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> cb)
|
||||
{
|
||||
connectionCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
void reloadSSLFiles();
|
||||
|
||||
private:
|
||||
@ -101,6 +107,7 @@ class ListenerManager : public trantor::NonCopyable
|
||||
std::unique_ptr<trantor::EventLoopThread> listeningThread_;
|
||||
std::function<void(int)> beforeListenSetSockOptCallback_;
|
||||
std::function<void(int)> afterAcceptSetSockOptCallback_;
|
||||
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
||||
|
@ -44,7 +44,7 @@ void StaticFileRouter::init(const std::vector<trantor::EventLoop *> &ioLoops)
|
||||
size_t i) {
|
||||
assert(i == ioLoops[i]->index());
|
||||
mapPtr = std::make_unique<CacheMap<std::string, char>>(ioLoops[i],
|
||||
1.0,
|
||||
1.0f,
|
||||
4,
|
||||
50);
|
||||
});
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <json/value.h>
|
||||
#include <json/writer.h>
|
||||
#include <thread>
|
||||
#include <limits>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
@ -268,14 +269,14 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
||||
{
|
||||
// According to the rfc6455
|
||||
gotAll_ = false;
|
||||
if (buffer->readableBytes() >= 2)
|
||||
while (buffer->readableBytes() >= 2)
|
||||
{
|
||||
unsigned char opcode = (*buffer)[0] & 0x0f;
|
||||
bool isControlFrame = false;
|
||||
switch (opcode)
|
||||
{
|
||||
case 0:
|
||||
// continuation frame
|
||||
LOG_TRACE << "continuation frame";
|
||||
break;
|
||||
case 1:
|
||||
type_ = WebSocketMessageType::Text;
|
||||
@ -327,8 +328,13 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
||||
{
|
||||
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)
|
||||
{
|
||||
// rfc6455-5.5
|
||||
@ -344,14 +350,17 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
||||
}
|
||||
else if (indexFirstMask == 10)
|
||||
{
|
||||
length = (unsigned char)(*buffer)[2];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[3];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[4];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[5];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[6];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[7];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[8];
|
||||
length = (length << 8) + (unsigned char)(*buffer)[9];
|
||||
length = 0;
|
||||
for (int i = 2; i <= 9; ++i)
|
||||
{
|
||||
if (length > ((std::numeric_limits<size_t>::max)() >> 8))
|
||||
{
|
||||
LOG_ERROR
|
||||
<< "Payload length too large to handle safely";
|
||||
return false;
|
||||
}
|
||||
length = (length << 8) + (unsigned char)(*buffer)[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -380,9 +389,16 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
||||
{
|
||||
message_[oldLen + i] = (rawData[i] ^ masks[i % 4]);
|
||||
}
|
||||
if (isFin)
|
||||
gotAll_ = true;
|
||||
buffer->retrieve(indexFirstMask + 4 + length);
|
||||
if (isFin)
|
||||
{
|
||||
gotAll_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not enough data yet, wait for more.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -392,9 +408,16 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
||||
{
|
||||
auto rawData = buffer->peek() + indexFirstMask;
|
||||
message_.append(rawData, length);
|
||||
if (isFin)
|
||||
gotAll_ = true;
|
||||
buffer->retrieve(indexFirstMask + length);
|
||||
if (isFin)
|
||||
{
|
||||
gotAll_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not enough data yet, wait for more.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <drogon/DrObject.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpController.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
@ -41,3 +42,45 @@ DROGON_TEST(DrObjectNamespaceTest)
|
||||
CHECK(objPtr2.get() != nullptr);
|
||||
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);
|
||||
}
|
||||
|
@ -214,6 +214,28 @@ class DROGON_EXPORT DbClient : public trantor::NonCopyable
|
||||
(binder << std::forward<Arguments>(args), 0)...};
|
||||
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
|
||||
|
||||
/// Streaming-like method for sql execution. For more information, see the
|
||||
|
@ -1053,9 +1053,9 @@ inline void Mapper<T>::findBy(const Criteria &criteria,
|
||||
std::vector<T> ret;
|
||||
for (auto const &row : r)
|
||||
{
|
||||
ret.push_back(T(row));
|
||||
ret.emplace_back(row);
|
||||
}
|
||||
rcb(ret);
|
||||
rcb(std::move(ret));
|
||||
};
|
||||
binder >> ecb;
|
||||
}
|
||||
|
@ -116,6 +116,14 @@ enum class Mode
|
||||
Blocking
|
||||
};
|
||||
|
||||
struct RawParameter
|
||||
{
|
||||
std::shared_ptr<void> obj;
|
||||
const char *parameter;
|
||||
int length;
|
||||
int format;
|
||||
};
|
||||
|
||||
namespace internal
|
||||
{
|
||||
template <typename T>
|
||||
@ -434,6 +442,15 @@ class DROGON_EXPORT SqlBinder : public trantor::NonCopyable
|
||||
return *this;
|
||||
}
|
||||
|
||||
self &operator<<(const RawParameter &);
|
||||
|
||||
self &operator<<(RawParameter ¶m)
|
||||
{
|
||||
return operator<<((const RawParameter &)param);
|
||||
}
|
||||
|
||||
self &operator<<(RawParameter &&);
|
||||
|
||||
// template <>
|
||||
self &operator<<(const char str[])
|
||||
{
|
||||
|
@ -429,6 +429,8 @@ DbConnectionPtr DbClientImpl::newConnection(trantor::EventLoop *loop)
|
||||
}
|
||||
// Reconnect after 1 second
|
||||
auto loop = closeConnPtr->loop();
|
||||
// closeConnPtr may be not valid. Close the connection file descriptor.
|
||||
closeConnPtr->disconnect();
|
||||
loop->runAfter(1, [weakPtr, loop, closeConnPtr] {
|
||||
auto thisPtr = weakPtr.lock();
|
||||
if (!thisPtr)
|
||||
|
@ -118,7 +118,7 @@ class DbConnection : public trantor::NonCopyable
|
||||
|
||||
virtual ~DbConnection()
|
||||
{
|
||||
LOG_TRACE << "Destruct DbConn" << this;
|
||||
LOG_TRACE << "Destruct DbConn " << this;
|
||||
}
|
||||
|
||||
ConnectStatus status() const
|
||||
|
@ -18,6 +18,9 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <future>
|
||||
#include <regex>
|
||||
#if defined(__cpp_lib_format)
|
||||
#include <format>
|
||||
#endif
|
||||
#if USE_MYSQL
|
||||
#include <mysql.h>
|
||||
#endif
|
||||
@ -134,6 +137,26 @@ SqlBinder::~SqlBinder()
|
||||
}
|
||||
}
|
||||
|
||||
SqlBinder &SqlBinder::operator<<(const RawParameter ¶m)
|
||||
{
|
||||
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 &¶m)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()));
|
||||
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)
|
||||
|
@ -87,11 +87,14 @@ PgConnection::PgConnection(trantor::EventLoop *loop,
|
||||
[](PGconn *conn) { PQfinish(conn); })),
|
||||
channel_(loop, PQsocket(connectionPtr_.get()))
|
||||
{
|
||||
if (channel_.fd() < 0)
|
||||
{
|
||||
LOG_ERROR << "Failed to create Postgres connection";
|
||||
}
|
||||
}
|
||||
|
||||
void PgConnection::init()
|
||||
{
|
||||
PQsetnonblocking(connectionPtr_.get(), 1);
|
||||
if (channel_.fd() < 0)
|
||||
{
|
||||
LOG_ERROR << "Connection with Postgres could not be established";
|
||||
@ -103,6 +106,8 @@ void PgConnection::init()
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PQsetnonblocking(connectionPtr_.get(), 1);
|
||||
channel_.setReadCallback([this]() {
|
||||
if (status_ == ConnectStatus::Bad)
|
||||
{
|
||||
@ -165,8 +170,11 @@ void PgConnection::disconnect()
|
||||
auto thisPtr = shared_from_this();
|
||||
loop_->runInLoop([thisPtr, &pro]() {
|
||||
thisPtr->status_ = ConnectStatus::Bad;
|
||||
thisPtr->channel_.disableAll();
|
||||
thisPtr->channel_.remove();
|
||||
if (thisPtr->channel_.fd() >= 0)
|
||||
{
|
||||
thisPtr->channel_.disableAll();
|
||||
thisPtr->channel_.remove();
|
||||
}
|
||||
thisPtr->connectionPtr_.reset();
|
||||
pro.set_value(1);
|
||||
});
|
||||
@ -522,11 +530,17 @@ void PgConnection::handleFatalError(bool clearAll, bool isAbortPipeline)
|
||||
{
|
||||
for (auto &cmd : batchCommandsForWaitingResults_)
|
||||
{
|
||||
cmd->exceptionCallback_(exceptPtr);
|
||||
if (cmd->exceptionCallback_)
|
||||
{
|
||||
cmd->exceptionCallback_(exceptPtr);
|
||||
}
|
||||
}
|
||||
for (auto &cmd : batchSqlCommands_)
|
||||
{
|
||||
cmd->exceptionCallback_(exceptPtr);
|
||||
if (cmd->exceptionCallback_)
|
||||
{
|
||||
cmd->exceptionCallback_(exceptPtr);
|
||||
}
|
||||
}
|
||||
batchCommandsForWaitingResults_.clear();
|
||||
batchSqlCommands_.clear();
|
||||
@ -536,13 +550,19 @@ void PgConnection::handleFatalError(bool clearAll, bool isAbortPipeline)
|
||||
if (!batchSqlCommands_.empty() &&
|
||||
!batchSqlCommands_.front()->preparingStatement_.empty())
|
||||
{
|
||||
batchSqlCommands_.front()->exceptionCallback_(exceptPtr);
|
||||
if (batchSqlCommands_.front()->exceptionCallback_)
|
||||
{
|
||||
batchSqlCommands_.front()->exceptionCallback_(exceptPtr);
|
||||
}
|
||||
batchSqlCommands_.pop_front();
|
||||
}
|
||||
else if (!batchCommandsForWaitingResults_.empty())
|
||||
{
|
||||
auto &cmd = batchCommandsForWaitingResults_.front();
|
||||
cmd->exceptionCallback_(exceptPtr);
|
||||
if (cmd->exceptionCallback_)
|
||||
{
|
||||
cmd->exceptionCallback_(exceptPtr);
|
||||
}
|
||||
batchCommandsForWaitingResults_.pop_front();
|
||||
}
|
||||
else
|
||||
|
@ -65,11 +65,14 @@ PgConnection::PgConnection(trantor::EventLoop *loop,
|
||||
[](PGconn *conn) { PQfinish(conn); })),
|
||||
channel_(loop, PQsocket(connectionPtr_.get()))
|
||||
{
|
||||
if (channel_.fd() < 0)
|
||||
{
|
||||
LOG_ERROR << "Failed to create Postgres connection";
|
||||
}
|
||||
}
|
||||
|
||||
void PgConnection::init()
|
||||
{
|
||||
PQsetnonblocking(connectionPtr_.get(), 1);
|
||||
if (channel_.fd() < 0)
|
||||
{
|
||||
LOG_ERROR << "Connection with Postgres could not be established";
|
||||
@ -80,6 +83,8 @@ void PgConnection::init()
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
PQsetnonblocking(connectionPtr_.get(), 1);
|
||||
channel_.setReadCallback([this]() {
|
||||
if (status_ == ConnectStatus::Bad)
|
||||
{
|
||||
@ -128,6 +133,15 @@ void PgConnection::handleClosed()
|
||||
if (status_ == ConnectStatus::Bad)
|
||||
return;
|
||||
status_ = ConnectStatus::Bad;
|
||||
|
||||
if (isWorking_)
|
||||
{
|
||||
// Connection was closed unexpectedly while isWorking_ was true.
|
||||
isWorking_ = false;
|
||||
handleFatalError();
|
||||
callback_ = nullptr;
|
||||
}
|
||||
|
||||
channel_.disableAll();
|
||||
channel_.remove();
|
||||
assert(closeCallback_);
|
||||
@ -142,8 +156,11 @@ void PgConnection::disconnect()
|
||||
auto thisPtr = shared_from_this();
|
||||
loop_->runInLoop([thisPtr, &pro]() {
|
||||
thisPtr->status_ = ConnectStatus::Bad;
|
||||
thisPtr->channel_.disableAll();
|
||||
thisPtr->channel_.remove();
|
||||
if (thisPtr->channel_.fd() >= 0)
|
||||
{
|
||||
thisPtr->channel_.disableAll();
|
||||
thisPtr->channel_.remove();
|
||||
}
|
||||
thisPtr->connectionPtr_.reset();
|
||||
pro.set_value(1);
|
||||
});
|
||||
@ -398,9 +415,13 @@ void PgConnection::doAfterPreparing()
|
||||
|
||||
void PgConnection::handleFatalError()
|
||||
{
|
||||
auto exceptPtr =
|
||||
std::make_exception_ptr(Failure(PQerrorMessage(connectionPtr_.get())));
|
||||
exceptionCallback_(exceptPtr);
|
||||
if (exceptionCallback_)
|
||||
{
|
||||
auto exceptPtr = std::make_exception_ptr(
|
||||
Failure(PQerrorMessage(connectionPtr_.get())));
|
||||
exceptionCallback_(exceptPtr);
|
||||
}
|
||||
|
||||
exceptionCallback_ = nullptr;
|
||||
}
|
||||
|
||||
|
@ -287,11 +287,24 @@ DROGON_TEST(PostgreTest)
|
||||
FAULT("postgresql - DbClient streaming-type interface(8) 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" >>
|
||||
[TEST_CTX](const Result &r) { SUCCESS(); } >>
|
||||
[TEST_CTX](const DrogonDbException &e) {
|
||||
FAULT("postgresql - DbClient streaming-type interface(9) what():",
|
||||
FAULT("postgresql - DbClient streaming-type interface(10) what():",
|
||||
e.base().what());
|
||||
};
|
||||
/// Test asynchronous method
|
||||
@ -379,12 +392,21 @@ DROGON_TEST(PostgreTest)
|
||||
"postgresql1",
|
||||
"pg",
|
||||
"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(
|
||||
"truncate table users restart identity",
|
||||
[TEST_CTX](const Result &r) { SUCCESS(); },
|
||||
[TEST_CTX](const DrogonDbException &e) {
|
||||
FAULT("postgresql - DbClient asynchronous interface(7) what():",
|
||||
FAULT("postgresql - DbClient asynchronous interface(8) what():",
|
||||
e.base().what());
|
||||
});
|
||||
|
||||
@ -464,7 +486,19 @@ DROGON_TEST(PostgreTest)
|
||||
{
|
||||
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
|
||||
{
|
||||
auto r =
|
||||
@ -557,7 +591,20 @@ DROGON_TEST(PostgreTest)
|
||||
{
|
||||
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");
|
||||
try
|
||||
{
|
||||
@ -1670,11 +1717,28 @@ DROGON_TEST(MySQLTest)
|
||||
FAULT("mysql - DbClient streaming-type interface(8) 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) {
|
||||
SUCCESS();
|
||||
} >> [TEST_CTX](const DrogonDbException &e) {
|
||||
FAULT("mysql - DbClient streaming-type interface(9) what():",
|
||||
FAULT("mysql - DbClient streaming-type interface(10) what():",
|
||||
e.base().what());
|
||||
};
|
||||
/// Test asynchronous method
|
||||
@ -1759,12 +1823,21 @@ DROGON_TEST(MySQLTest)
|
||||
"postgresql1",
|
||||
"pg",
|
||||
"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(
|
||||
"truncate table users",
|
||||
[TEST_CTX](const Result &r) { SUCCESS(); },
|
||||
[TEST_CTX](const DrogonDbException &e) {
|
||||
FAULT("mysql - DbClient asynchronous interface(7) what():",
|
||||
FAULT("mysql - DbClient asynchronous interface(9) what():",
|
||||
e.base().what());
|
||||
});
|
||||
|
||||
@ -1845,7 +1918,19 @@ DROGON_TEST(MySQLTest)
|
||||
{
|
||||
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
|
||||
{
|
||||
auto r = clientPtr->execSqlSync("truncate table users");
|
||||
@ -1932,7 +2017,19 @@ DROGON_TEST(MySQLTest)
|
||||
{
|
||||
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");
|
||||
try
|
||||
{
|
||||
@ -1941,7 +2038,7 @@ DROGON_TEST(MySQLTest)
|
||||
}
|
||||
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
|
||||
@ -2881,17 +2978,29 @@ DROGON_TEST(SQLite3Test)
|
||||
FAULT("sqlite3 - DbClient streaming-type interface(8) 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) {
|
||||
SUCCESS();
|
||||
} >> [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());
|
||||
};
|
||||
*clientPtr << "UPDATE sqlite_sequence SET seq = 0" >>
|
||||
[TEST_CTX](const Result &r) { SUCCESS(); } >>
|
||||
[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());
|
||||
};
|
||||
/// Test asynchronous method
|
||||
@ -2975,19 +3084,28 @@ DROGON_TEST(SQLite3Test)
|
||||
"postgresql1",
|
||||
"pg",
|
||||
"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(
|
||||
"delete from users",
|
||||
[TEST_CTX](const Result &r) { SUCCESS(); },
|
||||
[TEST_CTX](const DrogonDbException &e) {
|
||||
FAULT("sqlite3 - DbClient asynchronous interface(7.1) what():",
|
||||
FAULT("sqlite3 - DbClient asynchronous interface(8.1) what():",
|
||||
e.base().what());
|
||||
});
|
||||
clientPtr->execSqlAsync(
|
||||
"UPDATE sqlite_sequence SET seq = 0",
|
||||
[TEST_CTX](const Result &r) { SUCCESS(); },
|
||||
[TEST_CTX](const DrogonDbException &e) {
|
||||
FAULT("sqlite3 - DbClient asynchronous interface(7.2) what():",
|
||||
FAULT("sqlite3 - DbClient asynchronous interface(8.2) what():",
|
||||
e.base().what());
|
||||
});
|
||||
|
||||
@ -3074,7 +3192,19 @@ DROGON_TEST(SQLite3Test)
|
||||
{
|
||||
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
|
||||
{
|
||||
auto r = clientPtr->execSqlSync("delete from users");
|
||||
@ -3177,6 +3307,19 @@ DROGON_TEST(SQLite3Test)
|
||||
{
|
||||
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
|
||||
f = clientPtr->execSqlAsyncFuture("delete from users");
|
||||
try
|
||||
|
2
trantor
2
trantor
@ -1 +1 @@
|
||||
Subproject commit 8bf280ba043eb77c3eb02121065e0891a9a4f431
|
||||
Subproject commit 43fd79b2dbac59608a819ebba167e8fe2c079d90
|
Loading…
x
Reference in New Issue
Block a user