Compare commits

...

169 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
antao
6d9ecb8d8d Bump version to 1.9.8 2024-10-27 14:31:56 +08:00
fantasy-peak
23c561f072
Add check the client connection status (#2191) 2024-10-25 16:08:03 +08:00
Tanglong3bf
284d14b8ca
Fix some bugs in plugin PromExporter. (#2189) 2024-10-21 09:51:34 +08:00
fantasy-peak
ca2210331d
Add sending customized http requests to drogon_ctl (#2186) 2024-10-17 10:47:04 +08:00
Martin Chang
3fce70b535
Replace rejection sampling and remove use of rand() (#2180) 2024-10-10 13:45:46 +08:00
An Tao
bf1fc03bff
Fix lint (#2181) 2024-10-09 18:20:29 +08:00
an-tao
5225bb3295 Update trantor and add docker actions 2024-10-09 16:45:19 +08:00
An Tao
ac0a1b873e
Fix a bug after removing content-length header in some responses (#2176) 2024-10-02 16:42:40 +08:00
Muhammad
912f1d803c
Optimize query params and allow for empty values (#2171) 2024-09-26 15:47:58 +08:00
An Tao
13d7148764
Remove websocketResponseTest from windows shared library env (#2170) 2024-09-25 20:16:40 +08:00
Chad Barth
b0c5331bc1
Remove content-length header from 101 Switching Protocols response (#2164) 2024-09-25 17:52:41 +08:00
Christopher T
c9f5754423
Add support for escaped identifiers in Postgresql (#2167) 2024-09-23 16:00:50 +08:00
an-tao
31fb18fb46 Update trantor 2024-09-20 16:22:09 +08:00
toge
f918ead0ae
include exception header for std::exception_ptr (#2159) 2024-09-19 10:11:04 +08:00
Muhammad
fee34095a2
Add Hodor whitelists (#2154) 2024-09-19 09:56:11 +08:00
Omar Mohamed Khallaf
2911a7c08a
Fix coroutine continuation handle (#2163)
Using coroutines directly by the user e.g. declaring a function with
drogon::Task<> return type, will have it's continuation set to nullptr.

Returning nullptr causes a segfault and it must be checked before
returning e.g. the snippet form https://en.cppreference.com/w/cpp/coroutine/noop_coroutine

```cpp
struct final_awaiter
{
    std::coroutine_handle<>
        await_suspend(std::coroutine_handle<promise_type> h) noexcept
    {
        // final_awaiter::await_suspend is called when the execution of the
        // current coroutine (referred to by 'h') is about to finish.
        // If the current coroutine was resumed by another coroutine via
        // co_await get_task(), a handle to that coroutine has been stored
        // as h.promise().previous. In that case, return the handle to resume
        // the previous coroutine.
        // Otherwise, return noop_coroutine(), whose resumption does nothing.

        if (auto previous = h.promise().previous; previous)
            return previous;
        else
            return std::noop_coroutine();
    }
};
```

This commit default initializes the continuation handle to no op coroutine and
avoids the check.

Signed-off-by: Omar Mohamed <mohamed.omar67492@gmail.com>
2024-09-19 09:52:51 +08:00
Muhammad
1b4653577f
Revert original path to its initial behavior (#2157) 2024-09-17 11:54:33 +08:00
Muhammad
bbcad71458
Add in-place base64 encode and decode (#2153) 2024-09-14 09:57:07 +08:00
Muhammad
beec858eba
Partially revert commit 93d8fb425d7e95939d398afd51b9d5adc4392c43 (#2156) 2024-09-13 17:34:23 +08:00
Muhammad
93d8fb425d
Fix forwarding with space in url by encoding (#2155) 2024-09-13 09:46:06 +08:00
antao
73406d1225 Bump version to 1.9.7 2024-09-10 23:12:02 +08:00
Ponder
6bafdf30fd
Feature: TcpServer hot reload SSL file (#2150) 2024-09-10 11:38:56 +08:00
元路
59919f33ef
Refine SQLite3 error types with new exception handling (#2145)
Signed-off-by: yuanlu <2573580691@qq.com>
2024-09-06 15:37:09 +08:00
an-tao
1326205483 Change a log level 2024-08-23 11:34:50 +08:00
Omar Mohamed Khallaf
80ec7d9211
Use correct libraries when compiling statically (#2136)
When compiling statically, cmake pulls shared libraries as dependencies
for drogon e.g. libpq.so instead of libpq.a. This causes linkage errors
when compiling the whole program.

The flag USE_STATIC_LIBS_ONLY should set the CMAKE_FIND_LIBRARY_SUFFIXES
to the appropriate suffix on different platforms, thus find_* commands
find the right library.
2024-08-22 15:46:47 +08:00
fantasy-peak
5b5d1906bf
Add requestsBufferSize function (#2124) 2024-08-14 11:15:33 +08:00
Muhammad
206ef0d881
Modernize cookies (#2112) 2024-08-08 22:23:28 +08:00
fantasy-peak
c46f149c2c
Add coroutine mutex (#2095) 2024-08-08 15:17:06 +08:00
Bohdan Tsehelnyk
0546032edc
change stoi to stoul (#2115) 2024-08-06 17:16:06 +08:00
Bohdan Tsehelnyk
f743cfd4d1
add quotes (#2116) 2024-08-06 17:15:01 +08:00
Muhammad
500d44faac
Allow MultiPartParser to be movable (#2107) 2024-07-23 11:52:52 +03:00
antao
e786907478 Bump version to 1.9.6 2024-07-20 23:35:17 +08:00
Nitromelon
5d4523a3a6
Support request stream (#2055) 2024-07-03 11:31:39 +08:00
fantasy-peak
dfacd1b454
Add setsockopt to HttpServer (#2086) 2024-07-02 10:04:56 +08:00
Chuck
85b918f9e9
Update README.md (#2080)
A few minor typos.
2024-06-21 10:04:11 +08:00
fantasy-peak
7b8d0085b1
Delay parsing json for HttpClient (#2077) 2024-06-20 00:09:08 +08:00
An Tao
f6913f6328 Add an example of prometheus (#2076) 2024-06-19 14:13:15 +08:00
Martin Chang
a2f759e4cd
Update trantor (#2074) 2024-06-17 18:59:57 +08:00
Nitromelon
ad2b7e13e1
Fix typo in yaml config. (#2069) 2024-06-15 10:27:51 +08:00
An Tao
b04dfd7f96
Fix some compilation warnings (#2066) 2024-06-13 17:11:03 +08:00
Muhammad
de5a4a5f57
Allow MultiPartParser to parse PATCH requests (#2062) 2024-06-09 18:46:57 +03:00
An Tao
8bdb9b2fa6
Bump version to 1.9.5 (#2057) 2024-06-08 20:31:23 +08:00
fantasy-peak
0a889e921d
Add registerMiddleware (#2052) 2024-06-04 17:05:52 +08:00
TYUTthelastone
9a96a20c6e
Add regex support for websocket controller (#1779) 2024-06-04 11:27:00 +08:00
Nitromelon
f37a1d036f
Fix pg client name; Add testcase. (#2043) 2024-05-29 00:23:43 +08:00
Nitromelon
c4c95918bf
Fix wrong parameter order. (#2040) 2024-05-27 19:10:05 +08:00
Martin Chang
6726df139d
fix codespell error (#2038) 2024-05-26 14:08:18 +08:00
Jonathan S. Fonseca
150735848d
Intention to present an alternative to improve the aforementioned method (#2034) 2024-05-25 21:30:01 +08:00
Nitromelon
155ae9ad65
Support postgresql connection options (#1972) 2024-05-23 14:03:28 +08:00
Nitromelon
306df8a8fb
Install gcc in ci. (#2028) 2024-05-22 14:48:12 +08:00
Nitromelon
82c46f13f8
Fix CI on windows (#2025) 2024-05-10 14:11:28 +08:00
an-tao
5f75222243 Add the conan badge to readme files 2024-05-10 10:59:32 +08:00
Nitromelon
5b7cefd32c Support per-method middlewares. (#2015) 2024-05-10 10:59:32 +08:00
Tanglong3bf
abbcf6023d
Fix an error in the yaml format config file (#2020)
There is an error in the `app.log.use_spdlog` item in the default
config file in the yaml format.
While fixing this error, other minor problems in the config files were
fixed.
For example, some spelling errors and missing items in yaml format.
At the same time, different config files are modified to store the same
content.
2024-05-07 14:00:15 +08:00
antao
b5cd748a12 Bump version to 1.9.4 2024-05-04 22:42:15 +08:00
Nitromelon
439ddd8dbe
Bypass clang thread_local error. (#2016) 2024-04-29 11:32:30 +08:00
Alexey Gerasimchuck
e79d5170b4
Implemented database reconnection loop (#2003) 2024-04-24 10:09:05 +08:00
Muhammad
519398c159
Avoid string copy and lowercasing on every request (#2008) 2024-04-22 17:52:36 +08:00
lirunjie
96919df488
Fix typo in HttpAppFrameworkImpl.cc (#1992) 2024-04-09 10:15:47 +08:00
Martin Chang
294035beb9
update trantor (#1988) 2024-04-04 18:24:18 +08:00
Muhammad
46ac53adb3
Add JSON send overloads for WebSocket connections (#1970) 2024-04-02 15:41:47 +08:00
An Tao
9f2872639a
Remove the request shared_ptr from the multipart parser (#1984) 2024-03-27 16:02:37 +08:00
I-LOVE-C2H5OH
4cbac301fb
add client cert support for websocket (#1967) 2024-03-04 16:50:33 +08:00
itrofimow
88d06684f2
Minor enhancement: move some smart pointers around instead of copying them (#1954) 2024-02-15 14:52:10 +08:00
an-tao
da7f065a6f Bump version to 1.9.3 2024-02-09 16:06:33 +08:00
Greisberger Christophe
aa04d33e02
Enhancement: extend drogon::ContentType for file handling (#1921) 2024-02-09 15:30:50 +08:00
Muhammad
1a9ad1a2c9
Use std::string_view for WebSockets (#1949) 2024-02-08 13:44:08 +08:00
Ken Matsui
22f4b4fad6
Simplify traits in utils (#1952) 2024-02-08 00:25:31 +08:00
An Tao
99d97df25f
Alias the safe hashmap template (#1947) 2024-02-04 23:13:01 +08:00
Muhammad
6aed658fab
Change drogon::MultiPartParser's parameters data type from std::map to std::unordered_map (#1946) 2024-02-04 21:32:12 +08:00
Ken Matsui
3d469112ca
Enable readability/multiline_string for cpplint (#1940) 2024-01-31 20:17:59 +08:00
Ken Matsui
f63480674f
Enable build/storage_class for cpplint (#1937) 2024-01-31 19:21:46 +08:00
Ken Matsui
0d178877f0
Enable build/header_guard for cpplint (#1936) 2024-01-31 19:05:20 +08:00
Ken Matsui
5f273d8744
Enable build/include_order for cpplint (#1938) 2024-01-31 14:00:56 +08:00
Ken Matsui
3c8c273582
Enable build/explicit_make_pair for cpplint (#1935) 2024-01-31 11:21:39 +08:00
Ken Matsui
c2b8e7c624
Enable readability/inheritance for cpplint (#1934) 2024-01-31 11:21:18 +08:00
Ken Matsui
56a53165b6
Add newline at EOF (#1932) 2024-01-31 10:43:30 +08:00
Ken Matsui
674137e89d
Use clang-format-17 (#1931) 2024-01-30 09:57:58 +08:00
Ken Matsui
ffc6e74f27
Enable readability/alt_tokens for cpplint (#1930)
* Enable readability/alt_tokens for cpplint

* Format drogon_ctl/create_model.cc
2024-01-30 09:55:47 +08:00
Ken Matsui
359b63fa77
Introduce cpplint (#1929) 2024-01-30 00:47:28 +08:00
Greisberger Christophe
baea2dce47
Added getParameter() and getOptionalParameter() (#1923) 2024-01-27 13:37:58 +08:00
Greisberger Christophe
cca1c8f262
Fix drogon::util::fromString() (#1925) 2024-01-26 08:27:14 +08:00
Nitromelon
aeed963915
Fix a wrong place of return. (#1922) 2024-01-25 19:13:28 +08:00
Hazimi Md Nazri
e640cc092d
Add support for gentoo linux, dev-db/mariadb contains mysql (#1914) 2024-01-19 22:11:53 +08:00
an-tao
93c568bb95 Bump version to 1.9.2 2024-01-18 21:46:10 +08:00
MWX
af29e25b03
Fix name issue when cross-compiling (#1906) 2024-01-18 13:38:27 +08:00
An Tao
d745cfe765
Move the RealIpResolver plugin to the PreRouting join point (#1904) 2024-01-16 11:18:13 +08:00
tripleslash
6b36b3a4f9
Support asynchronous sending of chunked responses (trantors new API) (#1886)
Co-authored-by: fantasy-peak <1356346239@qq.com>
Co-authored-by: Michael Bleis <michael.bleis@aracom.de>
2024-01-15 23:26:27 +08:00
antores
8e4ffa71e2
Fix building with MSYS2 (#1902)
Co-authored-by: antores <antores@users.noreply.github.com>
2024-01-15 10:14:05 +08:00
an-tao
25f874a278 Update trantor 2024-01-12 13:56:14 +08:00
antores
5ecbd1f184
Fix htonll/ntohll redefinition (#1899)
Co-authored-by: antores <antores@users.noreply.github.com>
2024-01-09 09:47:07 +08:00
dependabot[bot]
34d32a1ef0
Bump github/codeql-action from 2 to 3 (#1894)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-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-01-01 21:09:34 +08:00
Ken Matsui
1fd5c7ea5e
Remove macos-11 CI; not supported by Homebrew (#1891) 2023-12-28 22:29:45 +08:00
An Tao
2a0da80d5f
Avoid a race condition in database listener tests (#1890) 2023-12-28 19:23:42 +08:00
Ken Matsui
01ad18d2d5
Add CI tests with more compilers (#1889) 2023-12-28 15:59:29 +08:00
Nitromelon
f4443dce44
Refine request routing process (#1871)
Code structure changes:
* Combine HttpSimpleControllersRouter, HttpControllersRouter and WebsocketControllersRouter, saving a lot of repetitive codes.
* Extract ControllerBinder classes from three Router classes.
* Extract aop advices and logics into AopAdvice class. Flatten the callback hells of AOPs. Prevent aop vectors from being passed into every router.
* Extract request handling process out of Router class. Let router class do its own work (routing only). Put them in HttpServer class. Now all http process logic stays in HttpServer class, no need to jump around to follow the code flow.
* Adjust doFilters() and doAdviceChain(), save a few lambda construction.
Fixed logic bugs (behavior changing):
* Fix inconsistent session handling (callCallback() vs. callback directly)
* Fix inconsistent aop path between SimpleController and HttpController
* Remove router if simple controller class not found.
2023-12-28 11:53:10 +08:00
An Tao
021c89ec78
Add -k option to the drogon_ctl when running the press command (#1887)
* Add -k option to the drogon_ctl when running the press command

* Fix some warnings

* Fix a bug of bytes statistics in HttpClient
2023-12-25 09:39:16 +08:00
an-tao
125dd0e69e Set the url of trantor to the official repository 2023-12-19 10:02:42 +08:00
an-tao
38bde80aaa Update trantor 2023-12-18 18:25:36 +08:00
An Tao
4eec56c49f
Remove the default ctor of the Row class in ORM (#1885) 2023-12-17 08:28:35 +08:00
Tanglong3bf
ba9e9731d2
Fix ORM: The original way did not handle exceptions correctly. (#1872) 2023-12-16 14:07:27 +08:00
Martin Chang
41b740f649
add discord link to readme (#1879) 2023-12-11 14:10:33 +08:00
Christian Clauss
e76bf08eb2
GitHub Action to find typos in the codebase using codespell (#1876)
GitHub Action to find typos in the codebase using codespell
https://github.com/codespell-project/codespell
2023-12-09 13:48:24 +08:00
An Tao
c35e62ccd2
Use execute_process instead of exec_program in FindJsoncpp.cmake (#1875) 2023-12-08 17:33:06 +08:00
antao
358de6e66f Modify the configuration file templates in drogon_ctl 2023-12-05 22:05:19 +08:00
Greisberger Christophe
8026790e1a
Feature: Integrate spdlog as logging backend (#1771) 2023-12-04 23:42:35 +08:00
An Tao
4e890f52d6
Fix a error of coroutines on Windows (#1870) 2023-12-04 15:13:09 +08:00
Minha, Jeong
27f1a3d812
Fix: typo on Mapper method (#1867) 2023-11-28 17:52:25 +08:00
an-tao
6370461896 Bump version to 1.9.1 2023-11-27 18:55:54 +08:00
Muhammad
6b20a9fa8d
Return HttpAppFramework by setExceptionHandler (#1866) 2023-11-27 17:43:56 +08:00
Muhammad
830ced8c5b
Remove unused and undefined overloads of isBase64 (#1865) 2023-11-27 17:08:01 +08:00
Tanglong3bf
6f6a03b14b
Provide some functions for incrementing the value of given columns. (#1831) 2023-11-25 02:23:37 +08:00
Martin Chang
f21b899e63
Simplify drogon test with c++17 (#1862) 2023-11-24 18:17:30 +08:00
Vinicius
26840aa056
Fix: uuid formatting (#1854)
Co-authored-by: root <vinicts@protonmail.com>
Co-authored-by: an-tao <antao2002@gmail.com>
2023-11-18 16:23:13 +08:00
George Constantinides
01385f4f33
Update test_cmake.csp (#1856)
_test not needed for submodule target_link_libraries since it was added in project
2023-11-18 16:13:30 +08:00
Muhammad
2000a4279e
Make id generator consistent (#1851) 2023-11-18 15:46:59 +08:00
An Tao
1ec3c96cbb
Use the constexpr if instead of std::enable_if (#1843) 2023-11-15 11:22:14 +08:00
Muhammad
56f0102cfe
Custom sessions (#1841) 2023-11-09 21:38:41 +08:00
antores
a76c11cc34
Pass HttpRequestPtr to custom error handlers (#1830) 2023-11-09 12:49:11 +08:00
An Tao
e5daba6bf5
Fix a bug of the GlobalFilters plugin (#1842) 2023-11-09 12:47:02 +08:00
Vincent Le Quang
8586874c87
Fix build due to trantor commit out of date and address warnings. (#1839) 2023-11-08 15:10:50 +08:00
antao
f215cb15a0 Bump version to 1.9.0 2023-10-29 11:44:42 +08:00
An Tao
7599ae98a0
Change logs in the AccessLogger plugin to TRACE level (#1829) 2023-10-29 11:30:44 +08:00
Viktor Mukha
4323e7b6ef
FIX int mapping to int64_t instead of uint64_t (#1825)
Negative numbers were not passing json validation
2023-10-28 22:13:57 +08:00
An Tao
9ffe1b267b
Fix an error in the secureRandomString function (#1816) 2023-10-19 19:38:17 +08:00
Muhammad
645c2d8aaf
Use wss://echo.websocket.events/.ws in WebSocket client example (#1809) 2023-10-12 11:27:49 +08:00
An Tao
ab76e80089
Make & and * directly adjacent to variable names (#1810) 2023-10-12 11:27:25 +08:00
TheEnigmist
d9afdf279a
Added isTopicEmpty function (#1808)
Co-authored-by: TheEnigmist <lthenigmistl@gmail.com>
2023-10-10 11:03:27 +08:00
dependabot[bot]
1efe89a719
Bump actions/checkout from 3 to 4 (#1801)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  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>
2023-10-01 20:37:09 +08:00
OrbitZore
fd7af8110f
Add optional Criteria && || operator support (#1797) 2023-09-26 13:58:02 +08:00
an-tao
63b7f5eb13 Update the ubuntu Dockerfile 2023-09-25 18:06:08 +08:00
340 changed files with 28126 additions and 6426 deletions

View File

@ -53,7 +53,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4 ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4 ContinuationIndentWidth: 4
Cpp11BracedListStyle: true Cpp11BracedListStyle: true
DerivePointerAlignment: true DerivePointerAlignment: false
DisableFormat: false DisableFormat: false
FixNamespaceComments: true FixNamespaceComments: true
ForEachMacros: ForEachMacros:
@ -75,6 +75,7 @@ IndentCaseLabels: true
IndentPPDirectives: None IndentPPDirectives: None
IndentWidth: 4 IndentWidth: 4
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
InsertNewlineAtEOF: true
JavaScriptQuotes: Leave JavaScriptQuotes: Leave
JavaScriptWrapImports: true JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtTheStartOfBlocks: false
@ -94,7 +95,8 @@ PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10 PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000 PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 2000 PenaltyReturnTypeOnItsOwnLine: 2000
PointerAlignment: Left PointerAlignment: Right
ReferenceAlignment: Right
RawStringFormats: RawStringFormats:
- Language: Cpp - Language: Cpp
Delimiters: Delimiters:
@ -125,7 +127,7 @@ RawStringFormats:
BasedOnStyle: google BasedOnStyle: google
ReflowComments: true ReflowComments: true
SeparateDefinitionBlocks: Always SeparateDefinitionBlocks: Always
SortIncludes: false SortIncludes: Never
SortUsingDeclarations: true SortUsingDeclarations: true
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true SpaceAfterTemplateKeyword: true

View File

@ -17,14 +17,14 @@ 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:
link: ["STATIC", "SHARED"] link: ["STATIC", "SHARED"]
steps: steps:
- name: Checkout Drogon source code - name: Checkout Drogon source code
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
@ -53,6 +53,7 @@ jobs:
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \ -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \
-DBUILD_CTL=ON \ -DBUILD_CTL=ON \
-DBUILD_EXAMPLES=ON \ -DBUILD_EXAMPLES=ON \
-DUSE_SPDLOG=ON \
-DCMAKE_INSTALL_PREFIX=../install \ -DCMAKE_INSTALL_PREFIX=../install \
-DCMAKE_POLICY_DEFAULT_CMP0091=NEW \ -DCMAKE_POLICY_DEFAULT_CMP0091=NEW \
-DCMAKE_CXX_STANDARD=17 -DCMAKE_CXX_STANDARD=17
@ -65,18 +66,21 @@ jobs:
run: ./test.sh -w run: ./test.sh -w
macos: macos:
name: macos/clang runs-on: macos-${{ matrix.osver }}
runs-on: macos-latest strategy:
fail-fast: false
matrix:
osver: [13, 14, 15]
steps: steps:
- name: Checkout Drogon source code - name: Checkout Drogon source code
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
- name: Install dependencies - name: Install dependencies
# Already installed: brotli, zlib, postgresql@14, lz4, sqlite3 # Already installed: brotli, zlib, lz4, sqlite3
run: brew install ninja jsoncpp mariadb hiredis redis run: brew install ninja jsoncpp mariadb hiredis redis spdlog postgresql@14
- name: Create Build Environment & Configure Cmake - name: Create Build Environment & Configure Cmake
# Some projects don't allow in-source building, so create a separate build directory # Some projects don't allow in-source building, so create a separate build directory
@ -85,6 +89,7 @@ jobs:
cmake -B build -G Ninja \ cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DBUILD_TESTING=on \ -DBUILD_TESTING=on \
-DUSE_SPDLOG=ON \
-DBUILD_SHARED_LIBS=OFF -DBUILD_SHARED_LIBS=OFF
- name: Build - name: Build
@ -94,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
@ -112,21 +116,47 @@ jobs:
run: ./test.sh -t run: ./test.sh -t
ubuntu: ubuntu:
name: ${{ matrix.buildname }}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
link: [SHARED, STATIC]
compiler:
- cxx: g++
ver: 9
- cxx: g++
ver: 10
- cxx: g++
ver: 11
- cxx: g++
ver: 12
- cxx: g++
ver: 13
- cxx: clang++
ver: 11
- cxx: clang++
ver: 12
- cxx: clang++
ver: 13
- cxx: clang++
ver: 14
- cxx: clang++
ver: 15
- cxx: clang++
ver: 16
- cxx: clang++
ver: 17
include: include:
- buildname: "ubuntu-22.04/gcc" - link: STATIC
link: SHARED compiler:
- buildname: "ubuntu-22.04/gcc" cxx: g++
link: STATIC ver: 13
- buildname: "ubuntu-22.04/coroutines" feature: coroutines
link: STATIC env:
CXX: ${{ matrix.compiler.cxx }}-${{ matrix.compiler.ver }}
steps: steps:
- name: Checkout Drogon source code - name: Checkout Drogon source code
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
@ -138,12 +168,31 @@ jobs:
# These aren't available or don't work well in vcpkg # These aren't available or don't work well in vcpkg
sudo apt-get install -y libjsoncpp-dev uuid-dev libssl-dev zlib1g-dev libsqlite3-dev sudo apt-get install -y libjsoncpp-dev uuid-dev libssl-dev zlib1g-dev libsqlite3-dev
sudo apt-get install -y ninja-build libbrotli-dev sudo apt-get install -y ninja-build libbrotli-dev
sudo apt-get install -y libspdlog-dev
- name: Install postgresql - name: Install postgresql
run: | run: |
sudo apt-get --purge remove postgresql postgresql-doc postgresql-common postgresql-client-common sudo apt-get --purge remove postgresql postgresql-doc postgresql-common postgresql-client-common
sudo apt-get -y install postgresql-all sudo apt-get -y install postgresql-all
- name: Install g++-13
if: startsWith(matrix.compiler.cxx, 'g++') && matrix.compiler.ver == 13
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get install g++-${{ matrix.compiler.ver }}
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13
- name: Install Clang
if: startsWith(matrix.compiler.cxx, 'clang') && matrix.compiler.ver < 13
run: sudo apt-get install clang-${{ matrix.compiler.ver }}
- name: Install Clang
if: startsWith(matrix.compiler.cxx, 'clang') && matrix.compiler.ver >= 13
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh ${{ matrix.compiler.ver }}
- name: Export `shared` - name: Export `shared`
run: | run: |
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF" [[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
@ -152,22 +201,24 @@ jobs:
- name: Create Build Environment & Configure Cmake - name: Create Build Environment & Configure Cmake
# Some projects don't allow in-source building, so create a separate build directory # Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands # We'll use this as our working directory for all subsequent commands
if: matrix.buildname != 'ubuntu-22.04/coroutines' if: matrix.compiler.feature != 'coroutines'
run: | run: |
cmake -B build -G Ninja \ cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DBUILD_TESTING=on \ -DBUILD_TESTING=on \
-DUSE_SPDLOG=ON \
-DBUILD_SHARED_LIBS=$shared -DBUILD_SHARED_LIBS=$shared
- name: Create Build Environment & Configure Cmake (coroutines) - name: Create Build Environment & Configure Cmake (coroutines)
# Some projects don't allow in-source building, so create a separate build directory # Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands # We'll use this as our working directory for all subsequent commands
if: matrix.buildname == 'ubuntu-22.04/coroutines' if: matrix.compiler.feature == 'coroutines'
run: | run: |
cmake -B build -G Ninja \ cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DBUILD_TESTING=on \ -DBUILD_TESTING=on \
-DUSE_SPDLOG=ON \
-DCMAKE_CXX_FLAGS="-fcoroutines" \ -DCMAKE_CXX_FLAGS="-fcoroutines" \
-DBUILD_SHARED_LIBS=$shared -DBUILD_SHARED_LIBS=$shared \
- name: Build - name: Build
working-directory: ./build working-directory: ./build

View File

@ -40,7 +40,7 @@ jobs:
steps: steps:
- name: Checkout Drogon source code - name: Checkout Drogon source code
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: true submodules: true
fetch-depth: 0 fetch-depth: 0
@ -60,7 +60,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -75,6 +75,6 @@ jobs:
run: ninja && sudo ninja install run: ninja && sudo ninja install
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

15
.github/workflows/codespell.yml vendored Normal file
View File

@ -0,0 +1,15 @@
# Look for typos in the codebase using codespell.
# https://github.com/codespell-project/codespell#readme
name: codespell
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
codespell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y codespell
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows,NotIn,aNULL," --skip="*.csp"

View File

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

28
.github/workflows/docker-publish.yml vendored Normal file
View File

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

1
.gitignore vendored
View File

@ -35,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

3
.gitmodules vendored
View File

@ -1,3 +1,4 @@
[submodule "trantor"] [submodule "trantor"]
path = trantor path = trantor
url = https://github.com/an-tao/trantor.git url = https://github.com/an-tao/trantor.git
branch = master

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5...3.31)
project(drogon) project(drogon)
@ -13,6 +13,7 @@ option(BUILD_DOC "Build Doxygen documentation" OFF)
option(BUILD_BROTLI "Build Brotli" ON) option(BUILD_BROTLI "Build Brotli" ON)
option(BUILD_YAML_CONFIG "Build yaml config" ON) option(BUILD_YAML_CONFIG "Build yaml config" ON)
option(USE_SUBMODULE "Use trantor as a submodule" ON) option(USE_SUBMODULE "Use trantor as a submodule" ON)
option(USE_STATIC_LIBS_ONLY "Use only static libraries as dependencies" OFF)
include(CMakeDependentOption) include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(BUILD_POSTGRESQL "Build with postgresql support" ON "BUILD_ORM" OFF) CMAKE_DEPENDENT_OPTION(BUILD_POSTGRESQL "Build with postgresql support" ON "BUILD_ORM" OFF)
@ -20,13 +21,14 @@ CMAKE_DEPENDENT_OPTION(LIBPQ_BATCH_MODE "Use batch mode for libpq" ON "BUILD_POS
CMAKE_DEPENDENT_OPTION(BUILD_MYSQL "Build with mysql support" ON "BUILD_ORM" OFF) CMAKE_DEPENDENT_OPTION(BUILD_MYSQL "Build with mysql support" ON "BUILD_ORM" OFF)
CMAKE_DEPENDENT_OPTION(BUILD_SQLITE "Build with sqlite3 support" ON "BUILD_ORM" OFF) CMAKE_DEPENDENT_OPTION(BUILD_SQLITE "Build with sqlite3 support" ON "BUILD_ORM" OFF)
CMAKE_DEPENDENT_OPTION(BUILD_REDIS "Build with redis support" ON "BUILD_ORM" OFF) CMAKE_DEPENDENT_OPTION(BUILD_REDIS "Build with redis support" ON "BUILD_ORM" OFF)
CMAKE_DEPENDENT_OPTION(USE_SPDLOG "Allow using the spdlog logging library" OFF "USE_SUBMODULE" 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 0) 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}-rc.1") set(DROGON_VERSION_STRING "${DROGON_VERSION}")
include(GNUInstallDirs) include(GNUInstallDirs)
# Offer the user the choice of overriding the installation directories # Offer the user the choice of overriding the installation directories
@ -38,11 +40,17 @@ set(INSTALL_DROGON_CMAKE_DIR ${DEF_INSTALL_DROGON_CMAKE_DIR}
CACHE PATH "Installation directory for cmake files") CACHE PATH "Installation directory for cmake files")
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)
# Tells Visual Studio 2017 (15.7+) and newer to correctly set the value of the standard __cplusplus macro,
# instead of leaving it to 199711L and settings the effective c++ version in _MSVC_LANG
# Dropping support for older versions of VS would allow to only rely on __cplusplus
add_compile_options("/Zc:__cplusplus")
endif()
endif () endif ()
add_library(${PROJECT_NAME}) add_library(${PROJECT_NAME})
@ -59,7 +67,7 @@ if (BUILD_SHARED_LIBS)
SOVERSION ${DROGON_MAJOR_VERSION}) SOVERSION ${DROGON_MAJOR_VERSION})
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads) target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
if (WIN32) if (WIN32)
target_link_libraries(${PROJECT_NAME} PUBLIC Rpcrt4 ws2_32 crypt32 Advapi32) target_link_libraries(${PROJECT_NAME} PUBLIC rpcrt4 crypt32 advapi32 ws2_32)
if (CMAKE_CXX_COMPILER_ID MATCHES MSVC) if (CMAKE_CXX_COMPILER_ID MATCHES MSVC)
# Ignore MSVC C4251 and C4275 warning of exporting std objects with no dll export # Ignore MSVC C4251 and C4275 warning of exporting std objects with no dll export
# We export class to facilitate maintenance, thus if you compile # We export class to facilitate maintenance, thus if you compile
@ -70,6 +78,19 @@ if (BUILD_SHARED_LIBS)
endif () endif ()
endif (BUILD_SHARED_LIBS) endif (BUILD_SHARED_LIBS)
if(USE_STATIC_LIBS_ONLY)
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}")
endif(USE_STATIC_LIBS_ONLY)
if(USE_SPDLOG)
find_package(spdlog CONFIG)
if(spdlog_FOUND)
message(STATUS "spdlog found!")
target_link_libraries(${PROJECT_NAME} PUBLIC spdlog::spdlog_header_only)
target_compile_definitions(${PROJECT_NAME} PUBLIC DROGON_SPDLOG_SUPPORT SPDLOG_FMT_EXTERNAL FMT_HEADER_ONLY)
endif(spdlog_FOUND)
endif(USE_SPDLOG)
if (NOT ${CMAKE_PLATFORM_NAME} STREQUAL "Windows" AND CMAKE_CXX_COMPILER_ID MATCHES GNU) if (NOT ${CMAKE_PLATFORM_NAME} STREQUAL "Windows" AND CMAKE_CXX_COMPILER_ID MATCHES GNU)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Werror) target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Werror)
endif () endif ()
@ -100,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}>
@ -125,7 +147,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
elseif (NOT WIN32 AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") elseif (NOT WIN32 AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
target_link_libraries(${PROJECT_NAME} PRIVATE dl) target_link_libraries(${PROJECT_NAME} PRIVATE dl)
elseif (WIN32) elseif (WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi) target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi ws2_32 iphlpapi)
endif () endif ()
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
@ -237,7 +259,7 @@ set(DROGON_SOURCES
lib/src/Cookie.cc lib/src/Cookie.cc
lib/src/DrClassMap.cc lib/src/DrClassMap.cc
lib/src/DrTemplateBase.cc lib/src/DrTemplateBase.cc
lib/src/FiltersFunction.cc lib/src/MiddlewaresFunction.cc
lib/src/FixedWindowRateLimiter.cc lib/src/FixedWindowRateLimiter.cc
lib/src/GlobalFilters.cc lib/src/GlobalFilters.cc
lib/src/Histogram.cc lib/src/Histogram.cc
@ -245,15 +267,17 @@ set(DROGON_SOURCES
lib/src/HttpAppFrameworkImpl.cc lib/src/HttpAppFrameworkImpl.cc
lib/src/HttpBinder.cc lib/src/HttpBinder.cc
lib/src/HttpClientImpl.cc lib/src/HttpClientImpl.cc
lib/src/HttpConnectionLimit.cc
lib/src/HttpControllerBinder.cc
lib/src/HttpControllersRouter.cc lib/src/HttpControllersRouter.cc
lib/src/HttpFileImpl.cc lib/src/HttpFileImpl.cc
lib/src/HttpFileUploadRequest.cc lib/src/HttpFileUploadRequest.cc
lib/src/HttpRequestImpl.cc lib/src/HttpRequestImpl.cc
lib/src/HttpRequestParser.cc lib/src/HttpRequestParser.cc
lib/src/RequestStream.cc
lib/src/HttpResponseImpl.cc lib/src/HttpResponseImpl.cc
lib/src/HttpResponseParser.cc lib/src/HttpResponseParser.cc
lib/src/HttpServer.cc lib/src/HttpServer.cc
lib/src/HttpSimpleControllersRouter.cc
lib/src/HttpUtils.cc lib/src/HttpUtils.cc
lib/src/HttpViewData.cc lib/src/HttpViewData.cc
lib/src/IntranetIpFilter.cc lib/src/IntranetIpFilter.cc
@ -261,6 +285,7 @@ set(DROGON_SOURCES
lib/src/ListenerManager.cc lib/src/ListenerManager.cc
lib/src/LocalHostFilter.cc lib/src/LocalHostFilter.cc
lib/src/MultiPart.cc lib/src/MultiPart.cc
lib/src/MultipartStreamParser.cc
lib/src/NotFound.cc lib/src/NotFound.cc
lib/src/PluginsManager.cc lib/src/PluginsManager.cc
lib/src/PromExporter.cc lib/src/PromExporter.cc
@ -278,16 +303,18 @@ set(DROGON_SOURCES
lib/src/Utilities.cc lib/src/Utilities.cc
lib/src/WebSocketClientImpl.cc lib/src/WebSocketClientImpl.cc
lib/src/WebSocketConnectionImpl.cc lib/src/WebSocketConnectionImpl.cc
lib/src/WebsocketControllersRouter.cc
lib/src/YamlConfigAdapter.cc lib/src/YamlConfigAdapter.cc
lib/src/drogon_test.cc) lib/src/drogon_test.cc)
set(private_headers set(private_headers
lib/src/AOPAdvice.h lib/src/AOPAdvice.h
lib/src/CacheFile.h lib/src/CacheFile.h
lib/src/ConfigLoader.h lib/src/ConfigLoader.h
lib/src/FiltersFunction.h lib/src/ControllerBinderBase.h
lib/src/MiddlewaresFunction.h
lib/src/HttpAppFrameworkImpl.h lib/src/HttpAppFrameworkImpl.h
lib/src/HttpClientImpl.h lib/src/HttpClientImpl.h
lib/src/HttpConnectionLimit.h
lib/src/HttpControllerBinder.h
lib/src/HttpControllersRouter.h lib/src/HttpControllersRouter.h
lib/src/HttpFileImpl.h lib/src/HttpFileImpl.h
lib/src/HttpFileUploadRequest.h lib/src/HttpFileUploadRequest.h
@ -297,7 +324,6 @@ set(private_headers
lib/src/HttpResponseImpl.h lib/src/HttpResponseImpl.h
lib/src/HttpResponseParser.h lib/src/HttpResponseParser.h
lib/src/HttpServer.h lib/src/HttpServer.h
lib/src/HttpSimpleControllersRouter.h
lib/src/HttpUtils.h lib/src/HttpUtils.h
lib/src/impl_forwards.h lib/src/impl_forwards.h
lib/src/ListenerManager.h lib/src/ListenerManager.h
@ -308,30 +334,30 @@ set(private_headers
lib/src/TaskTimeoutFlag.h lib/src/TaskTimeoutFlag.h
lib/src/WebSocketClientImpl.h lib/src/WebSocketClientImpl.h
lib/src/WebSocketConnectionImpl.h lib/src/WebSocketConnectionImpl.h
lib/src/WebsocketControllersRouter.h
lib/src/FixedWindowRateLimiter.h lib/src/FixedWindowRateLimiter.h
lib/src/SlidingWindowRateLimiter.h lib/src/SlidingWindowRateLimiter.h
lib/src/TokenBucketRateLimiter.h lib/src/TokenBucketRateLimiter.h
lib/src/ConfigAdapterManager.h lib/src/ConfigAdapterManager.h
lib/src/JsonConfigAdapter.h lib/src/JsonConfigAdapter.h
lib/src/YamlConfigAdapter.h lib/src/YamlConfigAdapter.h
lib/src/ConfigAdapter.h) lib/src/ConfigAdapter.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
@ -485,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)
@ -540,7 +566,9 @@ set(DROGON_HEADERS
lib/inc/drogon/HttpClient.h lib/inc/drogon/HttpClient.h
lib/inc/drogon/HttpController.h lib/inc/drogon/HttpController.h
lib/inc/drogon/HttpFilter.h lib/inc/drogon/HttpFilter.h
lib/inc/drogon/HttpMiddleware.h
lib/inc/drogon/HttpRequest.h lib/inc/drogon/HttpRequest.h
lib/inc/drogon/RequestStream.h
lib/inc/drogon/HttpResponse.h lib/inc/drogon/HttpResponse.h
lib/inc/drogon/HttpSimpleController.h lib/inc/drogon/HttpSimpleController.h
lib/inc/drogon/HttpTypes.h lib/inc/drogon/HttpTypes.h
@ -556,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
@ -665,6 +693,7 @@ set(ORM_HEADERS
orm_lib/inc/drogon/orm/BaseBuilder.h orm_lib/inc/drogon/orm/BaseBuilder.h
orm_lib/inc/drogon/orm/Criteria.h orm_lib/inc/drogon/orm/Criteria.h
orm_lib/inc/drogon/orm/DbClient.h orm_lib/inc/drogon/orm/DbClient.h
orm_lib/inc/drogon/orm/DbConfig.h
orm_lib/inc/drogon/orm/DbListener.h orm_lib/inc/drogon/orm/DbListener.h
orm_lib/inc/drogon/orm/DbTypes.h orm_lib/inc/drogon/orm/DbTypes.h
orm_lib/inc/drogon/orm/Exception.h orm_lib/inc/drogon/orm/Exception.h

36
CPPLINT.cfg Normal file
View File

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

View File

@ -4,6 +4,382 @@ 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
### API changes list
- Add in-place base64 encode and decode.
- Add check the client connection status.
### Changed
- Add Hodor whitelists.
- Include exception header for std::exception_ptr.
- Add support for escaped identifiers in Postgresql.
- Remove content-length header from 101 Switching Protocols response.
- Remove websocketResponseTest from windows shared library env.
- Optimize query params and allow for empty values.
- Replace rejection sampling and remove use of rand().
- Add sending customized http requests to drogon_ctl.
### Fixed
- Fix coroutine continuation handle.
- Fix some bugs in plugin PromExporter.
- Fix a bug after removing content-length header in some responses.
## [1.9.7] - 2024-09-10
### API changes list
- Add coroutine mutex.
- Add requestsBufferSize function.
- Refine SQLite3 error types with new exception handling.
- Add a new method to reload SSL files on the fly.
### Changed
- Allow MultiPartParser to be movable.
- Add quotes to the table name in the ORM generator.
- Change stoi to stoul in the Field class.
- Modernize cookies.
- Change a log level.
### Fixed
- Use correct libraries when compiling statically.
## [1.9.6] - 2024-07-20
### API changes list
- Add setsockopt to HttpServer.
- Support request stream.
### Changed
- Allow MultiPartParser to parse PATCH requests.
- Add an example of prometheus.
- Delay parsing json for HttpClient.
- Update README.md.
### Fixed
- Fix some compilation warnings.
- Fix typo in yaml config.
## [1.9.5] - 2024-06-08
### API changes list
- Fix an error in the yaml format config file.
- Support postgresql connection options.
- Add regex support for websocket controller.
- Add the registerMiddleware method.
### Changed
- Add the conan badge to readme files.
- Install gcc in ci.
- Intention to present an alternative to improve the performance of a method in models.
### Fixed
- Fix an error in the yaml format config file.
- Fix CI on Windows.
- Fix some spelling errors.
## [1.9.4] - 2024-05-04
### API changes list
- Add client cert support for websocket.
- Add JSON send overloads for WebSocket connections.
### Changed
- Minor enhancement: move some smart pointers around instead of copying them.
- Remove the request shared_ptr from the multipart parser.
- Fix typo in HttpAppFrameworkImpl.cc.
- Avoid string copy and lowercasing on every request.
- Implemented database reconnection loop.
### Fixed
- Bypass clang thread_local error.
## [1.9.3] - 2024-02-09
### API changes list
- Added getParameter() and getOptionalParameter().
- Change drogon::MultiPartParser's parameters data type.
- Use std::string_view for WebSockets.
### Changed
- Add support for gentoo linux, dev-db/mariadb contains mysql.
- Introduce cpplint to the CI.
- Enable readability/alt_tokens for cpplint.
- Use clang-format-17.
- Add newline at EOF.
- Enable readability/inheritance for cpplint.
- Enable build/explicit_make_pair for cpplint.
- Enable build/include_order for cpplint.
- Enable build/header_guard for cpplint.
- Enable build/storage_class for cpplint.
- Enable readability/multiline_string for cpplint.
- Alias the safe hashmap template.
- Simplify traits in utils.
- Enhancement: extend drogon::ContentType for file handling.
### Fixed
- Fix a wrong place of return.
- Fix drogon::util::fromString().
## [1.9.2] - 2024-01-18
### API changes list
- Feature: Integrate spdlog as logging backend.
- Support asynchronous sending of chunked responses.
### Changed
- Modify the configuration file templates in drogon_ctl.
- Use execute_process instead of exec_program in FindJsoncpp.cmake.
- GitHub Action to find typos in the codebase using codespell.
- add discord link to readme.
- Add -k option to the drogon_ctl when running the press command.
- Refine request routing process.
- Add CI tests with more compilers.
- Avoid a race condition in database listener tests.
- Remove macos-11 CI; not supported by Homebrew.
- Bump github/codeql-action from 2 to 3.
- Move the RealIpResolver plugin to the PreRouting join point.
### Fixed
- Fix: typo on Mapper method.
- Fix a error of coroutines on Windows.
- Fix ORM: The original way did not handle exceptions correctly.
- Remove the default ctor of the Row class in ORM.
- Set the url of trantor to the official repository.
- Fix htonll/ntohll redefinition.
- Fix building with MSYS2.
- Fix name issue when cross-compiling.
## [1.9.1] - 2023-11-27
### API changes list
- Pass HttpRequestPtr to custom error handlers.
- Provide some functions for incrementing the value of given columns.
- Return HttpAppFramework by setExceptionHandler.
### Changed
- Custom sessions.
- Use the constexpr if instead of std::enable_if.
- Make id generator consistent.
- Update test_cmake.csp.
- Simplify drogon test with c++17.
- Remove unused and undefined overloads of isBase64.
### Fixed
- Fix build due to trantor commit out of date and address warnings.
- Fix a bug of the GlobalFilters plugin.
- Fix: uuid formatting.
## [1.9.0] - 2023-10-29
### API changes list
- Added isTopicEmpty function;
### Changed
- Update the ubuntu Dockerfile;
- Add optional Criteria && || operator support;
- Bump actions/checkout from 3 to 4;
- Make & and * directly adjacent to variable names;
- Use wss://echo.websocket.events/.ws in WebSocket client example;
- Change logs in the AccessLogger plugin to TRACE level;
### Fixed
- Fix an error in the secureRandomString function;
- FIX int mapping to int64_t instead of uint64_t;
## [1.9.0-rc.1] - 2023-09-23 ## [1.9.0-rc.1] - 2023-09-23
### API changes list ### API changes list
@ -12,7 +388,7 @@ All notable changes to this project will be documented in this file.
- Add isHead() method to HttpRequest, to preserve information about the original method for use in the controller. - Add isHead() method to HttpRequest, to preserve information about the original method for use in the controller.
- Allow omitting template paremeter in execCommandSync. - Allow omitting template parameter in execCommandSync.
- Add a method to HttpRequest to access the matched routing parameters. - Add a method to HttpRequest to access the matched routing parameters.
@ -144,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.
@ -476,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.
@ -578,7 +954,7 @@ All notable changes to this project will be documented in this file.
- Return on redis connection errors - Return on redis connection errors
- Fix(MutliPart): Does not respect quotes in Content-Disposition header - Fix(MultiPart): Does not respect quotes in Content-Disposition header
- Fix(cmake): error in FindFilesystem - Fix(cmake): error in FindFilesystem
@ -736,7 +1112,7 @@ All notable changes to this project will be documented in this file.
- Use two-phase construction for the DbClientImpl and the RedisClientImpl. - Use two-phase construction for the DbClientImpl and the RedisClientImpl.
- Add support 'select <db>' for redis. - Add support 'select &lt;db&gt;' for redis.
### Fixed ### Fixed
@ -1046,7 +1422,7 @@ All notable changes to this project will be documented in this file.
- Destroy DNS resolver of HttpClient in the correct thread. - Destroy DNS resolver of HttpClient in the correct thread.
- Add the header <cctype> to resolve build errors in VS2017. - Add the header &lt;cctype&gt; to resolve build errors in VS2017.
## [1.0.0-beta18] - 2020-06-14 ## [1.0.0-beta18] - 2020-06-14
@ -1466,9 +1842,33 @@ 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.0-rc.1...HEAD [Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.11...HEAD
[1.9.0-rc.1]: https://github.com/an-tao/drogon/compare/v1.8.6...1.9.0-rc.1 [1.9.11]: https://github.com/an-tao/drogon/compare/v1.9.10...v1.9.11
[1.9.10]: https://github.com/an-tao/drogon/compare/v1.9.9...v1.9.10
[1.9.9]: https://github.com/an-tao/drogon/compare/v1.9.8...v1.9.9
[1.9.8]: https://github.com/an-tao/drogon/compare/v1.9.7...v1.9.8
[1.9.7]: https://github.com/an-tao/drogon/compare/v1.9.6...v1.9.7
[1.9.6]: https://github.com/an-tao/drogon/compare/v1.9.5...v1.9.6
[1.9.5]: https://github.com/an-tao/drogon/compare/v1.9.4...v1.9.5
[1.9.4]: https://github.com/an-tao/drogon/compare/v1.9.3...v1.9.4
[1.9.3]: https://github.com/an-tao/drogon/compare/v1.9.2...v1.9.3
[1.9.2]: https://github.com/an-tao/drogon/compare/v1.9.1...v1.9.2
[1.9.1]: https://github.com/an-tao/drogon/compare/v1.9.0...v1.9.1
[1.9.0]: https://github.com/an-tao/drogon/compare/v1.9.0-rc.1...v1.9.0
[1.9.0-rc.1]: https://github.com/an-tao/drogon/compare/v1.8.6...v1.9.0-rc.1
[1.8.6]: https://github.com/an-tao/drogon/compare/v1.8.5...v1.8.6 [1.8.6]: https://github.com/an-tao/drogon/compare/v1.8.5...v1.8.6

View File

@ -1,13 +1,14 @@
![](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)
[![Join the chat at https://gitter.im/drogon-web/community](https://badges.gitter.im/drogon-web/community.svg)](https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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)
[![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon) [![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon)
English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md) 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:
@ -33,7 +34,7 @@ Drogon is a cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD
* Support ARM Architecture; * Support ARM Architecture;
* Provide a convenient lightweight ORM implementation that supports for regular object-to-database bidirectional mapping; * Provide a convenient lightweight ORM implementation that supports for regular object-to-database bidirectional mapping;
* Support plugins which can be installed by the configuration file at load time; * Support plugins which can be installed by the configuration file at load time;
* Support AOP with build-in joinpoints. * Support AOP with built-in joinpoints.
* Support C++ coroutines * Support C++ coroutines
## A very simple example ## A very simple example
@ -182,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,8 +1,9 @@
![](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)
[![Join the chat at https://gitter.im/drogon-web/community](https://badges.gitter.im/drogon-web/community.svg)](https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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)
[![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon) [![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon)
[English](./README.md) | 简体中文 | [繁體中文](./README.zh-TW.md) [English](./README.md) | 简体中文 | [繁體中文](./README.zh-TW.md)
@ -185,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)**
## 贡献方式 ## 贡献方式
@ -196,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,47 +1,49 @@
![](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)
[![Join the chat at https://gitter.im/drogon-web/community](https://badges.gitter.im/drogon-web/community.svg)](https://gitter.im/drogon-web/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![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)
[![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon) [![Docker image](https://img.shields.io/badge/Docker-image-blue.svg)](https://cloud.docker.com/u/drogonframework/repository/docker/drogonframework/drogon)
[English](./README.md) | [简体中文](./README.zh-CN.md) | 繁體中文 [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>
@ -57,7 +59,7 @@ int main()
} }
``` ```
如果使用設定文件,可以進一步簡化成這樣: 如果使用設定檔案,可以進一步簡化成:
```c++ ```c++
#include <drogon/drogon.h> #include <drogon/drogon.h>
@ -68,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}",
@ -85,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
@ -116,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
@ -147,7 +147,7 @@ void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
} }
``` ```
讓我們更進一步,通過HttpController類別創造一個RESTful API的例子如下所示忽略了實做文件 讓我們更進一步,透過 HttpController 類別建立一個 RESTful API 的範例,如下所示(省略實作檔案
```c++ ```c++
/// The header file /// The header file
@ -181,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

@ -1,5 +1,7 @@
#pragma once
#define MAJOR @DROGON_MAJOR_VERSION@ #define MAJOR @DROGON_MAJOR_VERSION@
#define MINOR @DROGON_MINOR_VERSION@ #define MINOR @DROGON_MINOR_VERSION@
#define PATCH @DROGON_PATCH_VERSION@ #define PATCH @DROGON_PATCH_VERSION@
#define DROGON_VERSION "@DROGON_VERSION_STRING@" #define DROGON_VERSION "@DROGON_VERSION_STRING@"
#define DROGON_VERSION_SHA1 "@GIT_SHA1@" #define DROGON_VERSION_SHA1 "@GIT_SHA1@"

View File

@ -102,7 +102,7 @@ if(TARGET std::filesystem)
return() return()
endif() endif()
# Ignore fileystem check if version too low # Ignore filesystem check if version too low
if(CMAKE_VERSION VERSION_LESS 3.10) if(CMAKE_VERSION VERSION_LESS 3.10)
set(CXX_FILESYSTEM_HAVE_FS FALSE CACHE BOOL "TRUE if we have the C++ filesystem headers") set(CXX_FILESYSTEM_HAVE_FS FALSE CACHE BOOL "TRUE if we have the C++ filesystem headers")
set(Filesystem_FOUND FALSE CACHE BOOL "TRUE if we can run a program using std::filesystem" FORCE) set(Filesystem_FOUND FALSE CACHE BOOL "TRUE if we can run a program using std::filesystem" FORCE)
@ -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

@ -44,13 +44,16 @@ if(Jsoncpp_FOUND)
message(FATAL_ERROR "Error: jsoncpp lib is too old.....stop") message(FATAL_ERROR "Error: jsoncpp lib is too old.....stop")
endif() endif()
if(NOT WIN32) if(NOT WIN32)
exec_program( execute_process(
cat COMMAND cat ${JSONCPP_INCLUDE_DIRS}/json/version.h
ARGS COMMAND grep JSONCPP_VERSION_STRING
"${JSONCPP_INCLUDE_DIRS}/json/version.h |grep JSONCPP_VERSION_STRING|sed s'/.*define/define/'|awk '{printf $3}'|sed s'/\"//g'" COMMAND sed -e "s/.*define/define/"
OUTPUT_VARIABLE COMMAND awk "{ printf \$3 }"
jsoncpp_ver) COMMAND sed -e "s/\"//g"
message(STATUS "jsoncpp verson:" ${jsoncpp_ver}) OUTPUT_VARIABLE 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

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

View File

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

View File

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

View File

@ -1,29 +1,29 @@
FROM ubuntu:20.04 FROM ubuntu:22.04
ENV TZ=UTC ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update -yqq \ RUN apt-get update -yqq \
&& apt-get install -yqq --no-install-recommends software-properties-common \ && apt-get install -yqq --no-install-recommends software-properties-common \
curl wget cmake make pkg-config locales git gcc-10 g++-10 \ sudo curl wget cmake make pkg-config locales git gcc-11 g++-11 \
openssl libssl-dev libjsoncpp-dev uuid-dev zlib1g-dev libc-ares-dev\ openssl libssl-dev libjsoncpp-dev uuid-dev zlib1g-dev libc-ares-dev\
postgresql-server-dev-all libmariadbclient-dev libsqlite3-dev libhiredis-dev\ postgresql-server-dev-all libmariadb-dev libsqlite3-dev libhiredis-dev\
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& locale-gen en_US.UTF-8 && locale-gen en_US.UTF-8
ENV LANG=en_US.UTF-8 \ ENV LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \ LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \
CC=gcc-10 \ CC=gcc-11 \
CXX=g++-10 \ CXX=g++-11 \
AR=gcc-ar-10 \ AR=gcc-ar-11 \
RANLIB=gcc-ranlib-10 \ RANLIB=gcc-ranlib-11 \
IROOT=/install IROOT=/install
ENV DROGON_ROOT="$IROOT/drogon" ENV DROGON_ROOT="$IROOT/drogon"
ADD https://api.github.com/repos/an-tao/drogon/git/refs/heads/master $IROOT/version.json ADD https://api.github.com/repos/drogonframework/drogon/git/refs/heads/master $IROOT/version.json
RUN git clone https://github.com/an-tao/drogon $DROGON_ROOT RUN git clone https://github.com/drogonframework/drogon $DROGON_ROOT
WORKDIR $DROGON_ROOT WORKDIR $DROGON_ROOT

View File

@ -57,7 +57,7 @@ target_link_libraries(drogon_ctl PRIVATE ${PROJECT_NAME})
target_include_directories(drogon_ctl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(drogon_ctl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(drogon_ctl _drogon_ctl) add_dependencies(drogon_ctl _drogon_ctl)
if(WIN32) if(WIN32)
target_link_libraries(drogon_ctl PRIVATE ws2_32 Rpcrt4 iphlpapi) target_link_libraries(drogon_ctl PRIVATE ws2_32 rpcrt4 iphlpapi)
endif(WIN32) endif(WIN32)
if(APPLE) if(APPLE)
target_link_libraries(drogon_ctl PRIVATE resolv) target_link_libraries(drogon_ctl PRIVATE resolv)

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

@ -468,4 +468,4 @@ void create_controller::createARestfulController(const std::string &className,
<< std::endl; << std::endl;
std::cout << "File name: " << ctlName << ".h and " << ctlName << ".cc" std::cout << "File name: " << ctlName << ".h and " << ctlName << ".cc"
<< std::endl; << std::endl;
} }

View File

@ -66,6 +66,17 @@ static std::string escapeConnString(const std::string &str)
return escaped; return escaped;
} }
std::string drogon_ctl::escapeIdentifier(const std::string &identifier,
const std::string &rdbms)
{
if (rdbms != "postgresql")
{
return identifier;
}
return "\\\"" + identifier + "\\\"";
}
static std::map<std::string, std::vector<ConvertMethod>> getConvertMethods( static std::map<std::string, std::vector<ConvertMethod>> getConvertMethods(
const Json::Value &convertColumns) const Json::Value &convertColumns)
{ {
@ -166,7 +177,7 @@ void create_model::createModelClassFromPG(
auto className = nameTransform(tableName, true); auto className = nameTransform(tableName, true);
HttpViewData data; HttpViewData data;
data["className"] = className; data["className"] = className;
data["tableName"] = toLower(tableName); data["tableName"] = tableName;
data["hasPrimaryKey"] = (int)0; data["hasPrimaryKey"] = (int)0;
data["primaryKeyName"] = ""; data["primaryKeyName"] = "";
data["dbName"] = dbname_; data["dbName"] = dbname_;
@ -178,10 +189,10 @@ void create_model::createModelClassFromPG(
data["schema"] = schema; data["schema"] = schema;
} }
std::vector<ColumnInfo> cols; std::vector<ColumnInfo> cols;
*client << "SELECT * \ *client << "SELECT * "
FROM information_schema.columns \ "FROM information_schema.columns "
WHERE table_schema = $1 \ "WHERE table_schema = $1 "
AND table_name = $2" "AND table_name = $2"
<< schema << tableName << Mode::Blocking >> << schema << tableName << Mode::Blocking >>
[&](const Result &r) { [&](const Result &r) {
if (r.size() == 0) if (r.size() == 0)
@ -284,14 +295,14 @@ void create_model::createModelClassFromPG(
exit(1); exit(1);
}; };
size_t pkNumber = 0; size_t pkNumber = 0;
*client << "SELECT \ *client << "SELECT "
pg_constraint.conname AS pk_name,\ "pg_constraint.conname AS pk_name,"
pg_constraint.conkey AS pk_vector \ "pg_constraint.conkey AS pk_vector "
FROM pg_constraint \ "FROM pg_constraint "
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \ "INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid "
WHERE \ "WHERE "
pg_class.relname = $1 \ "pg_class.relname = $1 "
AND pg_constraint.contype = 'p'" "AND pg_constraint.contype = 'p'"
<< tableName << Mode::Blocking >> << tableName << Mode::Blocking >>
[&](bool isNull, [&](bool isNull,
const std::string &pkName, const std::string &pkName,
@ -308,16 +319,18 @@ void create_model::createModelClassFromPG(
data["hasPrimaryKey"] = (int)pkNumber; data["hasPrimaryKey"] = (int)pkNumber;
if (pkNumber == 1) if (pkNumber == 1)
{ {
*client << "SELECT \ *client << "SELECT "
pg_attribute.attname AS colname,\ "pg_attribute.attname AS colname,"
pg_type.typname AS typename,\ "pg_type.typname AS typename,"
pg_constraint.contype AS contype \ "pg_constraint.contype AS contype "
FROM pg_constraint \ "FROM pg_constraint "
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \ "INNER JOIN pg_class ON pg_constraint.conrelid = "
INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid \ "pg_class.oid "
AND pg_attribute.attnum = pg_constraint.conkey [ 1 ] \ "INNER JOIN pg_attribute ON pg_attribute.attrelid = "
INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid \ "pg_class.oid "
WHERE pg_class.relname = $1 and pg_constraint.contype='p'" "AND pg_attribute.attnum = pg_constraint.conkey [ 1 ] "
"INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid "
"WHERE pg_class.relname = $1 and pg_constraint.contype='p'"
<< tableName << Mode::Blocking >> << tableName << Mode::Blocking >>
[&](bool isNull, [&](bool isNull,
const std::string &colName, const std::string &colName,
@ -345,16 +358,20 @@ void create_model::createModelClassFromPG(
std::vector<std::string> pkNames, pkTypes, pkValNames; std::vector<std::string> pkNames, pkTypes, pkValNames;
for (size_t i = 1; i <= pkNumber; ++i) for (size_t i = 1; i <= pkNumber; ++i)
{ {
*client << "SELECT \ *client << "SELECT "
pg_attribute.attname AS colname,\ "pg_attribute.attname AS colname,"
pg_type.typname AS typename,\ "pg_type.typname AS typename,"
pg_constraint.contype AS contype \ "pg_constraint.contype AS contype "
FROM pg_constraint \ "FROM pg_constraint "
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \ "INNER JOIN pg_class ON pg_constraint.conrelid = "
INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid \ "pg_class.oid "
AND pg_attribute.attnum = pg_constraint.conkey [ $1 ] \ "INNER JOIN pg_attribute ON pg_attribute.attrelid = "
INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid \ "pg_class.oid "
WHERE pg_class.relname = $2 and pg_constraint.contype='p'" "AND pg_attribute.attnum = pg_constraint.conkey [ $1 ] "
"INNER JOIN pg_type ON pg_type.oid = "
"pg_attribute.atttypid "
"WHERE pg_class.relname = $2 and "
"pg_constraint.contype='p'"
<< (int)i << tableName << Mode::Blocking >> << (int)i << tableName << Mode::Blocking >>
[&](bool isNull, std::string colName, const std::string &type) { [&](bool isNull, std::string colName, const std::string &type) {
if (isNull) if (isNull)
@ -454,7 +471,7 @@ void create_model::createModelClassFromMysql(
data["convertMethods"] = convertMethods; data["convertMethods"] = convertMethods;
std::vector<ColumnInfo> cols; std::vector<ColumnInfo> cols;
int i = 0; int i = 0;
*client << "desc " + tableName << Mode::Blocking >> *client << "desc `" + tableName + "`" << Mode::Blocking >>
[&i, &cols](bool isNull, [&i, &cols](bool isNull,
const std::string &field, const std::string &field,
const std::string &type, const std::string &type,
@ -693,7 +710,7 @@ void create_model::createModelClassFromSqlite3(
if (type.find("int") != std::string::npos) if (type.find("int") != std::string::npos)
{ {
info.colType_ = "uint64_t"; info.colType_ = "int64_t";
info.colLength_ = 8; info.colLength_ = 8;
} }
else if (type.find("char") != std::string::npos || type == "text" || else if (type.find("char") != std::string::npos || type == "text" ||
@ -809,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
@ -1156,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)
{ {
@ -1194,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

@ -78,6 +78,9 @@ inline std::string nameTransform(const std::string &origName, bool isType)
return ret; return ret;
} }
std::string escapeIdentifier(const std::string &identifier,
const std::string &rdbms);
class PivotTable class PivotTable
{ {
public: public:
@ -426,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

@ -18,7 +18,11 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <iomanip> #include <iomanip>
#include <stdlib.h> #include <cstdlib>
#include <json/json.h>
#include <fstream>
#include <string>
#include <unordered_map>
#ifndef _WIN32 #ifndef _WIN32
#include <unistd.h> #include <unistd.h>
#endif #endif
@ -32,10 +36,11 @@ std::string press::detail()
" -n num number of requests(default : 1)\n" " -n num number of requests(default : 1)\n"
" -t num number of threads(default : 1)\n" " -t num number of threads(default : 1)\n"
" -c num concurrent connections(default : 1)\n" " -c num concurrent connections(default : 1)\n"
// " -k keep alive(default: no)\n" " -k disable SSL certificate validation(default: enable)\n"
" -q no progress indication(default: no)\n\n" " -f customize http request json file(default: disenable)\n"
" -q no progress indication(default: show)\n\n"
"example: drogon_ctl press -n 10000 -c 100 -t 4 -q " "example: drogon_ctl press -n 10000 -c 100 -t 4 -q "
"http://localhost:8080/index.html\n"; "http://localhost:8080/index.html -f ./http_request.json\n";
} }
void outputErrorAndExit(const std::string_view &err) void outputErrorAndExit(const std::string_view &err)
@ -151,11 +156,29 @@ void press::handleCommand(std::vector<std::string> &parameters)
continue; continue;
} }
} }
// else if (param == "-k") else if (param.find("-f") == 0)
// { {
// keepAlive_ = true; if (param == "-f")
// continue; {
// } ++iter;
if (iter == parameters.end())
{
outputErrorAndExit("No http request json file!");
}
httpRequestJsonFile_ = *iter;
continue;
}
else
{
httpRequestJsonFile_ = param.substr(2);
continue;
}
}
else if (param == "-k")
{
certValidation_ = false;
continue;
}
else if (param == "-q") else if (param == "-q")
{ {
processIndication_ = false; processIndication_ = false;
@ -178,7 +201,7 @@ void press::handleCommand(std::vector<std::string> &parameters)
else else
{ {
auto pos = url_.find("://"); auto pos = url_.find("://");
auto posOfPath = url_.find("/", pos + 3); auto posOfPath = url_.find('/', pos + 3);
if (posOfPath == std::string::npos) if (posOfPath == std::string::npos)
{ {
host_ = url_; host_ = url_;
@ -190,6 +213,118 @@ void press::handleCommand(std::vector<std::string> &parameters)
path_ = url_.substr(posOfPath); path_ = url_.substr(posOfPath);
} }
} }
/*
http_request.json
{
"method": "POST",
"header": {
"token": "e2e9d0fe-dd14-4eaf-8ac1-0997730a805d"
},
"body": {
"passwd": "123456",
"account": "10001"
}
}
*/
if (!httpRequestJsonFile_.empty())
{
Json::Value httpRequestJson;
std::ifstream httpRequestFile(httpRequestJsonFile_,
std::ifstream::binary);
if (!httpRequestFile.is_open())
{
outputErrorAndExit(std::string{"No "} + httpRequestJsonFile_);
}
httpRequestFile >> httpRequestJson;
if (!httpRequestJson.isMember("method"))
{
outputErrorAndExit("No contain method");
}
auto methodStr = httpRequestJson["method"].asString();
std::transform(methodStr.begin(),
methodStr.end(),
methodStr.begin(),
::toupper);
auto toHttpMethod = [&]() -> drogon::HttpMethod {
if (methodStr == "GET")
{
return drogon::HttpMethod::Get;
}
else if (methodStr == "POST")
{
return drogon::HttpMethod::Post;
}
else if (methodStr == "HEAD")
{
return drogon::HttpMethod::Head;
}
else if (methodStr == "PUT")
{
return drogon::HttpMethod::Put;
}
else if (methodStr == "DELETE")
{
return drogon::HttpMethod::Delete;
}
else if (methodStr == "OPTIONS")
{
return drogon::HttpMethod::Options;
}
else if (methodStr == "PATCH")
{
return drogon::HttpMethod::Patch;
}
else
{
outputErrorAndExit("invalid method");
}
return drogon::HttpMethod::Get;
};
std::unordered_map<std::string, std::string> header;
if (httpRequestJson.isMember("header"))
{
auto &jsonValue = httpRequestJson["header"];
for (const auto &key : jsonValue.getMemberNames())
{
if (jsonValue[key].isString())
{
header[key] = jsonValue[key].asString();
}
else
{
header[key] = jsonValue[key].toStyledString();
}
}
}
std::string body;
if (httpRequestJson.isMember("body"))
{
Json::FastWriter fastWriter;
body = fastWriter.write(httpRequestJson["body"]);
}
createHttpRequestFunc_ = [this,
method = toHttpMethod(),
body = std::move(body),
header =
std::move(header)]() -> HttpRequestPtr {
auto request = HttpRequest::newHttpRequest();
request->setPath(path_);
request->setMethod(method);
for (const auto &[field, val] : header)
request->addHeader(field, val);
if (!body.empty())
request->setBody(body);
return request;
};
}
// std::cout << "host=" << host_ << std::endl; // std::cout << "host=" << host_ << std::endl;
// std::cout << "path=" << path_ << std::endl; // std::cout << "path=" << path_ << std::endl;
doTesting(); doTesting();
@ -216,8 +351,10 @@ void press::createRequestAndClients()
loopPool_->start(); loopPool_->start();
for (size_t i = 0; i < numOfConnections_; ++i) for (size_t i = 0; i < numOfConnections_; ++i)
{ {
auto client = auto client = HttpClient::newHttpClient(host_,
HttpClient::newHttpClient(host_, loopPool_->getNextLoop()); loopPool_->getNextLoop(),
false,
certValidation_);
client->enableCookies(); client->enableCookies();
clients_.push_back(client); clients_.push_back(client);
} }
@ -230,9 +367,19 @@ void press::sendRequest(const HttpClientPtr &client)
{ {
return; return;
} }
auto request = HttpRequest::newHttpRequest();
request->setPath(path_); HttpRequestPtr request;
request->setMethod(Get); if (createHttpRequestFunc_)
{
request = createHttpRequestFunc_();
}
else
{
request = HttpRequest::newHttpRequest();
request->setPath(path_);
request->setMethod(Get);
}
// std::cout << "send!" << std::endl; // std::cout << "send!" << std::endl;
client->sendRequest( client->sendRequest(
request, request,
@ -284,7 +431,6 @@ void press::sendRequest(const HttpClientPtr &client)
void press::outputResults() void press::outputResults()
{ {
static std::mutex mtx;
size_t totalSent = 0; size_t totalSent = 0;
size_t totalRecv = 0; size_t totalRecv = 0;
for (auto &client : clients_) for (auto &client : clients_)
@ -296,7 +442,7 @@ void press::outputResults()
auto microSecs = now.microSecondsSinceEpoch() - auto microSecs = now.microSecondsSinceEpoch() -
statistics_.startDate_.microSecondsSinceEpoch(); statistics_.startDate_.microSecondsSinceEpoch();
double seconds = (double)microSecs / 1000000.0; double seconds = (double)microSecs / 1000000.0;
size_t rps = static_cast<size_t>(statistics_.numOfGoodResponse_ / seconds); auto rps = static_cast<size_t>(statistics_.numOfGoodResponse_ / seconds);
std::cout << std::endl; std::cout << std::endl;
std::cout << "TOTALS: " << numOfConnections_ << " connect, " std::cout << "TOTALS: " << numOfConnections_ << " connect, "
<< numOfRequests_ << " requests, " << numOfRequests_ << " requests, "

View File

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

View File

@ -39,7 +39,7 @@
"db_clients": [ "db_clients": [
{ {
//name: Name of the client,'default' by default //name: Name of the client,'default' by default
//"name":"", "name": "default",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default //rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
"rdbms": "postgresql", "rdbms": "postgresql",
//filename: Sqlite3 db file name //filename: Sqlite3 db file name
@ -66,15 +66,18 @@
//timeout: -1.0 by default, in seconds, the timeout for executing a SQL query. //timeout: -1.0 by default, in seconds, the timeout for executing a SQL query.
//zero or negative value means no timeout. //zero or negative value means no timeout.
"timeout": -1.0, "timeout": -1.0,
//"auto_batch": this feature is only available for the PostgreSQL driver(version >= 14.0), see //auto_batch: this feature is only available for the PostgreSQL driver(version >= 14.0), see
// the wiki for more details. //the wiki for more details.
"auto_batch": false "auto_batch": false
//connect_options: extra options for the connection. Only works for PostgreSQL now.
//For more information, see https://www.postgresql.org/docs/16/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
//"connect_options": { "statement_timeout": "1s" }
} }
], ],
"redis_clients": [ "redis_clients": [
{ {
//name: Name of the client,'default' by default //name: Name of the client,'default' by default
//"name":"", "name": "default",
//host: Server IP, 127.0.0.1 by default //host: Server IP, 127.0.0.1 by default
"host": "127.0.0.1", "host": "127.0.0.1",
//port: Server port, 6379 by default //port: Server port, 6379 by default
@ -103,10 +106,14 @@
//enable_session: False by default //enable_session: False by default
"enable_session": false, "enable_session": false,
"session_timeout": 0, "session_timeout": 0,
//string value of SameSite attribute of the Set-Cookie HTTP respone header //string value of SameSite attribute of the Set-Cookie HTTP response header
//valid value is either 'Null' (default), 'Lax', 'Strict' or 'None' //valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
"session_same_site" : "Null", "session_same_site" : "Null",
//document_root: Root path of HTTP document, defaut path is ./ //session_cookie_key: The cookie key of the session, "JSESSIONID" by default
"session_cookie_key": "JSESSIONID",
//session_max_age: The max age of the session cookie, -1 by default
"session_max_age": -1,
//document_root: Root path of HTTP document, default path is ./
"document_root": "./", "document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html" //home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response //If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
@ -119,10 +126,10 @@
"implicit_page": "index.html", "implicit_page": "index.html",
//static_file_headers: Headers for static files //static_file_headers: Headers for static files
/*"static_file_headers": [ /*"static_file_headers": [
{ {
"name": "field-name", "name": "field-name",
"value": "field-value" "value": "field-value"
} }
],*/ ],*/
//upload_path: The path to save the uploaded file. "uploads" by default. //upload_path: The path to save the uploaded file. "uploads" by default.
//If the path isn't prefixed with /, ./ or ../, //If the path isn't prefixed with /, ./ or ../,
@ -146,8 +153,8 @@
"apk", "apk",
"cur", "cur",
"xml", "xml",
"svg", "webp",
"webp" "svg"
], ],
// mime: A dictionary that extends the internal MIME type support. Maps extensions into new MIME types // mime: A dictionary that extends the internal MIME type support. Maps extensions into new MIME types
// note: This option only adds MIME to the sever. `file_types` above have to be set for the server to serve them. // note: This option only adds MIME to the sever. `file_types` above have to be set for the server to serve them.
@ -180,7 +187,7 @@
], ],
//max_connections: maximum number of connections, 100000 by default //max_connections: maximum number of connections, 100000 by default
"max_connections": 100000, "max_connections": 100000,
//max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit //max_connections_per_ip: maximum number of connections per client, 0 by default which means no limit
"max_connections_per_ip": 0, "max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon //Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined //compiles and loads dynamically "CSP View Files" in directories defined
@ -210,6 +217,8 @@
}, },
//log: Set log output, drogon output logs to stdout by default //log: Set log output, drogon output logs to stdout by default
"log": { "log": {
//use_spdlog: Use spdlog library to log
"use_spdlog": false,
//log_path: Log file path,empty by default,in which case,logs are output to the stdout //log_path: Log file path,empty by default,in which case,logs are output to the stdout
//"log_path": "./", //"log_path": "./",
//logfile_base_name: Log file base name,empty by default which means drogon names logfile as //logfile_base_name: Log file base name,empty by default which means drogon names logfile as
@ -244,19 +253,19 @@
//0 means cache forever, the negative value means no cache //0 means cache forever, the negative value means no cache
"static_files_cache_time": 5, "static_files_cache_time": 5,
//simple_controllers_map: Used to configure mapping from path to simple controller //simple_controllers_map: Used to configure mapping from path to simple controller
"simple_controllers_map": [ //"simple_controllers_map": [
{ // {
"path": "/path/name", // "path": "/path/name",
"controller": "controllerClassName", // "controller": "controllerClassName",
"http_methods": [ // "http_methods": [
"get", // "get",
"post" // "post"
], // ],
"filters": [ // "filters": [
"FilterClassName" // "FilterClassName"
] // ]
} // }
], //],
//idle_connection_timeout: Defaults to 60 seconds, the lifetime //idle_connection_timeout: Defaults to 60 seconds, the lifetime
//of the connection without read or write //of the connection without read or write
"idle_connection_timeout": 60, "idle_connection_timeout": 60,
@ -301,22 +310,38 @@
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests. // Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request // Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
// will be rejected. // will be rejected.
"enabled_compressed_request": false "enabled_compressed_request": false,
// enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
// See the wiki for more details.
"enable_request_stream": false,
}, },
//plugins: Define all plugins running in the application //plugins: Define all plugins running in the application
"plugins": [ "plugins": [
{ {
//name: The class name of the plugin //name: The class name of the plugin
//"name": "drogon::plugin::SecureSSLRedirector", "name": "drogon::plugin::PromExporter",
//dependencies: Plugins that the plugin depends on. It can be commented out //dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [], "dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin. //config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out //It can be commented out
"config": { "config": {
"ssl_redirect_exempt": [ "path": "/metrics"
".*\\.jpg" }
], },
"secure_ssl_host": "localhost:8849" {
"name": "drogon::plugin::AccessLogger",
"dependencies": [],
"config": {
"use_spdlog": false,
"log_path": "",
"log_format": "",
"log_file": "access.log",
"log_size_limit": 0,
"use_local_time": true,
"log_index": 0,
// "show_microseconds": true,
// "custom_time_format": "",
// "use_real_ip": false
} }
} }
], ],

View File

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

View File

@ -76,7 +76,7 @@ else
<%c++for(auto col:cols){ <%c++for(auto col:cols){
%> %>
const std::string [[className]]::Cols::_{%col.colName_%} = "{%col.colName_%}"; const std::string [[className]]::Cols::_{%col.colName_%} = "{%escapeIdentifier(col.colName_, rdbms)%}";
<%c++ <%c++
}%> }%>
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%> <%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
@ -102,7 +102,7 @@ if(!schema.empty())
{ {
$$<<schema<<"."; $$<<schema<<".";
} }
%>[[tableName]]"; %>{%escapeIdentifier(@@.get<std::string>("tableName"), rdbms)%}";
const std::vector<typename [[className]]::MetaData> [[className]]::metaData_={ const std::vector<typename [[className]]::MetaData> [[className]]::metaData_={
<%c++for(size_t i=0;i<cols.size();i++){ <%c++for(size_t i=0;i<cols.size();i++){
@ -975,7 +975,7 @@ void [[className]]::updateByJson(const Json::Value &pJson) noexcept(false)
{ {
$$<<"const "<<col.colType_<<" &"<<className<<"::getValueOf"<<col.colTypeName_<<"() const noexcept\n"; $$<<"const "<<col.colType_<<" &"<<className<<"::getValueOf"<<col.colTypeName_<<"() const noexcept\n";
$$<<"{\n"; $$<<"{\n";
$$<<" const static "<<col.colType_<<" defaultValue = "<<col.colType_<<"();\n"; $$<<" static const "<<col.colType_<<" defaultValue = "<<col.colType_<<"();\n";
$$<<" if("<<col.colValName_<<"_)\n"; $$<<" if("<<col.colValName_<<"_)\n";
$$<<" return *"<<col.colValName_<<"_;\n"; $$<<" return *"<<col.colValName_<<"_;\n";
$$<<" return defaultValue;\n"; $$<<" return defaultValue;\n";
@ -984,7 +984,7 @@ void [[className]]::updateByJson(const Json::Value &pJson) noexcept(false)
{ {
$$<<"std::string "<<className<<"::getValueOf"<<col.colTypeName_<<"AsString() const noexcept\n"; $$<<"std::string "<<className<<"::getValueOf"<<col.colTypeName_<<"AsString() const noexcept\n";
$$<<"{\n"; $$<<"{\n";
$$<<" const static std::string defaultValue = std::string();\n"; $$<<" static const std::string defaultValue = std::string();\n";
$$<<" if("<<col.colValName_<<"_)\n"; $$<<" if("<<col.colValName_<<"_)\n";
$$<<" return std::string("<<col.colValName_<<"_->data(),"<<col.colValName_<<"_->size());\n"; $$<<" return std::string("<<col.colValName_<<"_->data(),"<<col.colValName_<<"_->size());\n";
$$<<" return defaultValue;\n"; $$<<" return defaultValue;\n";
@ -1534,8 +1534,7 @@ if(!col.notNull_){%>
if(col.colType_ == "std::string" && col.colLength_>0) if(col.colType_ == "std::string" && col.colLength_>0)
{ {
%> %>
// asString().length() creates a string object, is there any better way to validate the length? if(pJson.isString() && std::strlen(pJson.asCString()) > {%col.colLength_%})
if(pJson.isString() && pJson.asString().length() > {%col.colLength_%})
{ {
err="String length exceeds limit for the " + err="String length exceeds limit for the " +
fieldName + fieldName +
@ -1568,64 +1567,54 @@ for(auto &relationship : relationships)
auto relationshipValName=nameTransform(name, false); auto relationshipValName=nameTransform(name, false);
auto alias=relationship.targetTableAlias(); auto alias=relationship.targetTableAlias();
auto aliasValName=nameTransform(alias, false); auto aliasValName=nameTransform(alias, false);
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
}
else
{
alias = relationshipClassName;
}
std::string alind(alias.length(), ' ');
if(relationship.type() == Relationship::Type::HasOne) if(relationship.type() == Relationship::Type::HasOne)
{ {
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
std::string alind(alias.length(), ' ');
%> %>
{%relationshipClassName%} [[className]]::get{%alias%}(const DbClientPtr &clientPtr) const {
{%relationshipClassName%} [[className]]::get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const { static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
std::shared_ptr<std::promise<{%relationshipClassName%}>> pro(new std::promise<{%relationshipClassName%}>); if(rdbms=="postgresql")
std::future<{%relationshipClassName%}> f = pro->get_future(); {
get{%alias%}(clientPtr, [&pro] ({%relationshipClassName%} result) { $$<<"$1";
try {
pro->set_value(result);
}
catch (...) {
pro->set_exception(std::current_exception());
}
}, [&pro] (const DrogonDbException &err) {
pro->set_exception(std::make_exception_ptr(err));
});
return f.get();
}
void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%alind%} const std::function<void({%relationshipClassName%})> &rcb,
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
<%c++
} }
else else
{ {
std::string relationshipClassInde(relationshipClassName.length(), ' '); $$<<"?";
%> }%>";
{%relationshipClassName%} [[className]]::get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const { Result r(nullptr);
std::shared_ptr<std::promise<{%relationshipClassName%}>> pro(new std::promise<{%relationshipClassName%}>); {
std::future<{%relationshipClassName%}> f = pro->get_future(); auto binder = *clientPtr << sql;
get{%relationshipClassName%}(clientPtr, [&pro] ({%relationshipClassName%} result) { binder << *{%nameTransform(relationship.originalKey(), false)%}_ << Mode::Blocking >>
try { [&r](const Result &result) { r = result; };
pro->set_value(result); binder.exec();
} }
catch (...) { if (r.size() == 0)
pro->set_exception(std::current_exception()); {
} throw UnexpectedRows("0 rows found");
}, [&pro] (const DrogonDbException &err) { }
pro->set_exception(std::make_exception_ptr(err)); else if (r.size() > 1)
}); {
return f.get(); throw UnexpectedRows("Found more than one row");
}
return {%relationshipClassName%}(r[0]);
} }
void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
{%indentStr%} {%relationshipClassInde%} const std::function<void({%relationshipClassName%})> &rcb, void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%relationshipClassInde%} const ExceptionCallback &ecb) const {%indentStr%} {%alind%} const std::function<void({%relationshipClassName%})> &rcb,
<%c++ {%indentStr%} {%alind%} const ExceptionCallback &ecb) const
}
%>
{ {
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++ static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
if(rdbms=="postgresql") if(rdbms=="postgresql")
{ {
$$<<"$1"; $$<<"$1";
@ -1656,61 +1645,38 @@ void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
} }
else if(relationship.type() == Relationship::Type::HasMany) else if(relationship.type() == Relationship::Type::HasMany)
{ {
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
std::string alind(alias.length(), ' ');
%> %>
std::vector<{%relationshipClassName%}> [[className]]::get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const { std::vector<{%relationshipClassName%}> [[className]]::get{%alias%}(const DbClientPtr &clientPtr) const {
std::shared_ptr<std::promise<std::vector<{%relationshipClassName%}>>> pro(new std::promise<std::vector<{%relationshipClassName%}>>); static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
std::future<std::vector<{%relationshipClassName%}>> f = pro->get_future(); if(rdbms=="postgresql")
get{%alias%}(clientPtr, [&pro] (std::vector<{%relationshipClassName%}> result) { {
try { $$<<"$1";
pro->set_value(result);
}
catch (...) {
pro->set_exception(std::current_exception());
}
}, [&pro] (const DrogonDbException &err) {
pro->set_exception(std::make_exception_ptr(err));
});
return f.get();
}
void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
<%c++
} }
else else
{ {
std::string relationshipClassInde(relationshipClassName.length(), ' '); $$<<"?";
%> }%>";
std::vector<{%relationshipClassName%}> [[className]]::get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const { Result r(nullptr);
std::shared_ptr<std::promise<std::vector<{%relationshipClassName%}>>> pro(new std::promise<std::vector<{%relationshipClassName%}>>); {
std::future<std::vector<{%relationshipClassName%}>> f = pro->get_future(); auto binder = *clientPtr << sql;
get{%relationshipClassName%}(clientPtr, [&pro] (std::vector<{%relationshipClassName%}> result) { binder << *{%nameTransform(relationship.originalKey(), false)%}_ << Mode::Blocking >>
try { [&r](const Result &result) { r = result; };
pro->set_value(result); binder.exec();
} }
catch (...) { std::vector<{%relationshipClassName%}> ret;
pro->set_exception(std::current_exception()); ret.reserve(r.size());
} for (auto const &row : r)
}, [&pro] (const DrogonDbException &err) { {
pro->set_exception(std::make_exception_ptr(err)); ret.emplace_back({%relationshipClassName%}(row));
}); }
return f.get(); return ret;
} }
void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
{%indentStr%} {%relationshipClassInde%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb, void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%relationshipClassInde%} const ExceptionCallback &ecb) const {%indentStr%} {%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
<%c++ {%indentStr%} {%alind%} const ExceptionCallback &ecb) const
}
%>
{ {
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++ static const std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
if(rdbms=="postgresql") if(rdbms=="postgresql")
{ {
$$<<"$1"; $$<<"$1";
@ -1740,61 +1706,39 @@ void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
auto pivotTableClassName=nameTransform(pivotTableName, true); auto pivotTableClassName=nameTransform(pivotTableName, true);
auto &pivotOriginalKey=relationship.pivotTable().originalKey(); auto &pivotOriginalKey=relationship.pivotTable().originalKey();
auto &pivotTargetKey=relationship.pivotTable().targetKey(); auto &pivotTargetKey=relationship.pivotTable().targetKey();
if(!alias.empty())
{
if(alias[0] <= 'z' && alias[0] >= 'a')
{
alias[0] += ('A' - 'a');
}
std::string alind(alias.length(), ' ');
%> %>
std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> [[className]]::get{%alias%}(const drogon::orm::DbClientPtr &clientPtr) const { std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> [[className]]::get{%alias%}(const DbClientPtr &clientPtr) const {
std::shared_ptr<std::promise<std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>>> pro(new std::promise<std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>>); static const std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
std::future<std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>> f = pro->get_future(); if(rdbms=="postgresql")
get{%alias%}(clientPtr, [&pro] (std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> result) { {
try { $$<<"$1";
pro->set_value(result);
}
catch (...) {
pro->set_exception(std::current_exception());
}
}, [&pro] (const DrogonDbException &err) {
pro->set_exception(std::make_exception_ptr(err));
});
return f.get();
}
void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
<%c++
} }
else else
{ {
std::string relationshipClassInde(relationshipClassName.length(), ' '); $$<<"?";
%> }%> and {%pivotTableName%}.{%pivotTargetKey%} = {%name%}.{%relationship.targetKey()%}";
std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> [[className]]::get{%relationshipClassName%}(const drogon::orm::DbClientPtr &clientPtr) const { Result r(nullptr);
std::shared_ptr<std::promise<std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>>> pro(new std::promise<std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>>); {
std::future<std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>> f = pro->get_future(); auto binder = *clientPtr << sql;
get{%relationshipClassName%}(clientPtr, [&pro] (std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> result) { binder << *{%nameTransform(relationship.originalKey(), false)%}_ << Mode::Blocking >>
try { [&r](const Result &result) { r = result; };
pro->set_value(result); binder.exec();
} }
catch (...) { std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> ret;
pro->set_exception(std::current_exception()); ret.reserve(r.size());
} for (auto const &row : r)
}, [&pro] (const DrogonDbException &err) { {
pro->set_exception(std::make_exception_ptr(err)); ret.emplace_back(std::pair<{%relationshipClassName%},{%pivotTableClassName%}>(
}); {%relationshipClassName%}(row),{%pivotTableClassName%}(row,{%relationshipClassName%}::getColumnNumber())));
return f.get(); }
return ret;
} }
void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
{%indentStr%} {%relationshipClassInde%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb, void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
{%indentStr%} {%relationshipClassInde%} const ExceptionCallback &ecb) const {%indentStr%} {%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
<%c++ {%indentStr%} {%alind%} const ExceptionCallback &ecb) const
}
%>
{ {
const static std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++ static const std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
if(rdbms=="postgresql") if(rdbms=="postgresql")
{ {
$$<<"$1"; $$<<"$1";

View File

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

View File

@ -6,9 +6,9 @@ add_executable(${PROJECT_NAME} test_main.cc)
# ############################################################################## # ##############################################################################
# If you include the drogon source code locally in your project, use this method # If you include the drogon source code locally in your project, use this method
# to add drogon # to add drogon
# target_link_libraries(${PROJECT_NAME}_test PRIVATE drogon) # target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
# #
# and comment out the following lines # and comment out the following lines
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon) target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
ParseAndAddDrogonTests(${PROJECT_NAME}) ParseAndAddDrogonTests(${PROJECT_NAME})

View File

@ -31,6 +31,11 @@ add_executable(redis_simple redis/main.cc
add_executable(redis_chat redis_chat/main.cc add_executable(redis_chat redis_chat/main.cc
redis_chat/controllers/Chat.cc) redis_chat/controllers/Chat.cc)
add_executable(async_stream async_stream/main.cc
async_stream/RequestStreamExampleCtrl.cc)
add_executable(cors cors/main.cc)
set(example_targets set(example_targets
benchmark benchmark
client client
@ -41,11 +46,13 @@ set(example_targets
login_session login_session
jsonstore jsonstore
redis_simple redis_simple
redis_chat) redis_chat
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.
if (CMAKE_CXX_COMPILER_ID MATCHES GNU) if(NOT ${CMAKE_PLATFORM_NAME} STREQUAL "Windows" AND CMAKE_CXX_COMPILER_ID MATCHES GNU)
foreach(target ${example_targets}) foreach(target ${example_targets})
target_compile_options(${target} PRIVATE -Wall -Wextra -Werror) target_compile_options(${target} PRIVATE -Wall -Wextra -Werror)
endforeach() endforeach()

View File

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

View File

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

View File

@ -0,0 +1,113 @@
#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 &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 =
std::shared_ptr<drogon::ResponseStream>{
std::move(stream)}]() mutable {
std::cout << std::boolalpha << stream->send("hello ")
<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << std::boolalpha << stream->send("world");
std::this_thread::sleep_for(std::chrono::seconds(2));
stream->close();
}).detach();
});
resp->setContentTypeCodeAndCustomString(
ContentType::CT_APPLICATION_JSON, "application/json");
callback(resp);
});
// Example: register a stream-mode function handler
app().registerHandler(
"/stream_req",
[](const HttpRequestPtr &req,
RequestStreamPtr &&stream,
std::function<void(const HttpResponsePtr &)> &&callback) {
if (!stream)
{
LOG_INFO << "stream mode is not enabled";
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
resp->setBody("no stream");
callback(resp);
return;
}
auto reader = RequestStreamReader::newReader(
[](const char *data, size_t length) {
LOG_INFO << "piece[" << length
<< "]: " << std::string_view{data, length};
},
[callback = std::move(callback)](std::exception_ptr ex) {
auto resp = HttpResponse::newHttpResponse();
if (ex)
{
try
{
std::rethrow_exception(std::move(ex));
}
catch (const std::exception &e)
{
LOG_ERROR << "stream error: " << e.what();
}
resp->setStatusCode(k400BadRequest);
resp->setBody("error\n");
callback(resp);
}
else
{
LOG_INFO << "stream finish";
resp->setBody("success\n");
callback(resp);
}
});
stream->setStreamReader(std::move(reader));
},
{Post});
LOG_INFO << "Server running on 127.0.0.1:8848";
app().enableRequestStream(); // This is for request stream.
app().setConnectionCallback([](const trantor::TcpConnectionPtr &conn) {
if (conn->disconnected())
{
std::lock_guard lk(mutex);
if (auto it = connMapping.find(conn); it != connMapping.end())
{
LOG_INFO << "disconnect";
connMapping[conn]();
connMapping.erase(conn);
}
}
});
app().addListener("127.0.0.1", 8848).run();
}

View File

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

View File

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

View File

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

View File

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

@ -43,4 +43,4 @@ int main()
.setUploadPath("./uploads") .setUploadPath("./uploads")
.addListener("127.0.0.1", 8848) .addListener("127.0.0.1", 8848)
.run(); .run();
} }

View File

@ -37,4 +37,4 @@ class SayHello : public HttpController<SayHello>
"Hi there, this is another hello from the SayHello Controller"); "Hi there, this is another hello from the SayHello Controller");
callback(resp); callback(resp);
} }
}; };

View File

@ -26,4 +26,4 @@ class HelloViewController : public HttpSimpleController<HelloViewController>
auto resp = HttpResponse::newHttpViewResponse("HelloView", data); auto resp = HttpResponse::newHttpViewResponse("HelloView", data);
callback(resp); callback(resp);
} }
}; };

View File

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

View File

@ -24,7 +24,7 @@ Create a new JSON object associated with the token
* **method**: POST * **method**: POST
* **URL params**: None * **URL params**: None
* **Body**: The inital JSON object to store * **Body**: The initial JSON object to store
* **Success response** * **Success response**
* **Code**: 200 * **Code**: 200
* **Content**: `{"ok":true}` * **Content**: `{"ok":true}`
@ -121,7 +121,7 @@ Creating new data
> {"ok":true} > {"ok":true}
Retrieving value of data["foo"]["bar"] Retrieving value of data["foo"]["bar"]
> 42 > 42
Modifing data Modifying data
> {"ok":true} > {"ok":true}
Now data["foo"]["bar"] no longer exists Now data["foo"]["bar"] no longer exists
> {"ok":false} > {"ok":false}

View File

@ -50,8 +50,8 @@ class JsonStore : public HttpController<JsonStore>
ADD_METHOD_VIA_REGEX(JsonStore::updateItem, "/([a-f0-9]{64})/(.*)", Put); ADD_METHOD_VIA_REGEX(JsonStore::updateItem, "/([a-f0-9]{64})/(.*)", Put);
METHOD_LIST_END METHOD_LIST_END
void getToken(const HttpRequestPtr&, void getToken(const HttpRequestPtr &,
std::function<void(const HttpResponsePtr&)>&& callback) std::function<void(const HttpResponsePtr &)> &&callback)
{ {
std::string randomString = getRandomString(64); std::string randomString = getRandomString(64);
Json::Value res; Json::Value res;
@ -60,10 +60,10 @@ class JsonStore : public HttpController<JsonStore>
callback(HttpResponse::newHttpJsonResponse(std::move(res))); callback(HttpResponse::newHttpJsonResponse(std::move(res)));
} }
void getItem(const HttpRequestPtr&, void getItem(const HttpRequestPtr &,
std::function<void(const HttpResponsePtr&)>&& callback, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& token, const std::string &token,
const std::string& path) const std::string &path)
{ {
auto itemPtr = [this, &token]() -> std::shared_ptr<DataItem> { auto itemPtr = [this, &token]() -> std::shared_ptr<DataItem> {
// It is possible that the item is being removed while another // It is possible that the item is being removed while another
@ -81,12 +81,12 @@ class JsonStore : public HttpController<JsonStore>
return; return;
} }
auto& item = *itemPtr; auto &item = *itemPtr;
// Prevents another thread from writing to the same item while this // Prevents another thread from writing to the same item while this
// thread reads. Could cause blockage if multiple clients are asking to // thread reads. Could cause blockage if multiple clients are asking to
// read the same object. But that should be rare. // read the same object. But that should be rare.
std::lock_guard<std::mutex> lock(item.mtx); std::lock_guard<std::mutex> lock(item.mtx);
Json::Value* valuePtr = walkJson(item.item, path); Json::Value *valuePtr = walkJson(item.item, path);
if (valuePtr == nullptr) if (valuePtr == nullptr)
{ {
@ -98,10 +98,10 @@ class JsonStore : public HttpController<JsonStore>
callback(resp); callback(resp);
} }
void updateItem(const HttpRequestPtr& req, void updateItem(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr&)>&& callback, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& token, const std::string &token,
const std::string& path) const std::string &path)
{ {
auto jsonPtr = req->jsonObject(); auto jsonPtr = req->jsonObject();
auto itemPtr = [this, &token]() -> std::shared_ptr<DataItem> { auto itemPtr = [this, &token]() -> std::shared_ptr<DataItem> {
@ -121,9 +121,9 @@ class JsonStore : public HttpController<JsonStore>
return; return;
} }
auto& item = *itemPtr; auto &item = *itemPtr;
std::lock_guard<std::mutex> lock(item.mtx); std::lock_guard<std::mutex> lock(item.mtx);
Json::Value* valuePtr = walkJson(item.item, path, 1); Json::Value *valuePtr = walkJson(item.item, path, 1);
if (valuePtr == nullptr) if (valuePtr == nullptr)
{ {
@ -137,9 +137,9 @@ class JsonStore : public HttpController<JsonStore>
callback(makeSuccessResponse()); callback(makeSuccessResponse());
} }
void createItem(const HttpRequestPtr& req, void createItem(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr&)>&& callback, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& token) const std::string &token)
{ {
auto jsonPtr = req->jsonObject(); auto jsonPtr = req->jsonObject();
if (jsonPtr == nullptr) if (jsonPtr == nullptr)
@ -163,9 +163,9 @@ class JsonStore : public HttpController<JsonStore>
} }
} }
void deleteItem(const HttpRequestPtr&, void deleteItem(const HttpRequestPtr &,
std::function<void(const HttpResponsePtr&)>&& callback, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& token) const std::string &token)
{ {
std::lock_guard<std::mutex> lock(storageMtx_); std::lock_guard<std::mutex> lock(storageMtx_);
dataStore_.erase(token); dataStore_.erase(token);
@ -174,19 +174,19 @@ class JsonStore : public HttpController<JsonStore>
} }
protected: protected:
static Json::Value* walkJson(Json::Value& json, static Json::Value *walkJson(Json::Value &json,
const std::string& path, const std::string &path,
size_t ignore_back = 0) size_t ignore_back = 0)
{ {
auto pathElem = utils::splitString(path, "/", false); auto pathElem = utils::splitString(path, "/", false);
if (pathElem.size() >= ignore_back) if (pathElem.size() >= ignore_back)
pathElem.resize(pathElem.size() - ignore_back); pathElem.resize(pathElem.size() - ignore_back);
Json::Value* valuePtr = &json; Json::Value *valuePtr = &json;
for (const auto& elem : pathElem) for (const auto &elem : pathElem)
{ {
if (valuePtr->isArray()) if (valuePtr->isArray())
{ {
Json::Value& value = (*valuePtr)[std::stoi(elem)]; Json::Value &value = (*valuePtr)[std::stoi(elem)];
if (value.isNull()) if (value.isNull())
return nullptr; return nullptr;
@ -194,7 +194,7 @@ class JsonStore : public HttpController<JsonStore>
} }
else else
{ {
Json::Value& value = (*valuePtr)[elem]; Json::Value &value = (*valuePtr)[elem];
if (value.isNull()) if (value.isNull())
return nullptr; return nullptr;
@ -211,4 +211,4 @@ class JsonStore : public HttpController<JsonStore>
int main() int main()
{ {
app().addListener("127.0.0.1", 8848).run(); app().addListener("127.0.0.1", 8848).run();
} }

View File

@ -65,4 +65,4 @@ int main()
.enableSession(24h) .enableSession(24h)
.addListener("127.0.0.1", 8848) .addListener("127.0.0.1", 8848)
.run(); .run();
} }

561
examples/prometheus_example/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,9 +9,9 @@ struct ClientContext
std::shared_ptr<nosql::RedisSubscriber> subscriber_; std::shared_ptr<nosql::RedisSubscriber> subscriber_;
}; };
void WsClient::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr, void WsClient::handleNewMessage(const WebSocketConnectionPtr &wsConnPtr,
std::string&& message, std::string &&message,
const WebSocketMessageType& type) const WebSocketMessageType &type)
{ {
if (type == WebSocketMessageType::Ping || if (type == WebSocketMessageType::Ping ||
type == WebSocketMessageType::Pong || type == WebSocketMessageType::Pong ||
@ -44,13 +44,13 @@ void WsClient::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
// Publisher // Publisher
drogon::app().getRedisClient()->execCommandAsync( drogon::app().getRedisClient()->execCommandAsync(
[wsConnPtr](const nosql::RedisResult& result) { [wsConnPtr](const nosql::RedisResult &result) {
std::string nSubs = std::to_string(result.asInteger()); std::string nSubs = std::to_string(result.asInteger());
LOG_INFO << "PUBLISH success to " << nSubs << " subscribers."; LOG_INFO << "PUBLISH success to " << nSubs << " subscribers.";
wsConnPtr->send("PUBLISH success to " + nSubs + wsConnPtr->send("PUBLISH success to " + nSubs +
" subscribers."); " subscribers.");
}, },
[wsConnPtr](const nosql::RedisException& ex) { [wsConnPtr](const nosql::RedisException &ex) {
LOG_INFO << "PUBLISH failed, " << ex.what(); LOG_INFO << "PUBLISH failed, " << ex.what();
wsConnPtr->send(std::string("PUBLISH failed: ") + ex.what()); wsConnPtr->send(std::string("PUBLISH failed: ") + ex.what());
}, },
@ -84,8 +84,8 @@ void WsClient::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
context->subscriber_->subscribe( context->subscriber_->subscribe(
channel, channel,
[channel, wsConnPtr](const std::string& subChannel, [channel, wsConnPtr](const std::string &subChannel,
const std::string& subMessage) { const std::string &subMessage) {
assert(subChannel == channel); assert(subChannel == channel);
LOG_INFO << "Receive channel message " << subMessage; LOG_INFO << "Receive channel message " << subMessage;
std::string resp = "{\"channel\":\"" + subChannel + std::string resp = "{\"channel\":\"" + subChannel +
@ -109,8 +109,8 @@ void WsClient::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
} }
} }
void WsClient::handleNewConnection(const HttpRequestPtr& req, void WsClient::handleNewConnection(const HttpRequestPtr &req,
const WebSocketConnectionPtr& wsConnPtr) const WebSocketConnectionPtr &wsConnPtr)
{ {
if (req->getPath() == "/sub") if (req->getPath() == "/sub")
{ {
@ -128,7 +128,7 @@ void WsClient::handleNewConnection(const HttpRequestPtr& req,
} }
} }
void WsClient::handleConnectionClosed(const WebSocketConnectionPtr& wsConnPtr) void WsClient::handleConnectionClosed(const WebSocketConnectionPtr &wsConnPtr)
{ {
LOG_DEBUG << "WsClient close connection from " LOG_DEBUG << "WsClient close connection from "
<< wsConnPtr->peerAddr().toIpPort(); << wsConnPtr->peerAddr().toIpPort();

View File

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

View File

@ -11,4 +11,4 @@ template <>
inline std::string toString<trantor::Date>(const trantor::Date &date) inline std::string toString<trantor::Date>(const trantor::Date &date)
{ {
return std::to_string(date.microSecondsSinceEpoch()); return std::to_string(date.microSecondsSinceEpoch());
} }

View File

@ -41,7 +41,11 @@
//enable_session: False by default //enable_session: False by default
"enable_session": false, "enable_session": false,
"session_timeout": 0, "session_timeout": 0,
//document_root: Root path of HTTP document, defaut path is ./ //session_cookie_key: The cookie key of the session, "JSESSIONID" by default
"session_cookie_key": "JSESSIONID",
//session_max_age: The max age of the session cookie, -1 by default
"session_max_age": -1,
//document_root: Root path of HTTP document, default path is ./
"document_root": "./", "document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html" //home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response //If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
@ -107,7 +111,7 @@
], ],
//max_connections: maximum number of connections, 100000 by default //max_connections: maximum number of connections, 100000 by default
"max_connections": 100000, "max_connections": 100000,
//max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit //max_connections_per_ip: maximum number of connections per client, 0 by default which means no limit
"max_connections_per_ip": 0, "max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon //Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined //compiles and loads dynamically "CSP View Files" in directories defined

View File

@ -70,4 +70,4 @@ void TimeFilter::doFilter(const HttpRequestPtr &req,
co_return; co_return;
} }
}); });
} }

View File

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

View File

@ -7,7 +7,7 @@ DROGON_TEST(BasicTest)
// Add your tests here // Add your tests here
} }
int main(int argc, char** argv) int main(int argc, char **argv)
{ {
using namespace drogon; using namespace drogon;

View File

@ -3,11 +3,11 @@
#include <memory> #include <memory>
#include <unordered_set> #include <unordered_set>
static void redisLogin(std::function<void(int)>&& callback, static void redisLogin(std::function<void(int)> &&callback,
const std::string& loginKey, const std::string &loginKey,
unsigned int timeout); unsigned int timeout);
static void redisLogout(std::function<void(int)>&& callback, static void redisLogout(std::function<void(int)> &&callback,
const std::string& loginKey); const std::string &loginKey);
struct ClientContext struct ClientContext
{ {
@ -17,7 +17,7 @@ struct ClientContext
std::shared_ptr<nosql::RedisSubscriber> subscriber_; std::shared_ptr<nosql::RedisSubscriber> subscriber_;
}; };
static bool checkRoomNumber(const std::string& room) static bool checkRoomNumber(const std::string &room)
{ {
if (room.empty() || room.size() > 2 || (room.size() == 2 && room[0] == '0')) if (room.empty() || room.size() > 2 || (room.size() == 2 && room[0] == '0'))
{ {
@ -33,9 +33,9 @@ static bool checkRoomNumber(const std::string& room)
return true; return true;
} }
void Chat::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr, void Chat::handleNewMessage(const WebSocketConnectionPtr &wsConnPtr,
std::string&& message, std::string &&message,
const WebSocketMessageType& type) const WebSocketMessageType &type)
{ {
if (type == WebSocketMessageType::Close || if (type == WebSocketMessageType::Close ||
type == WebSocketMessageType::Ping) type == WebSocketMessageType::Ping)
@ -63,10 +63,10 @@ void Chat::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
if (type == WebSocketMessageType::Pong) if (type == WebSocketMessageType::Pong)
{ {
redisClient->execCommandAsync( redisClient->execCommandAsync(
[wsConnPtr](const nosql::RedisResult&) { [wsConnPtr](const nosql::RedisResult &) {
// Do nothing // Do nothing
}, },
[wsConnPtr](const nosql::RedisException& ex) { [wsConnPtr](const nosql::RedisException &ex) {
LOG_ERROR << "Update user status failed: " << ex.what(); LOG_ERROR << "Update user status failed: " << ex.what();
wsConnPtr->send("ERROR: Service unavailable."); wsConnPtr->send("ERROR: Service unavailable.");
wsConnPtr->forceClose(); wsConnPtr->forceClose();
@ -111,8 +111,8 @@ void Chat::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
// NOTICE: Dangerous to concat username into redis command!!! // NOTICE: Dangerous to concat username into redis command!!!
// Do not use in production. // Do not use in production.
redisClient->execCommandAsync( redisClient->execCommandAsync(
[](const nosql::RedisResult&) {}, [](const nosql::RedisResult &) {},
[wsConnPtr](const nosql::RedisException& ex) { [wsConnPtr](const nosql::RedisException &ex) {
wsConnPtr->send(std::string("ERROR: ") + ex.what()); wsConnPtr->send(std::string("ERROR: ") + ex.what());
}, },
"publish %s %s", "publish %s %s",
@ -133,7 +133,7 @@ void Chat::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
} }
wsConnPtr->send("INFO: Enter room " + room); wsConnPtr->send("INFO: Enter room " + room);
context->subscriber_->subscribe( context->subscriber_->subscribe(
room, [wsConnPtr](const std::string&, const std::string& msg) { room, [wsConnPtr](const std::string &, const std::string &msg) {
wsConnPtr->send(msg); wsConnPtr->send(msg);
}); });
context->room_ = room; context->room_ = room;
@ -160,8 +160,8 @@ void Chat::handleNewMessage(const WebSocketConnectionPtr& wsConnPtr,
} }
} }
void Chat::handleNewConnection(const HttpRequestPtr& req, void Chat::handleNewConnection(const HttpRequestPtr &req,
const WebSocketConnectionPtr& wsConnPtr) const WebSocketConnectionPtr &wsConnPtr)
{ {
LOG_DEBUG << "WsClient new connection from " LOG_DEBUG << "WsClient new connection from "
<< wsConnPtr->peerAddr().toIpPort(); << wsConnPtr->peerAddr().toIpPort();
@ -201,7 +201,7 @@ void Chat::handleNewConnection(const HttpRequestPtr& req,
120); 120);
} }
void Chat::handleConnectionClosed(const WebSocketConnectionPtr& wsConnPtr) void Chat::handleConnectionClosed(const WebSocketConnectionPtr &wsConnPtr)
{ {
LOG_DEBUG << "WsClient close connection from " LOG_DEBUG << "WsClient close connection from "
<< wsConnPtr->peerAddr().toIpPort(); << wsConnPtr->peerAddr().toIpPort();
@ -217,8 +217,8 @@ void Chat::handleConnectionClosed(const WebSocketConnectionPtr& wsConnPtr)
} }
} }
static void redisLogin(std::function<void(int)>&& callback, static void redisLogin(std::function<void(int)> &&callback,
const std::string& loginKey, const std::string &loginKey,
unsigned int timeout) unsigned int timeout)
{ {
static const char script[] = R"( static const char script[] = R"(
@ -229,10 +229,10 @@ return 1;
)"; )";
drogon::app().getRedisClient()->execCommandAsync( drogon::app().getRedisClient()->execCommandAsync(
[callback](const nosql::RedisResult& result) { [callback](const nosql::RedisResult &result) {
callback((int)result.asInteger()); callback((int)result.asInteger());
}, },
[callback](const nosql::RedisException& ex) { [callback](const nosql::RedisException &ex) {
LOG_ERROR << "Login error: " << ex.what(); LOG_ERROR << "Login error: " << ex.what();
callback(-1); callback(-1);
}, },
@ -242,14 +242,14 @@ return 1;
timeout); timeout);
} }
static void redisLogout(std::function<void(int)>&& callback, static void redisLogout(std::function<void(int)> &&callback,
const std::string& loginKey) const std::string &loginKey)
{ {
drogon::app().getRedisClient()->execCommandAsync( drogon::app().getRedisClient()->execCommandAsync(
[callback](const nosql::RedisResult& result) { [callback](const nosql::RedisResult &result) {
callback((int)result.asInteger()); callback((int)result.asInteger());
}, },
[callback](const nosql::RedisException& ex) { [callback](const nosql::RedisException &ex) {
LOG_ERROR << "Logout error: " << ex.what(); LOG_ERROR << "Logout error: " << ex.what();
callback(-1); callback(-1);
}, },

View File

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

View File

@ -22,7 +22,11 @@
//enable_session: False by default //enable_session: False by default
"enable_session": false, "enable_session": false,
"session_timeout": 0, "session_timeout": 0,
//document_root: Root path of HTTP document, defaut path is ./ //session_cookie_key: The cookie key of the session, "JSESSIONID" by default
"session_cookie_key": "JSESSIONID",
//session_max_age: The max age of the session cookie, -1 by default
"session_max_age": -1,
//document_root: Root path of HTTP document, default path is ./
"document_root": "./", "document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html" //home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response //If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
@ -60,7 +64,7 @@
], ],
//max_connections: maximum connections number, 100000 by default //max_connections: maximum connections number, 100000 by default
"max_connections": 100000, "max_connections": 100000,
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit //max_connections_per_ip: maximum connections number per client,0 by default which means no limit
"max_connections_per_ip": 0, "max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon //Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined //compiles and loads dynamically "CSP View Files" in directories defined

View File

@ -97,4 +97,4 @@ void SimpleReverseProxy::preRouting(const HttpRequestPtr &req,
callback(errResp); callback(errResp);
} }
}); });
} }

View File

@ -14,7 +14,7 @@ int main(int argc, char *argv[])
// Connect to a public echo server // Connect to a public echo server
if (argc > 1 && std::string(argv[1]) == "-p") if (argc > 1 && std::string(argv[1]) == "-p")
{ {
server = "wss://echo.websocket.org"; server = "wss://echo.websocket.events/.ws";
path = "/"; path = "/";
} }
else else

View File

@ -6,15 +6,15 @@ using namespace drogon;
class WebSocketChat : public drogon::WebSocketController<WebSocketChat> class WebSocketChat : public drogon::WebSocketController<WebSocketChat>
{ {
public: public:
virtual void handleNewMessage(const WebSocketConnectionPtr &, void handleNewMessage(const WebSocketConnectionPtr &,
std::string &&, std::string &&,
const WebSocketMessageType &) override; const WebSocketMessageType &) override;
virtual void handleConnectionClosed( void handleConnectionClosed(const WebSocketConnectionPtr &) override;
const WebSocketConnectionPtr &) override; void handleNewConnection(const HttpRequestPtr &,
virtual void handleNewConnection(const HttpRequestPtr &, const WebSocketConnectionPtr &) override;
const WebSocketConnectionPtr &) override;
WS_PATH_LIST_BEGIN WS_PATH_LIST_BEGIN
WS_PATH_ADD("/chat", Get); WS_PATH_ADD("/chat", Get);
WS_ADD_PATH_VIA_REGEX("/[^/]*", Get);
WS_PATH_LIST_END WS_PATH_LIST_END
private: private:
PubSubService<std::string> chatRooms_; PubSubService<std::string> chatRooms_;
@ -60,7 +60,7 @@ void WebSocketChat::handleNewConnection(const HttpRequestPtr &req,
s.id_ = chatRooms_.subscribe(s.chatRoomName_, s.id_ = chatRooms_.subscribe(s.chatRoomName_,
[conn](const std::string &topic, [conn](const std::string &topic,
const std::string &message) { const std::string &message) {
// Supress unused variable warning // Suppress unused variable warning
(void)topic; (void)topic;
conn->send(message); conn->send(message);
}); });
@ -70,4 +70,4 @@ void WebSocketChat::handleNewConnection(const HttpRequestPtr &req,
int main() int main()
{ {
app().addListener("127.0.0.1", 8848).run(); app().addListener("127.0.0.1", 8848).run();
} }

View File

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

View File

@ -39,7 +39,7 @@ class Attributes
template <typename T> template <typename T>
const T &get(const std::string &key) const const T &get(const std::string &key) const
{ {
const static T nullVal = T(); static const T nullVal = T();
auto it = attributesMap_.find(key); auto it = attributesMap_.find(key);
if (it != attributesMap_.end()) if (it != attributesMap_.end())
{ {
@ -96,7 +96,7 @@ class Attributes
} }
/** /**
* @brief Retrun true if the data identified by the key exists. * @brief Return true if the data identified by the key exists.
*/ */
bool find(const std::string &key) bool find(const std::string &key)
{ {

View File

@ -81,7 +81,11 @@ class CacheMap
* number of wheels * number of wheels
* @param bucketsNumPerWheel * @param bucketsNumPerWheel
* buckets number per wheel * buckets number per wheel
* The max delay of the CacheMap is about * @param fnOnInsert
* function to execute on insertion
* @param fnOnErase
* function to execute on erase
* @details The max delay of the CacheMap is about
* tickInterval*(bucketsNumPerWheel^wheelsNum) seconds. * tickInterval*(bucketsNumPerWheel^wheelsNum) seconds.
*/ */
CacheMap(trantor::EventLoop *loop, CacheMap(trantor::EventLoop *loop,

View File

@ -36,12 +36,7 @@ class DROGON_EXPORT Cookie
* @param key key of the cookie * @param key key of the cookie
* @param value value of the cookie * @param value value of the cookie
*/ */
Cookie(const std::string &key, const std::string &value) Cookie(std::string key, std::string value)
: key_(key), value_(value)
{
}
Cookie(std::string &&key, std::string &&value)
: key_(std::move(key)), value_(std::move(value)) : key_(std::move(key)), value_(std::move(value))
{ {
} }
@ -89,6 +84,9 @@ class DROGON_EXPORT Cookie
domain_ = domain; domain_ = domain;
} }
/**
* @brief Set the domain of the cookie.
*/
void setDomain(std::string &&domain) void setDomain(std::string &&domain)
{ {
domain_ = std::move(domain); domain_ = std::move(domain);
@ -102,6 +100,9 @@ class DROGON_EXPORT Cookie
path_ = path; path_ = path;
} }
/**
* @brief Set the path of the cookie.
*/
void setPath(std::string &&path) void setPath(std::string &&path)
{ {
path_ = std::move(path); path_ = std::move(path);
@ -115,6 +116,9 @@ class DROGON_EXPORT Cookie
key_ = key; key_ = key;
} }
/**
* @brief Set the key of the cookie.
*/
void setKey(std::string &&key) void setKey(std::string &&key)
{ {
key_ = std::move(key); key_ = std::move(key);
@ -128,6 +132,9 @@ class DROGON_EXPORT Cookie
value_ = value; value_ = value;
} }
/**
* @brief Set the value of the cookie.
*/
void setValue(std::string &&value) void setValue(std::string &&value)
{ {
value_ = std::move(value); value_ = std::move(value);
@ -149,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
*/ */
@ -275,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
*/ */
@ -318,7 +348,7 @@ class DROGON_EXPORT Cookie
* str2. so the function doesn't apply tolower to the second argument * str2. so the function doesn't apply tolower to the second argument
* str2 as it's always in lower case. * str2 as it's always in lower case.
* *
* @return true if both strings are equall ignoring case * @return true if both strings are equal ignoring case
*/ */
static bool stricmp(const std::string_view str1, static bool stricmp(const std::string_view str1,
const std::string_view str2) const std::string_view str2)
@ -343,21 +373,15 @@ class DROGON_EXPORT Cookie
* @brief Converts a string value to its associated enum class SameSite * @brief Converts a string value to its associated enum class SameSite
* value * value
*/ */
static SameSite convertString2SameSite(const std::string_view &sameSite) static SameSite convertString2SameSite(std::string_view sameSite)
{ {
if (stricmp(sameSite, "lax")) if (stricmp(sameSite, "lax"))
{
return Cookie::SameSite::kLax; return Cookie::SameSite::kLax;
} if (stricmp(sameSite, "strict"))
else if (stricmp(sameSite, "strict"))
{
return Cookie::SameSite::kStrict; return Cookie::SameSite::kStrict;
} if (stricmp(sameSite, "none"))
else if (stricmp(sameSite, "none"))
{
return Cookie::SameSite::kNone; return Cookie::SameSite::kNone;
} if (!stricmp(sameSite, "null"))
else if (!stricmp(sameSite, "null"))
{ {
LOG_WARN LOG_WARN
<< "'" << sameSite << "'" << sameSite
@ -372,34 +396,20 @@ class DROGON_EXPORT Cookie
* @brief Converts an enum class SameSite value to its associated string * @brief Converts an enum class SameSite value to its associated string
* value * value
*/ */
static const std::string_view &convertSameSite2String(SameSite sameSite) static std::string_view convertSameSite2String(SameSite sameSite)
{ {
switch (sameSite) switch (sameSite)
{ {
case SameSite::kLax: case SameSite::kLax:
{ return "Lax";
static std::string_view sv{"Lax"};
return sv;
}
case SameSite::kStrict: case SameSite::kStrict:
{ return "Strict";
static std::string_view sv{"Strict"};
return sv;
}
case SameSite::kNone: case SameSite::kNone:
{ return "None";
static std::string_view sv{"None"};
return sv;
}
case SameSite::kNull: case SameSite::kNull:
{ return "Null";
static std::string_view sv{"Null"}; default:
return sv; return "UNDEFINED";
}
}
{
static std::string_view sv{"UNDEFINED"};
return sv;
} }
} }
@ -407,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

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

View File

@ -57,6 +57,26 @@ class DROGON_EXPORT DrObjectBase
} }
}; };
template <typename T>
struct isAutoCreationClass
{
template <class C>
static constexpr auto check(C *) -> std::enable_if_t<
std::is_same_v<decltype(C::isAutoCreation), const bool>,
bool>
{
return C::isAutoCreation;
}
template <typename>
static constexpr bool check(...)
{
return false;
}
static constexpr bool value = check<T>(nullptr);
};
/** /**
* a class template to * a class template to
* implement the reflection function of creating the class object by class name * implement the reflection function of creating the class object by class name
@ -102,23 +122,22 @@ class DrObject : public virtual DrObjectBase
} }
template <typename D> template <typename D>
typename std::enable_if<std::is_default_constructible<D>::value, void registerClass()
void>::type
registerClass()
{
DrClassMap::registerClass(
className(),
[]() -> DrObjectBase * { return new T; },
[]() -> std::shared_ptr<DrObjectBase> {
return std::make_shared<T>();
});
}
template <typename D>
typename std::enable_if<!std::is_default_constructible<D>::value,
void>::type
registerClass()
{ {
if constexpr (std::is_default_constructible<D>::value)
{
DrClassMap::registerClass(
className(),
[]() -> DrObjectBase * { return new T; },
[]() -> std::shared_ptr<DrObjectBase> {
return std::make_shared<T>();
});
}
else if constexpr (isAutoCreationClass<D>::value)
{
static_assert(std::is_default_constructible<D>::value,
"Class is not default constructable!");
}
} }
}; };

View File

@ -30,6 +30,7 @@
#include <drogon/HttpRequest.h> #include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h> #include <drogon/HttpResponse.h>
#include <drogon/orm/DbClient.h> #include <drogon/orm/DbClient.h>
#include <drogon/orm/DbConfig.h>
#include <drogon/nosql/RedisClient.h> #include <drogon/nosql/RedisClient.h>
#include <drogon/Cookie.h> #include <drogon/Cookie.h>
#include <trantor/net/Resolver.h> #include <trantor/net/Resolver.h>
@ -42,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
@ -66,6 +77,8 @@ using ExceptionHandler =
using DefaultHandler = using DefaultHandler =
std::function<void(const HttpRequestPtr &, std::function<void(const HttpRequestPtr &,
std::function<void(const HttpResponsePtr &)> &&)>; std::function<void(const HttpResponsePtr &)> &&)>;
using HttpHandlerInfo = std::tuple<std::string, HttpMethod, std::string>;
#ifdef __cpp_impl_coroutine #ifdef __cpp_impl_coroutine
class HttpAppFramework; class HttpAppFramework;
@ -144,7 +157,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/ */
virtual trantor::EventLoop *getLoop() const = 0; virtual trantor::EventLoop *getLoop() const = 0;
/// Get an IO loop with id. E.g. 0 <= id < #Total thread-loops /// Get an IO loop with id. E.g. 0 <= id < \#Total thread-loops
/** /**
* @note * @note
* The event loop is one of the network IO loops. Use the loop * The event loop is one of the network IO loops. Use the loop
@ -171,7 +184,19 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* be sent to the client to provide a custom layout. * be sent to the client to provide a custom layout.
*/ */
virtual HttpAppFramework &setCustomErrorHandler( virtual HttpAppFramework &setCustomErrorHandler(
std::function<HttpResponsePtr(HttpStatusCode)> &&resp_generator) = 0; std::function<HttpResponsePtr(HttpStatusCode,
const HttpRequestPtr &req)>
&&resp_generator) = 0;
HttpAppFramework &setCustomErrorHandler(
std::function<HttpResponsePtr(HttpStatusCode)> &&resp_generator)
{
return setCustomErrorHandler(
[cb = std::move(resp_generator)](HttpStatusCode code,
const HttpRequestPtr &) {
return cb(code);
});
}
/// Get custom error handler /// Get custom error handler
/** /**
@ -179,8 +204,9 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* setCustomErrorHandler. If none was provided, the default error handler is * setCustomErrorHandler. If none was provided, the default error handler is
* returned. * returned.
*/ */
virtual const std::function<HttpResponsePtr(HttpStatusCode)> virtual const std::function<HttpResponsePtr(HttpStatusCode,
&getCustomErrorHandler() const = 0; const HttpRequestPtr &req)> &
getCustomErrorHandler() const = 0;
/// Get the plugin object registered in the framework /// Get the plugin object registered in the framework
/** /**
@ -307,7 +333,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
[Check Method]---------------->[405]----------->+ [Check Method]---------------->[405]----------->+
| | | |
v | v |
[Filters]------->[Filter callback]----------->+ [Filters/Middlewares]------>[Filter callback]------>+
| | | |
v Y | v Y |
[Is OPTIONS method?]------------->[200]----------->+ [Is OPTIONS method?]------------->[200]----------->+
@ -320,6 +346,9 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
| | | |
v | v |
Post-handling join point o---------------------------------------->+ Post-handling join point o---------------------------------------->+
| |
v |
[Middlewares post logic]--->[Middleware callback]--->+
@endcode @endcode
* *
@ -330,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.
@ -353,7 +382,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Register an advice called after routing /// Register an advice called after routing
/** /**
* @param advice is called immediately after the request matches a handler * @param advice is called immediately after the request matches a handler
* path and before any 'doFilter' method of filters applies. The parameters * path and before any filters/middlewares applies. 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.
*/ */
@ -375,8 +404,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Register an advice called before the request is handled /// Register an advice called before the request is handled
/** /**
* @param advice is called immediately after the request is approved by all * @param advice is called immediately after the request is approved by all
* filters and before it is handled. The parameters of the advice are * filters/middlewares and before it is handled. The parameters of the
* same as those of the doFilter method of the Filter class. * advice are same as those of the doFilter method of the Filter class.
*/ */
virtual HttpAppFramework &registerPreHandlingAdvice( virtual HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &, const std::function<void(const HttpRequestPtr &,
@ -426,14 +455,14 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Load the configuration file with json format. /// Load the configuration file with json format.
/** /**
* @param filename the configuration file * @param fileName the configuration file
*/ */
virtual HttpAppFramework &loadConfigFile( virtual HttpAppFramework &loadConfigFile(
const std::string &fileName) noexcept(false) = 0; const std::string &fileName) noexcept(false) = 0;
/// Load the configuration from a Json::Value Object. /// Load the configuration from a Json::Value Object.
/** /**
* @param Json::Value Object containing the configuration. * @param data Json::Value Object containing the configuration.
* @note Please refer to the configuration file for the content of the json * @note Please refer to the configuration file for the content of the json
* object. * object.
*/ */
@ -442,7 +471,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/// Load the configuration from a Json::Value Object. /// Load the configuration from a Json::Value Object.
/** /**
* @param rvalue reference to a Json::Value object containing the * @param data rvalue reference to a Json::Value object containing the
* configuration. * configuration.
* @note Please refer to the configuration file for the content of the json * @note Please refer to the configuration file for the content of the json
* object. * object.
@ -457,8 +486,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* called. * called.
* @param ctrlName is the name of the controller. It includes the namespace * @param ctrlName is the name of the controller. It includes the namespace
* to which the controller belongs. * to which the controller belongs.
* @param filtersAndMethods is a vector containing Http methods or filter * @param constraints is a vector containing Http methods or middleware
* name constraints. names
* *
* Example: * Example:
* @code * @code
@ -472,8 +501,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &registerHttpSimpleController( virtual HttpAppFramework &registerHttpSimpleController(
const std::string &pathName, const std::string &pathName,
const std::string &ctrlName, const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods = const std::vector<internal::HttpConstraint> &constraints = {}) = 0;
std::vector<internal::HttpConstraint>{}) = 0;
/// Register a handler into the framework. /// Register a handler into the framework.
/** /**
@ -481,7 +509,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* pathPattern, the handler indicated by the function parameter is called. * pathPattern, the handler indicated by the function parameter is called.
* @param function indicates any type of callable object with a valid * @param function indicates any type of callable object with a valid
* processing interface. * processing interface.
* @param filtersAndMethods is the same as the third parameter in the above * @param constraints is the same as the third parameter in the above
* method. * method.
* *
* Example: * Example:
@ -507,8 +535,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
HttpAppFramework &registerHandler( HttpAppFramework &registerHandler(
const std::string &pathPattern, const std::string &pathPattern,
FUNCTION &&function, FUNCTION &&function,
const std::vector<internal::HttpConstraint> &filtersAndMethods = const std::vector<internal::HttpConstraint> &constraints = {},
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "") const std::string &handlerName = "")
{ {
LOG_TRACE << "pathPattern:" << pathPattern; LOG_TRACE << "pathPattern:" << pathPattern;
@ -518,17 +545,16 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
getLoop()->queueInLoop([binder]() { binder->createHandlerInstance(); }); getLoop()->queueInLoop([binder]() { binder->createHandlerInstance(); });
std::vector<HttpMethod> validMethods; std::vector<HttpMethod> validMethods;
std::vector<std::string> filters; std::vector<std::string> middlewares;
for (auto const &filterOrMethod : filtersAndMethods) for (auto const &constraint : constraints)
{ {
if (filterOrMethod.type() == internal::ConstraintType::HttpFilter) if (constraint.type() == internal::ConstraintType::HttpMiddleware)
{ {
filters.push_back(filterOrMethod.getFilterName()); middlewares.push_back(constraint.getMiddlewareName());
} }
else if (filterOrMethod.type() == else if (constraint.type() == internal::ConstraintType::HttpMethod)
internal::ConstraintType::HttpMethod)
{ {
validMethods.push_back(filterOrMethod.getHttpMethod()); validMethods.push_back(constraint.getHttpMethod());
} }
else else
{ {
@ -537,7 +563,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
} }
} }
registerHttpController( registerHttpController(
pathPattern, binder, validMethods, filters, handlerName); pathPattern, binder, validMethods, middlewares, handlerName);
return *this; return *this;
} }
@ -551,8 +577,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* subexpression is sequentially mapped to a handler parameter. * subexpression is sequentially mapped to a handler parameter.
* @param function indicates any type of callable object with a valid * @param function indicates any type of callable object with a valid
* processing interface. * processing interface.
* @param filtersAndMethods is the same as the third parameter in the above * @param constraints is the same as the third parameter in the
* method. * above method.
* @param handlerName a name for the handler. * @param handlerName a name for the handler.
* @return HttpAppFramework& * @return HttpAppFramework&
*/ */
@ -560,8 +586,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
HttpAppFramework &registerHandlerViaRegex( HttpAppFramework &registerHandlerViaRegex(
const std::string &regExp, const std::string &regExp,
FUNCTION &&function, FUNCTION &&function,
const std::vector<internal::HttpConstraint> &filtersAndMethods = const std::vector<internal::HttpConstraint> &constraints = {},
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "") const std::string &handlerName = "")
{ {
LOG_TRACE << "regex:" << regExp; LOG_TRACE << "regex:" << regExp;
@ -571,17 +596,16 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
std::forward<FUNCTION>(function)); std::forward<FUNCTION>(function));
std::vector<HttpMethod> validMethods; std::vector<HttpMethod> validMethods;
std::vector<std::string> filters; std::vector<std::string> middlewares;
for (auto const &filterOrMethod : filtersAndMethods) for (auto const &constraint : constraints)
{ {
if (filterOrMethod.type() == internal::ConstraintType::HttpFilter) if (constraint.type() == internal::ConstraintType::HttpMiddleware)
{ {
filters.push_back(filterOrMethod.getFilterName()); middlewares.push_back(constraint.getMiddlewareName());
} }
else if (filterOrMethod.type() == else if (constraint.type() == internal::ConstraintType::HttpMethod)
internal::ConstraintType::HttpMethod)
{ {
validMethods.push_back(filterOrMethod.getHttpMethod()); validMethods.push_back(constraint.getHttpMethod());
} }
else else
{ {
@ -590,7 +614,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
} }
} }
registerHttpControllerViaRegex( registerHttpControllerViaRegex(
regExp, binder, validMethods, filters, handlerName); regExp, binder, validMethods, middlewares, handlerName);
return *this; return *this;
} }
@ -602,7 +626,18 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
virtual HttpAppFramework &registerWebSocketController( virtual HttpAppFramework &registerWebSocketController(
const std::string &pathName, const std::string &pathName,
const std::string &ctrlName, const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods = const std::vector<internal::HttpConstraint> &constraints = {}) = 0;
/// Register a WebSocketController into the framework.
/**
* The parameters of this method are the same as those in the
* registerHttpSimpleController() method but using regular
* expression string for path.
*/
virtual HttpAppFramework &registerWebSocketControllerRegex(
const std::string &regExp,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &constraints =
std::vector<internal::HttpConstraint>{}) = 0; std::vector<internal::HttpConstraint>{}) = 0;
/// Register controller objects created and initialized by the user /// Register controller objects created and initialized by the user
@ -666,13 +701,31 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
return *this; return *this;
} }
/// Register middleware objects created and initialized by the user
/**
* This method is similar to the above method.
*/
template <typename T>
HttpAppFramework &registerMiddleware(
const std::shared_ptr<T> &middlewarePtr)
{
static_assert(std::is_base_of<HttpMiddlewareBase, T>::value,
"Error! Only middleware objects can be registered here");
static_assert(!T::isAutoCreation,
"Middleware created and initialized "
"automatically by drogon cannot be "
"registered here");
DrClassMap::setSingleInstance(middlewarePtr);
return *this;
}
/// Register a default handler into the framework when no handler matches /// Register a default handler into the framework when no handler matches
/// the request. If set, it is executed if the static file router does /// the request. If set, it is executed if the static file router does
/// not find any file corresponding to the request. Thus it replaces /// not find any file corresponding to the request. Thus it replaces
/// the default 404 not found response. /// the default 404 not found response.
/** /**
* @param function indicates any type of callable object with a valid * @param handler function indicates any type of callable object with
* processing interface. * a valid processing interface.
*/ */
virtual HttpAppFramework &setDefaultHandler(DefaultHandler handler) = 0; virtual HttpAppFramework &setDefaultHandler(DefaultHandler handler) = 0;
@ -731,8 +784,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* pattern of the handler; * pattern of the handler;
* The last item in std::tuple is the description of the handler. * The last item in std::tuple is the description of the handler.
*/ */
virtual std::vector<std::tuple<std::string, HttpMethod, std::string>> virtual std::vector<HttpHandlerInfo> getHandlersInfo() const = 0;
getHandlersInfo() const = 0;
/// Get the custom configuration defined by users in the configuration file. /// Get the custom configuration defined by users in the configuration file.
virtual const Json::Value &getCustomConfig() const = 0; virtual const Json::Value &getCustomConfig() const = 0;
@ -764,6 +816,15 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
const std::vector<std::pair<std::string, std::string>> const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0; &sslConfCmds) = 0;
/// Reload the global cert file and private key file for https server
/// Note: The goal of this method is not to make the framework
/// use the new SSL path, but rather to reload the new content
/// from the old path while the framework is still running.
/// Typically, when our SSL is about to expire,
/// we need to reload the SSL. The purpose of this function
/// is to use the new SSL certificate without stopping the framework.
virtual HttpAppFramework &reloadSSLFiles() = 0;
/// Add plugins /// Add plugins
/** /**
* @param configs The plugins array * @param configs The plugins array
@ -793,8 +854,9 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* @param keyFile specify the cert file and the private key file for this * @param keyFile specify the cert file and the private key file for this
* listener. If they are empty, the global configuration set by the above * listener. If they are empty, the global configuration set by the above
* method is used. * method is used.
* @param useOldTLS If true, the TLS1.0/1.1 are enabled for HTTPS * @param useOldTLS if true, the TLS1.0/1.1 are enabled for HTTPS
* connections. * connections.
* @param sslConfCmds vector of ssl configuration key/value pairs.
* *
* @note * @note
* This operation can be performed by an option in the configuration file. * This operation can be performed by an option in the configuration file.
@ -813,6 +875,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/** /**
* @param timeout The number of seconds which is the timeout of a session * @param timeout The number of seconds which is the timeout of a session
* @param sameSite The default value of SameSite attribute * @param sameSite The default value of SameSite attribute
* @param cookieKey The key of the session cookie
* *
* @note * @note
* Session support is disabled by default. * Session support is disabled by default.
@ -823,7 +886,10 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/ */
virtual HttpAppFramework &enableSession( virtual HttpAppFramework &enableSession(
const size_t timeout = 0, const size_t timeout = 0,
Cookie::SameSite sameSite = Cookie::SameSite::kNull) = 0; Cookie::SameSite sameSite = Cookie::SameSite::kNull,
const std::string &cookieKey = "JSESSIONID",
int maxAge = -1,
std::function<std::string()> idGeneratorCallback = nullptr) = 0;
/// A wrapper of the above method. /// A wrapper of the above method.
/** /**
@ -835,9 +901,16 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/ */
inline HttpAppFramework &enableSession( inline HttpAppFramework &enableSession(
const std::chrono::duration<double> &timeout, const std::chrono::duration<double> &timeout,
Cookie::SameSite sameSite = Cookie::SameSite::kNull) Cookie::SameSite sameSite = Cookie::SameSite::kNull,
const std::string &cookieKey = "JSESSIONID",
int maxAge = -1,
std::function<std::string()> idGeneratorCallback = nullptr)
{ {
return enableSession((size_t)timeout.count(), sameSite); return enableSession((size_t)timeout.count(),
sameSite,
cookieKey,
maxAge,
idGeneratorCallback);
} }
/// Register an advice called when starting a new session. /// Register an advice called when starting a new session.
@ -893,7 +966,8 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* extension can be accessed. * extension can be accessed.
* @param isRecursive If it is set to false, files in sub directories can't * @param isRecursive If it is set to false, files in sub directories can't
* be accessed. * be accessed.
* @param filters The list of filters which acting on the location. * @param middlewareNames The list of middlewares which acting on the
* location.
* @return HttpAppFramework& * @return HttpAppFramework&
*/ */
virtual HttpAppFramework &addALocation( virtual HttpAppFramework &addALocation(
@ -903,7 +977,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
bool isCaseSensitive = false, bool isCaseSensitive = false,
bool allowAll = true, bool allowAll = true,
bool isRecursive = true, bool isRecursive = true,
const std::vector<std::string> &filters = {}) = 0; const std::vector<std::string> &middlewareNames = {}) = 0;
/// Set the path to store uploaded files. /// Set the path to store uploaded files.
/** /**
@ -933,21 +1007,21 @@ 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;
/// Enable supporting for dynamic views loading. #if !defined(_WIN32) && !TARGET_OS_IOS
/** /// Enable supporting for dynamic views loading.
* /**
* @param libPaths is a vector that contains paths to view files. *
* * @param libPaths is a vector that contains paths to view files.
* @param outputPath is the directory where the output source files locate. if *
* it is set to an empty string, drogon use libPaths as output paths. If the * @param outputPath is the directory where the output source files locate.
* path isn't prefixed with /, it is relative path of the current working * If it is set to an empty string, drogon use libPaths as output paths. If
* directory. * the path isn't prefixed with /, it is the relative path of the current
* * working directory.
* @note *
* It is disabled by default. * @note
* This operation can be performed by an option in the configuration file. * It is disabled by default.
*/ * This operation can be performed by an option in the configuration file.
#ifndef _WIN32 */
virtual HttpAppFramework &enableDynamicViewsLoading( virtual HttpAppFramework &enableDynamicViewsLoading(
const std::vector<std::string> &libPaths, const std::vector<std::string> &libPaths,
const std::string &outputPath = "") = 0; const std::string &outputPath = "") = 0;
@ -1000,11 +1074,14 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/ */
virtual HttpAppFramework &enableRelaunchOnError() = 0; virtual HttpAppFramework &enableRelaunchOnError() = 0;
/// Set the output path of logs.
/** /**
* @param logPath The path to logs. * @brief Set the output path of logs.
* @param logfileBaseName The base name of log files. * @param logPath The path to logs - logs to console if empty.
* @param logfileBaseName The base name of log files - defaults to "drogon"
* if empty.
* @param logSize indicates the maximum size of a log file. * @param logSize indicates the maximum size of a log file.
* @param maxFiles max count of log file - 0 = unlimited.
* @param useSpdlog Use spdlog for logging (if compiled-in).
* *
* @note * @note
* This operation can be performed by an option in the configuration file. * This operation can be performed by an option in the configuration file.
@ -1013,10 +1090,11 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
const std::string &logPath, const std::string &logPath,
const std::string &logfileBaseName = "", const std::string &logfileBaseName = "",
size_t logSize = 100000000, size_t logSize = 100000000,
size_t maxFiles = 0) = 0; size_t maxFiles = 0,
bool useSpdlog = false) = 0;
/// Set the log level
/** /**
* @brief Set the log level.
* @param level is one of TRACE, DEBUG, INFO, WARN. The Default value is * @param level is one of TRACE, DEBUG, INFO, WARN. The Default value is
* DEBUG. * DEBUG.
* *
@ -1359,7 +1437,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/ */
virtual size_t getJsonParserStackLimit() const noexcept = 0; virtual size_t getJsonParserStackLimit() const noexcept = 0;
/** /**
* @brief This method is to enable or disable the unicode escaping (\u) in * @brief This method is to enable or disable the unicode escaping (\\u) in
* the json string of HTTP responses or requests. it works (disable * the json string of HTTP responses or requests. it works (disable
* successfully) when the version of JsonCpp >= 1.9.3, the unicode escaping * successfully) when the version of JsonCpp >= 1.9.3, the unicode escaping
* is enabled by default. * is enabled by default.
@ -1391,19 +1469,19 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* *
* @return std::pair<size_t, std::string> * @return std::pair<size_t, std::string>
*/ */
virtual const std::pair<unsigned int, std::string> virtual const std::pair<unsigned int, std::string> &
&getFloatPrecisionInJson() const noexcept = 0; getFloatPrecisionInJson() const noexcept = 0;
/// Create a database client /// Create a database client
/** /**
* @param dbType The database type is one of * @param dbType The database type is one of
* "postgresql","mysql","sqlite3". * "postgresql","mysql","sqlite3".
* @param host IP or host name. * @param host IP or host name.
* @param port The port on which the database server is listening. * @param port The port on which the database server is listening.
* @databaseName Database name * @param databaseName Database name
* @param userName User name * @param userName User name
* @param password Password for the database server * @param password Password for the database server
* @param connectionNum The number of connections to the database server. * @param connectionNum The number of connections to the database server.
* It's valid only if @param isFast is false. * It's valid only if @p isFast is false.
* @param filename The file name of sqlite3 database file. * @param filename The file name of sqlite3 database file.
* @param name The client name. * @param name The client name.
* @param isFast Indicates if the client is a fast database client. * @param isFast Indicates if the client is a fast database client.
@ -1414,20 +1492,22 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
* @note * @note
* This operation can be performed by an option in the configuration file. * This operation can be performed by an option in the configuration file.
*/ */
virtual HttpAppFramework &createDbClient( [[deprecated("Use addDbClient() instead")]] virtual HttpAppFramework &
const std::string &dbType, createDbClient(const std::string &dbType,
const std::string &host, const std::string &host,
const unsigned short port, unsigned short port,
const std::string &databaseName, const std::string &databaseName,
const std::string &userName, const std::string &userName,
const std::string &password, const std::string &password,
const size_t connectionNum = 1, size_t connectionNum = 1,
const std::string &filename = "", const std::string &filename = "",
const std::string &name = "default", const std::string &name = "default",
const bool isFast = false, bool isFast = false,
const std::string &characterSet = "", const std::string &characterSet = "",
double timeout = -1.0, double timeout = -1.0,
const bool autoBatch = false) = 0; bool autoBatch = false) = 0;
virtual HttpAppFramework &addDbClient(const orm::DbConfig &config) = 0;
/// Create a redis client /// Create a redis client
/** /**
@ -1508,7 +1588,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
/** /**
* @brief handler will be called upon an exception escapes a request handler * @brief handler will be called upon an exception escapes a request handler
*/ */
virtual void setExceptionHandler(ExceptionHandler handler) = 0; virtual HttpAppFramework &setExceptionHandler(ExceptionHandler handler) = 0;
/** /**
* @brief returns the excaption handler * @brief returns the excaption handler
@ -1529,18 +1609,46 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
*/ */
virtual int64_t getConnectionCount() const = 0; virtual int64_t getConnectionCount() const = 0;
/**
* @brief Set the before listen setsockopt callback.
*
* @param cb This callback will be called before the listen
*/
virtual HttpAppFramework &setBeforeListenSockOptCallback(
std::function<void(int)> cb) = 0;
/**
* @brief Set the after accept setsockopt callback.
*
* @param cb This callback will be called after accept
*/
virtual HttpAppFramework &setAfterAcceptSockOptCallback(
std::function<void(int)> cb) = 0;
/**
* @brief Set the client disconnect or connect callback.
*
* @param cb This callback will be called, when the client disconnect or
* connect
*/
virtual HttpAppFramework &setConnectionCallback(
std::function<void(const trantor::TcpConnectionPtr &)> cb) = 0;
virtual HttpAppFramework &enableRequestStream(bool enable = true) = 0;
virtual bool isRequestStreamEnabled() const = 0;
private: private:
virtual void registerHttpController( virtual void registerHttpController(
const std::string &pathPattern, const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder, const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods = std::vector<HttpMethod>(), const std::vector<HttpMethod> &validMethods = {},
const std::vector<std::string> &filters = std::vector<std::string>(), const std::vector<std::string> &middlewareNames = {},
const std::string &handlerName = "") = 0; const std::string &handlerName = "") = 0;
virtual void registerHttpControllerViaRegex( virtual void registerHttpControllerViaRegex(
const std::string &regExp, const std::string &regExp,
const internal::HttpBinderBasePtr &binder, const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods, const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters, const std::vector<std::string> &middlewareNames,
const std::string &handlerName) = 0; const std::string &handlerName) = 0;
}; };

View File

@ -70,6 +70,91 @@ struct BinderArgTypeTraits<const T &>
static const bool isValid = true; static const bool isValid = true;
}; };
template <typename T>
T getHandlerArgumentValue(std::string &&p)
{
if constexpr (internal::CanConstructFromString<T>::value)
{
return T(std::move(p));
}
else if constexpr (internal::CanConvertFromStringStream<T>::value)
{
T value{T()};
if (!p.empty())
{
std::stringstream ss(std::move(p));
ss >> value;
}
return value;
}
else if constexpr (internal::CanConvertFromString<T>::value)
{
T value;
value = p;
return value;
}
else
{
LOG_ERROR << "Can't convert string to type " << typeid(T).name();
return T();
}
}
template <>
inline std::string getHandlerArgumentValue<std::string>(std::string &&p)
{
return std::move(p);
}
template <>
inline int getHandlerArgumentValue<int>(std::string &&p)
{
return std::stoi(p);
}
template <>
inline long getHandlerArgumentValue<long>(std::string &&p)
{
return std::stol(p);
}
template <>
inline long long getHandlerArgumentValue<long long>(std::string &&p)
{
return std::stoll(p);
}
template <>
inline unsigned long getHandlerArgumentValue<unsigned long>(std::string &&p)
{
return std::stoul(p);
}
template <>
inline unsigned long long getHandlerArgumentValue<unsigned long long>(
std::string &&p)
{
return std::stoull(p);
}
template <>
inline float getHandlerArgumentValue<float>(std::string &&p)
{
return std::stof(p);
}
template <>
inline double getHandlerArgumentValue<double>(std::string &&p)
{
return std::stod(p);
}
template <>
inline long double getHandlerArgumentValue<long double>(std::string &&p)
{
return std::stold(p);
}
class HttpBinderBase class HttpBinderBase
{ {
public: public:
@ -79,6 +164,7 @@ class HttpBinderBase
std::function<void(const HttpResponsePtr &)> &&callback) = 0; std::function<void(const HttpResponsePtr &)> &&callback) = 0;
virtual size_t paramCount() = 0; virtual size_t paramCount() = 0;
virtual const std::string &handlerName() const = 0; virtual const std::string &handlerName() const = 0;
virtual bool isStreamHandler() = 0;
virtual ~HttpBinderBase() virtual ~HttpBinderBase()
{ {
@ -133,6 +219,11 @@ class HttpBinder : public HttpBinderBase
return traits::arity; return traits::arity;
} }
bool isStreamHandler() override
{
return traits::isStreamHandler;
}
HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func)) HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
{ {
static_assert(traits::isHTTPFunction, static_assert(traits::isHTTPFunction,
@ -153,27 +244,22 @@ class HttpBinder : public HttpBinderBase
template <bool isClassFunction = traits::isClassFunction, template <bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass> bool isDrObjectClass = traits::isDrObjectClass>
typename std::enable_if<isDrObjectClass && isClassFunction, void>::type void createHandlerInstance()
createHandlerInstance()
{
auto objPtr =
DrClassMap::getSingleInstance<typename traits::class_type>();
LOG_TRACE << "create handler class object: " << objPtr.get();
}
template <bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass>
typename std::enable_if<!isDrObjectClass && isClassFunction, void>::type
createHandlerInstance()
{
auto &obj = getControllerObj<typename traits::class_type>();
LOG_TRACE << "create handler class object: " << &obj;
}
template <bool isClassFunction = traits::isClassFunction>
typename std::enable_if<!isClassFunction, void>::type
createHandlerInstance()
{ {
if constexpr (isClassFunction)
{
if constexpr (isDrObjectClass)
{
auto objPtr = DrClassMap::getSingleInstance<
typename traits::class_type>();
LOG_TRACE << "create handler class object: " << objPtr.get();
}
else
{
auto &obj = getControllerObj<typename traits::class_type>();
LOG_TRACE << "create handler class object: " << &obj;
}
}
} }
private: private:
@ -184,276 +270,214 @@ class HttpBinder : public HttpBinderBase
static const size_t argument_count = traits::arity; static const size_t argument_count = traits::arity;
std::string handlerName_; std::string handlerName_;
template <typename T> template <typename... Values,
typename std::enable_if<internal::CanConvertFromStringStream<T>::value, std::size_t Boundary = argument_count,
void>::type bool isStreamHandler = traits::isStreamHandler,
getHandlerArgumentValue(T &value, std::string &&p) bool isCoroutine = traits::isCoroutine>
void run(std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&...values)
{ {
if (!p.empty()) if constexpr (sizeof...(Values) < Boundary)
{ // Call this function recursively until parameter's count equals to
// the count of target function parameters
static_assert(
BinderArgTypeTraits<
nth_argument_type<sizeof...(Values)>>::isValid,
"your handler argument type must be value type or const left "
"reference type or right reference type");
using ValueType = std::remove_cv_t<
std::remove_reference_t<nth_argument_type<sizeof...(Values)>>>;
if (!pathArguments.empty())
{
std::string v{std::move(pathArguments.front())};
pathArguments.pop_front();
try
{
if (!v.empty())
{
auto value =
getHandlerArgumentValue<ValueType>(std::move(v));
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
std::move(value));
return;
}
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
}
else
{
try
{
auto value = req->as<ValueType>();
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
std::move(value));
return;
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
ValueType());
}
else if constexpr (sizeof...(Values) == Boundary)
{ {
std::stringstream ss(std::move(p)); if constexpr (!isCoroutine)
ss >> value; {
try
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
if constexpr (isStreamHandler)
{
callFunction(req,
createRequestStream(req),
cb,
std::move(values)...);
}
else
{
callFunction(req, cb, std::move(values)...);
}
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
#ifdef __cpp_impl_coroutine
else
{
static_assert(!isStreamHandler);
[this](HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
Values &&...values) -> AsyncTask {
try
{
if constexpr (std::is_same_v<
AsyncTask,
typename traits::return_type>)
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
callFunction(req, cb, std::move(values)...);
}
else if constexpr (std::is_same_v<
Task<>,
typename traits::return_type>)
{
// Explicit copy because `callFunction` moves it
auto cb = callback;
co_await callFunction(req,
cb,
std::move(values)...);
}
else if constexpr (std::is_same_v<
Task<HttpResponsePtr>,
typename traits::return_type>)
{
auto resp =
co_await callFunction(req,
std::move(values)...);
callback(std::move(resp));
}
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR
<< "Exception not derived from std::exception";
}
co_return;
}(req, std::move(callback), std::move(values)...);
}
#endif
} }
} }
template <typename T> template <typename... Values,
typename std::enable_if<!(internal::CanConvertFromStringStream<T>::value), bool isClassFunction = traits::isClassFunction,
void>::type bool isDrObjectClass = traits::isDrObjectClass,
getHandlerArgumentValue(T &value, std::string &&p) bool isNormal = std::is_same_v<typename traits::first_param_type,
HttpRequestPtr>>
typename traits::return_type callFunction(const HttpRequestPtr &req,
Values &&...values)
{ {
} if constexpr (isNormal)
void getHandlerArgumentValue(std::string &value, std::string &&p)
{
value = std::move(p);
}
void getHandlerArgumentValue(int &value, std::string &&p)
{
value = std::stoi(p);
}
void getHandlerArgumentValue(long &value, std::string &&p)
{
value = std::stol(p);
}
void getHandlerArgumentValue(long long &value, std::string &&p)
{
value = std::stoll(p);
}
void getHandlerArgumentValue(unsigned long &value, std::string &&p)
{
value = std::stoul(p);
}
void getHandlerArgumentValue(unsigned long long &value, std::string &&p)
{
value = std::stoull(p);
}
void getHandlerArgumentValue(float &value, std::string &&p)
{
value = std::stof(p);
}
void getHandlerArgumentValue(double &value, std::string &&p)
{
value = std::stod(p);
}
void getHandlerArgumentValue(long double &value, std::string &&p)
{
value = std::stold(p);
}
template <typename... Values, std::size_t Boundary = argument_count>
typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run(
std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&...values)
{
// Call this function recursively until parameter's count equals to the
// count of target function parameters
static_assert(
BinderArgTypeTraits<nth_argument_type<sizeof...(Values)>>::isValid,
"your handler argument type must be value type or const left "
"reference type or right reference type");
using ValueType =
typename std::remove_cv<typename std::remove_reference<
nth_argument_type<sizeof...(Values)>>::type>::type;
ValueType value = ValueType();
if (!pathArguments.empty())
{ {
std::string v = std::move(pathArguments.front()); if constexpr (isClassFunction)
pathArguments.pop_front();
try
{ {
if (v.empty() == false) if constexpr (!isDrObjectClass)
getHandlerArgumentValue(value, std::move(v)); {
static auto &obj =
getControllerObj<typename traits::class_type>();
return (obj.*func_)(req, std::move(values)...);
}
else
{
static auto objPtr = DrClassMap::getSingleInstance<
typename traits::class_type>();
return (*objPtr.*func_)(req, std::move(values)...);
}
} }
catch (const std::exception &e) else
{ {
handleException(e, req, std::move(callback)); return func_(req, std::move(values)...);
return;
} }
} }
else else
{ {
try if constexpr (isClassFunction)
{ {
value = req->as<ValueType>(); if constexpr (!isDrObjectClass)
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
std::move(value));
}
template <typename... Values,
std::size_t Boundary = argument_count,
bool isCoroutine = traits::isCoroutine>
typename std::enable_if<(sizeof...(Values) == Boundary) && !isCoroutine,
void>::type
run(std::deque<std::string> &,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&...values)
{
try
{
// Explcit copy because `callFunction` moves it
auto cb = callback;
callFunction(req, cb, std::move(values)...);
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
#ifdef __cpp_impl_coroutine
template <typename... Values,
std::size_t Boundary = argument_count,
bool isCoroutine = traits::isCoroutine>
typename std::enable_if<(sizeof...(Values) == Boundary) && isCoroutine,
void>::type
run(std::deque<std::string> &,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&...values)
{
[this](HttpRequestPtr req,
std::function<void(const HttpResponsePtr &)> callback,
Values &&...values) -> AsyncTask {
try
{
if constexpr (std::is_same_v<AsyncTask,
typename traits::return_type>)
{ {
// Explcit copy because `callFunction` moves it static auto &obj =
auto cb = callback; getControllerObj<typename traits::class_type>();
callFunction(req, cb, std::move(values)...); return (obj.*func_)((*req), std::move(values)...);
} }
else if constexpr (std::is_same_v<Task<>, else
typename traits::return_type>)
{ {
// Explcit copy because `callFunction` moves it static auto objPtr = DrClassMap::getSingleInstance<
auto cb = callback; typename traits::class_type>();
co_await callFunction(req, cb, std::move(values)...); return (*objPtr.*func_)((*req), std::move(values)...);
}
else if constexpr (std::is_same_v<Task<HttpResponsePtr>,
typename traits::return_type>)
{
auto resp =
co_await callFunction(req, std::move(values)...);
callback(std::move(resp));
} }
} }
catch (const std::exception &except) else
{ {
handleException(except, req, std::move(callback)); return func_((*req), std::move(values)...);
} }
catch (...) }
{
LOG_ERROR << "Exception not derived from std::exception";
}
}(req, std::move(callback), std::move(values)...);
}
#endif
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<isClassFunction && !isDrObjectClass && isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&...values)
{
static auto &obj = getControllerObj<typename traits::class_type>();
return (obj.*func_)(req, std::move(values)...);
}
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<isClassFunction && isDrObjectClass && isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&...values)
{
static auto objPtr =
DrClassMap::getSingleInstance<typename traits::class_type>();
return (*objPtr.*func_)(req, std::move(values)...);
}
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<!isClassFunction && isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&...values)
{
return func_(req, std::move(values)...);
}
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<isClassFunction && !isDrObjectClass && !isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&...values)
{
static auto &obj = getControllerObj<typename traits::class_type>();
return (obj.*func_)((*req), std::move(values)...);
}
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<isClassFunction && isDrObjectClass && !isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&...values)
{
static auto objPtr =
DrClassMap::getSingleInstance<typename traits::class_type>();
return (*objPtr.*func_)((*req), std::move(values)...);
}
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<!isClassFunction && !isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&...values)
{
return func_((*req), std::move(values)...);
} }
}; };

View File

@ -21,6 +21,7 @@
#include <drogon/HttpRequest.h> #include <drogon/HttpRequest.h>
#include <trantor/utils/NonCopyable.h> #include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h> #include <trantor/net/EventLoop.h>
#include <cstddef>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <future> #include <future>
@ -63,7 +64,7 @@ struct HttpRespAwaiter : public CallbackAwaiter<HttpResponsePtr>
* If the connection is broken, the client attempts to reconnect * If the connection is broken, the client attempts to reconnect
* when calling the sendRequest method. * when calling the sendRequest method.
* *
* Using the static mathod newHttpClient(...) to get shared_ptr of the object * Using the static method newHttpClient(...) to get shared_ptr of the object
* implementing the class, the shared_ptr is retained in the framework until all * implementing the class, the shared_ptr is retained in the framework until all
* response callbacks are invoked without fear of accidental deconstruction. * response callbacks are invoked without fear of accidental deconstruction.
* *
@ -178,6 +179,12 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
*/ */
virtual void setSockOptCallback(std::function<void(int)> cb) = 0; virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
/**
* @brief Return the number of unsent http requests in the current http
* client cache buffer
*/
virtual std::size_t requestsBufferSize() = 0;
/// Set the pipelining depth, which is the number of requests that are not /// Set the pipelining depth, which is the number of requests that are not
/// responding. /// responding.
/** /**
@ -224,7 +231,7 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
virtual void setUserAgent(const std::string &userAgent) = 0; virtual void setUserAgent(const std::string &userAgent) = 0;
/** /**
* @brief Creaet a new HTTP client which use ip and port to connect to * @brief Create a new HTTP client which use ip and port to connect to
* server * server
* *
* @param ip The ip address of the HTTP server * @param ip The ip address of the HTTP server
@ -235,7 +242,7 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
* HttpAppFramework's event loop, otherwise it runs in the loop identified * HttpAppFramework's event loop, otherwise it runs in the loop identified
* by the parameter. * by the parameter.
* @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are * @param useOldTLS If the parameter is set to true, the TLS1.0/1.1 are
* eanbled for HTTPS. * enabled for HTTPS.
* @param validateCert If the parameter is set to true, the client validates * @param validateCert If the parameter is set to true, the client validates
* the server certificate when SSL handshaking. * the server certificate when SSL handshaking.
* @return HttpClientPtr The smart pointer to the new client object. * @return HttpClientPtr The smart pointer to the new client object.

View File

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

View File

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

View File

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

View File

@ -30,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
{ {
@ -136,7 +137,7 @@ class DROGON_EXPORT HttpRequest
/// Get the header string identified by the key parameter. /// Get the header string identified by the key parameter.
/** /**
* @note * @note
* If there is no the header, a empty string is retured. * If there is no the header, a empty string is returned.
* The key is case insensitive * The key is case insensitive
*/ */
virtual const std::string &getHeader(std::string key) const = 0; virtual const std::string &getHeader(std::string key) const = 0;
@ -162,31 +163,34 @@ class DROGON_EXPORT HttpRequest
virtual const std::string &getCookie(const std::string &field) const = 0; virtual const std::string &getCookie(const std::string &field) const = 0;
/// Get all headers of the request /// Get all headers of the request
virtual const std:: virtual const SafeStringMap<std::string> &headers() const = 0;
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&headers() const = 0;
/// Get all headers of the request /// Get all headers of the request
const std:: const SafeStringMap<std::string> &getHeaders() const
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getHeaders() const
{ {
return headers(); return headers();
} }
/// Get all cookies of the request /// Get all cookies of the request
virtual const std:: virtual const SafeStringMap<std::string> &cookies() const = 0;
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&cookies() const = 0;
/// Get all cookies of the request /// Get all cookies of the request
const std:: const SafeStringMap<std::string> &getCookies() const
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getCookies() const
{ {
return cookies(); return cookies();
} }
/**
* @brief Return content length parsed from the Content-Length header
* If no Content-Length header, return null.
*/
virtual size_t realContentLength() const = 0;
size_t getRealContentLength() const
{
return realContentLength();
}
/// Get the query string of the request. /// Get the query string of the request.
/** /**
* The query string is the substring after the '?' in the URL string. * The query string is the substring after the '?' in the URL string.
@ -269,7 +273,7 @@ class DROGON_EXPORT HttpRequest
/// Return the enum type version of the request. /// Return the enum type version of the request.
/** /**
* kHttp10 means Http version is 1.0 * kHttp10 means Http version is 1.0
* kHttp11 means Http verison is 1.1 * kHttp11 means Http version is 1.1
*/ */
virtual Version version() const = 0; virtual Version version() const = 0;
@ -300,14 +304,10 @@ class DROGON_EXPORT HttpRequest
} }
/// Get parameters of the request. /// Get parameters of the request.
virtual const std:: virtual const SafeStringMap<std::string> &parameters() const = 0;
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&parameters() const = 0;
/// Get parameters of the request. /// Get parameters of the request.
const std:: const SafeStringMap<std::string> &getParameters() const
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getParameters() const
{ {
return parameters(); return parameters();
} }
@ -316,7 +316,7 @@ class DROGON_EXPORT HttpRequest
virtual const std::string &getParameter(const std::string &key) const = 0; virtual const std::string &getParameter(const std::string &key) const = 0;
/** /**
* @brief Get the optional parameter identified by the @param key. if the * @brief Get the optional parameter identified by the @p key. if the
* parameter doesn't exist, or the original parameter can't be converted to * parameter doesn't exist, or the original parameter can't be converted to
* a T type object, an empty optional object is returned. * a T type object, an empty optional object is returned.
* *
@ -450,13 +450,12 @@ class DROGON_EXPORT HttpRequest
virtual void setCustomContentTypeString(const std::string &type) = 0; virtual void setCustomContentTypeString(const std::string &type) = 0;
/// Add a cookie /// Add a cookie
virtual void addCookie(const std::string &key, virtual void addCookie(std::string key, std::string value) = 0;
const std::string &value) = 0;
/** /**
* @brief Set the request object to the pass-through mode or not. It's not * @brief Set the request object to the pass-through mode or not. It's not
* by default when a new request object is created. * by default when a new request object is created.
* In pass-through mode, no addtional headers (including user-agent, * In pass-through mode, no additional headers (including user-agent,
* connection, etc.) are added to the request. This mode is useful for some * connection, etc.) are added to the request. This mode is useful for some
* applications such as a proxy. * applications such as a proxy.
* *
@ -506,6 +505,11 @@ class DROGON_EXPORT HttpRequest
virtual void setContentTypeString(const char *typeString, virtual void setContentTypeString(const char *typeString,
size_t typeStringLength) = 0; size_t typeStringLength) = 0;
virtual bool connected() const noexcept = 0;
virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
const noexcept = 0;
virtual ~HttpRequest() virtual ~HttpRequest()
{ {
} }

View File

@ -15,8 +15,11 @@
#include <drogon/exports.h> #include <drogon/exports.h>
#include <trantor/net/Certificate.h> #include <trantor/net/Certificate.h>
#include <trantor/net/callbacks.h>
#include <trantor/net/AsyncStream.h>
#include <drogon/DrClassMap.h> #include <drogon/DrClassMap.h>
#include <drogon/Cookie.h> #include <drogon/Cookie.h>
#include <drogon/HttpRequest.h>
#include <drogon/HttpTypes.h> #include <drogon/HttpTypes.h>
#include <drogon/HttpViewData.h> #include <drogon/HttpViewData.h>
#include <drogon/utils/Utilities.h> #include <drogon/utils/Utilities.h>
@ -68,6 +71,48 @@ inline HttpResponsePtr toResponse<Json::Value &>(Json::Value &pJson)
return toResponse((const Json::Value &)pJson); return toResponse((const Json::Value &)pJson);
} }
class DROGON_EXPORT ResponseStream
{
public:
explicit ResponseStream(trantor::AsyncStreamPtr asyncStream)
: asyncStream_(std::move(asyncStream))
{
}
~ResponseStream()
{
close();
}
bool send(const std::string &data)
{
if (!asyncStream_)
{
return false;
}
std::ostringstream oss;
oss << std::hex << data.length() << "\r\n";
oss << data << "\r\n";
return asyncStream_->send(oss.str());
}
void close()
{
if (asyncStream_)
{
static std::string closeStream{"0\r\n\r\n"};
asyncStream_->send(closeStream);
asyncStream_->close();
asyncStream_.reset();
}
}
private:
trantor::AsyncStreamPtr asyncStream_;
};
using ResponseStreamPtr = std::unique_ptr<ResponseStream>;
class DROGON_EXPORT HttpResponse class DROGON_EXPORT HttpResponse
{ {
public: public:
@ -171,7 +216,7 @@ class DROGON_EXPORT HttpResponse
setContentTypeCodeAndCustomString(type, typeString, N - 1); setContentTypeCodeAndCustomString(type, typeString, N - 1);
} }
/// Set the reponse content type and the character set. /// Set the response content type and the character set.
/// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const /// virtual void setContentTypeCodeAndCharacterSet(ContentType type, const
/// std::string &charSet = "utf-8") = 0; /// std::string &charSet = "utf-8") = 0;
@ -186,7 +231,7 @@ class DROGON_EXPORT HttpResponse
/// Get the header string identified by the key parameter. /// Get the header string identified by the key parameter.
/** /**
* @note * @note
* If there is no the header, a empty string is retured. * If there is no the header, a empty string is returned.
* The key is case insensitive * The key is case insensitive
*/ */
virtual const std::string &getHeader(std::string key) const = 0; virtual const std::string &getHeader(std::string key) const = 0;
@ -199,14 +244,10 @@ class DROGON_EXPORT HttpResponse
virtual void removeHeader(std::string key) = 0; virtual void removeHeader(std::string key) = 0;
/// Get all headers of the response /// Get all headers of the response
virtual const std:: virtual const SafeStringMap<std::string> &headers() const = 0;
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&headers() const = 0;
/// Get all headers of the response /// Get all headers of the response
const std:: const SafeStringMap<std::string> &getHeaders() const
unordered_map<std::string, std::string, utils::internal::SafeStringHash>
&getHeaders() const
{ {
return headers(); return headers();
} }
@ -230,18 +271,14 @@ class DROGON_EXPORT HttpResponse
virtual void addCookie(Cookie &&cookie) = 0; virtual void addCookie(Cookie &&cookie) = 0;
/// Get the cookie identified by the key parameter. /// Get the cookie identified by the key parameter.
/// If there is no the cookie, the empty cookie is retured. /// If there is no the cookie, the empty cookie is returned.
virtual const Cookie &getCookie(const std::string &key) const = 0; virtual const Cookie &getCookie(const std::string &key) const = 0;
/// Get all cookies. /// Get all cookies.
virtual const std:: virtual const SafeStringMap<Cookie> &cookies() const = 0;
unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
&cookies() const = 0;
/// Get all cookies. /// Get all cookies.
const std:: const SafeStringMap<Cookie> &getCookies() const
unordered_map<std::string, Cookie, utils::internal::SafeStringHash>
&getCookies() const
{ {
return cookies(); return cookies();
} }
@ -290,7 +327,7 @@ class DROGON_EXPORT HttpResponse
/// Return the enum type version of the response. /// Return the enum type version of the response.
/** /**
* kHttp10 means Http version is 1.0 * kHttp10 means Http version is 1.0
* kHttp11 means Http verison is 1.1 * kHttp11 means Http version is 1.1
*/ */
virtual Version version() const = 0; virtual Version version() const = 0;
@ -300,7 +337,7 @@ class DROGON_EXPORT HttpResponse
return version(); return version();
} }
/// Reset the reponse object to its initial state /// Reset the response object to its initial state
virtual void clear() = 0; virtual void clear() = 0;
/// Set the expiration time of the response cache in memory. /// Set the expiration time of the response cache in memory.
@ -318,7 +355,7 @@ class DROGON_EXPORT HttpResponse
/// Get the json object from the server response. /// Get the json object from the server response.
/// If the response is not in json format, then a empty shared_ptr is /// If the response is not in json format, then a empty shared_ptr is
/// retured. /// returned.
virtual const std::shared_ptr<Json::Value> &jsonObject() const = 0; virtual const std::shared_ptr<Json::Value> &jsonObject() const = 0;
const std::shared_ptr<Json::Value> &getJsonObject() const const std::shared_ptr<Json::Value> &getJsonObject() const
@ -337,9 +374,9 @@ class DROGON_EXPORT HttpResponse
virtual const std::string &getJsonError() const = 0; virtual const std::string &getJsonError() const = 0;
/** /**
* @brief Set the reponse object to the pass-through mode or not. It's not * @brief Set the response object to the pass-through mode or not. It's not
* by default when a new response object is created. * by default when a new response object is created.
* In pass-through mode, no addtional headers (including server, date, * In pass-through mode, no additional headers (including server, date,
* content-type and content-length, etc.) are added to the response. This * content-type and content-length, etc.) are added to the response. This
* mode is useful for some applications such as a proxy. * mode is useful for some applications such as a proxy.
* *
@ -368,7 +405,8 @@ class DROGON_EXPORT HttpResponse
static HttpResponsePtr newHttpResponse(HttpStatusCode code, static HttpResponsePtr newHttpResponse(HttpStatusCode code,
ContentType type); ContentType type);
/// Create a response which returns a 404 page. /// Create a response which returns a 404 page.
static HttpResponsePtr newNotFoundResponse(); static HttpResponsePtr newNotFoundResponse(
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response which returns a json object. Its content-type is set /// Create a response which returns a json object. Its content-type is set
/// to application/json. /// to application/json.
static HttpResponsePtr newHttpJsonResponse(const Json::Value &data); static HttpResponsePtr newHttpJsonResponse(const Json::Value &data);
@ -384,7 +422,8 @@ class DROGON_EXPORT HttpResponse
*/ */
static HttpResponsePtr newHttpViewResponse( static HttpResponsePtr newHttpViewResponse(
const std::string &viewName, const std::string &viewName,
const HttpViewData &data = HttpViewData()); const HttpViewData &data = HttpViewData(),
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that returns a redirection page, redirecting to /// Create a response that returns a redirection page, redirecting to
/// another page located in the location parameter. /// another page located in the location parameter.
@ -411,7 +450,8 @@ class DROGON_EXPORT HttpResponse
const std::string &fullPath, const std::string &fullPath,
const std::string &attachmentFileName = "", const std::string &attachmentFileName = "",
ContentType type = CT_NONE, ContentType type = CT_NONE,
const std::string &typeString = ""); const std::string &typeString = "",
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that returns part of a file to the client. /// Create a response that returns part of a file to the client.
/** /**
@ -437,7 +477,8 @@ class DROGON_EXPORT HttpResponse
bool setContentRange = true, bool setContentRange = true,
const std::string &attachmentFileName = "", const std::string &attachmentFileName = "",
ContentType type = CT_NONE, ContentType type = CT_NONE,
const std::string &typeString = ""); const std::string &typeString = "",
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that returns a file to the client from buffer in /// Create a response that returns a file to the client from buffer in
/// memory/stack /// memory/stack
@ -463,23 +504,43 @@ class DROGON_EXPORT HttpResponse
/** /**
* @note if the Connection is keep-alive and the Content-Length header is * @note if the Connection is keep-alive and the Content-Length header is
* not set, the stream data is sent with Transfer-Encoding: chunked. * not set, the stream data is sent with Transfer-Encoding: chunked.
* @param function to retrieve the stream data (stream ends when a zero size * @param callback function to retrieve the stream data (stream ends when a
* is returned) the callback will be called with nullptr when the send is * zero size is returned) the callback will be called with
* finished/interruped so that it cleans up its internals. * nullptr when the send is finished/interrupted so that it
* cleans up its internals.
* @param attachmentFileName if the parameter is not empty, the browser * @param attachmentFileName if the parameter is not empty, the browser
* does not open the file, but saves it as an * does not open the file, but saves it as an
* attachment. * attachment.
* @param type the content type code. If the parameter is CT_NONE, the * @param type the content type code. If the parameter is CT_NONE, the
* content type is set by drogon based on the file extension and * content type is set by drogon based on the file extension and
* typeString. Set it to CT_CUSTOM when no drogon internal content type * typeString. Set it to CT_CUSTOM when no drogon internal
* matches. * content type matches.
* @param typeString the MIME string of the content type. * @param typeString the MIME string of the content type.
*/ */
static HttpResponsePtr newStreamResponse( static HttpResponsePtr newStreamResponse(
const std::function<std::size_t(char *, std::size_t)> &callback, const std::function<std::size_t(char *, std::size_t)> &callback,
const std::string &attachmentFileName = "", const std::string &attachmentFileName = "",
ContentType type = CT_NONE, ContentType type = CT_NONE,
const std::string &typeString = ""); const std::string &typeString = "",
const HttpRequestPtr &req = HttpRequestPtr());
/// Create a response that allows sending asynchronous data from a callback
/// function
/**
* @note Async streams are always sent with Transfer-Encoding: chunked.
* @param callback function that receives the asynchronous HTTP stream. You
* may call the stream->send() method to transmit new data.
* The send method will return true as long as the stream is
* still open. Once you have finished sending data, or the
* stream->send() function returned false, you should call
* stream->close() to gracefully close the chunked transfer.
* @param disableKickoffTimeout set this to true to disable trantors default
* kickoff timeout. This is useful if you need
* long running asynchronous streams.
*/
static HttpResponsePtr newAsyncStreamResponse(
const std::function<void(ResponseStreamPtr)> &callback,
bool disableKickoffTimeout = false);
/** /**
* @brief Create a custom HTTP response object. For using this template, * @brief Create a custom HTTP response object. For using this template,
@ -500,7 +561,7 @@ class DROGON_EXPORT HttpResponse
/** /**
* @brief Returns the range of the file response as a pair ot size_t * @brief Returns the range of the file response as a pair ot size_t
* (offset, length). Length of 0 means the entire file is sent. Behaivor of * (offset, length). Length of 0 means the entire file is sent. Behavior of
* this function is undefined if the response if not a file response * this function is undefined if the response if not a file response
*/ */
using SendfileRange = std::pair<size_t, size_t>; // { offset, length } using SendfileRange = std::pair<size_t, size_t>; // { offset, length }
@ -511,8 +572,15 @@ class DROGON_EXPORT HttpResponse
* newStreamResponse) returns the callback function. Otherwise a * newStreamResponse) returns the callback function. Otherwise a
* null function. * null function.
*/ */
virtual const std::function<std::size_t(char *, std::size_t)> virtual const std::function<std::size_t(char *, std::size_t)> &
&streamCallback() const = 0; streamCallback() const = 0;
/**
* @brief If the response is a async stream response (i.e. created by
* asyncStreamCallback) returns the stream ptr.
*/
virtual const std::function<void(ResponseStreamPtr)> &asyncStreamCallback()
const = 0;
/** /**
* @brief Returns the content type associated with the response * @brief Returns the content type associated with the response

View File

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

View File

@ -106,26 +106,71 @@ enum ContentType
CT_APPLICATION_X_JAVASCRIPT [[deprecated("use CT_TEXT_JAVASCRIPT")]], CT_APPLICATION_X_JAVASCRIPT [[deprecated("use CT_TEXT_JAVASCRIPT")]],
CT_TEXT_JAVASCRIPT, CT_TEXT_JAVASCRIPT,
CT_TEXT_CSS, CT_TEXT_CSS,
CT_TEXT_XML, CT_TEXT_CSV,
CT_APPLICATION_XML, CT_TEXT_XML, // suggests human readable xml
CT_APPLICATION_XML, // suggest machine-to-machine xml
CT_TEXT_XSL, CT_TEXT_XSL,
CT_APPLICATION_WASM, CT_APPLICATION_WASM,
CT_APPLICATION_OCTET_STREAM, CT_APPLICATION_OCTET_STREAM,
CT_APPLICATION_X_FONT_TRUETYPE,
CT_APPLICATION_X_FONT_OPENTYPE,
CT_APPLICATION_FONT_WOFF, CT_APPLICATION_FONT_WOFF,
CT_APPLICATION_FONT_WOFF2, CT_APPLICATION_FONT_WOFF2,
CT_APPLICATION_VND_MS_FONTOBJ, CT_APPLICATION_GZIP,
CT_APPLICATION_JAVA_ARCHIVE,
CT_APPLICATION_PDF, CT_APPLICATION_PDF,
CT_IMAGE_SVG_XML, CT_APPLICATION_MSWORD,
CT_IMAGE_PNG, CT_APPLICATION_MSWORDX,
CT_IMAGE_WEBP, CT_APPLICATION_VND_MS_FONTOBJ,
CT_APPLICATION_VND_RAR,
CT_APPLICATION_XHTML,
CT_APPLICATION_X_7Z,
CT_APPLICATION_X_BZIP,
CT_APPLICATION_X_BZIP2,
CT_APPLICATION_X_HTTPD_PHP,
CT_APPLICATION_X_FONT_TRUETYPE,
CT_APPLICATION_X_FONT_OPENTYPE,
CT_APPLICATION_X_TAR,
CT_APPLICATION_X_TGZ,
CT_APPLICATION_X_XZ,
CT_APPLICATION_ZIP,
CT_AUDIO_AAC,
CT_AUDIO_AC3,
CT_AUDIO_AIFF,
CT_AUDIO_FLAC,
CT_AUDIO_MATROSKA,
CT_AUDIO_MPEG,
CT_AUDIO_MPEG4,
CT_AUDIO_OGG,
CT_AUDIO_WAVE,
CT_AUDIO_WEBM,
CT_AUDIO_X_APE,
CT_AUDIO_X_MS_WMA,
CT_AUDIO_X_TTA,
CT_AUDIO_X_WAVPACK,
CT_IMAGE_APNG,
CT_IMAGE_AVIF, CT_IMAGE_AVIF,
CT_IMAGE_JPG,
CT_IMAGE_GIF,
CT_IMAGE_XICON,
CT_IMAGE_ICNS,
CT_IMAGE_BMP, CT_IMAGE_BMP,
CT_IMAGE_GIF,
CT_IMAGE_ICNS,
CT_IMAGE_JPG,
CT_IMAGE_JP2,
CT_IMAGE_PNG,
CT_IMAGE_SVG_XML,
CT_IMAGE_TIFF,
CT_IMAGE_WEBP,
CT_IMAGE_X_MNG,
CT_IMAGE_X_TGA,
CT_IMAGE_XICON,
CT_VIDEO_APG,
CT_VIDEO_AV1,
CT_VIDEO_QUICKTIME,
CT_VIDEO_MATROSKA,
CT_VIDEO_MP4,
CT_VIDEO_MPEG,
CT_VIDEO_MPEG2TS,
CT_VIDEO_OGG,
CT_VIDEO_WEBM,
CT_VIDEO_X_M4V,
CT_VIDEO_X_MSVIDEO,
CT_MULTIPART_FORM_DATA, CT_MULTIPART_FORM_DATA,
CT_CUSTOM CT_CUSTOM
}; };
@ -216,4 +261,34 @@ inline trantor::LogStream &operator<<(trantor::LogStream &out,
{ {
return out << to_string_view(result); return out << to_string_view(result);
} }
inline std::string_view to_string_view(drogon::HttpMethod method)
{
switch (method)
{
case drogon::HttpMethod::Get:
return "GET";
case drogon::HttpMethod::Post:
return "POST";
case drogon::HttpMethod::Head:
return "HEAD";
case drogon::HttpMethod::Put:
return "PUT";
case drogon::HttpMethod::Delete:
return "DELETE";
case drogon::HttpMethod::Options:
return "OPTIONS";
case drogon::HttpMethod::Patch:
return "PATCH";
default:
return "INVALID";
}
}
inline std::string to_string(drogon::HttpMethod method)
{
auto sv = to_string_view(method);
return std::string(sv.data(), sv.size());
}
} // namespace drogon } // namespace drogon

View File

@ -37,7 +37,7 @@ class DROGON_EXPORT HttpViewData
template <typename T> template <typename T>
const T &get(const std::string &key) const const T &get(const std::string &key) const
{ {
const static T nullVal = T(); static const T nullVal = T();
auto it = viewData_.find(key); auto it = viewData_.find(key);
if (it != viewData_.end()) if (it != viewData_.end())
{ {
@ -74,7 +74,7 @@ class DROGON_EXPORT HttpViewData
viewData_[key] = ss.str(); viewData_[key] = ss.str();
} }
/// Insert a formated string identified by the key parameter. /// Insert a formatted string identified by the key parameter.
void insertFormattedString(const std::string &key, const char *format, ...) void insertFormattedString(const std::string &key, const char *format, ...)
{ {
std::string strBuffer; std::string strBuffer;

View File

@ -90,7 +90,7 @@ class IOThreadStorage : public trantor::NonCopyable
} }
/** /**
* @brief Get the thread storage asociate with the current thread * @brief Get the thread storage associate with the current thread
* *
* This function may only be called in a request handler * This function may only be called in a request handler
*/ */

View File

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

View File

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

View File

@ -57,7 +57,7 @@ class Topic : public trantor::NonCopyable
} }
/** /**
* @brief Subcribe to the topic. * @brief Subscribe to the topic.
* *
* @param handler is invoked when a message arrives. * @param handler is invoked when a message arrives.
* @return SubscriberID * @return SubscriberID
@ -70,7 +70,7 @@ class Topic : public trantor::NonCopyable
} }
/** /**
* @brief Subcribe to the topic. * @brief Subscribe to the topic.
* *
* @param handler is invoked when a message arrives. * @param handler is invoked when a message arrives.
* @return SubscriberID * @return SubscriberID
@ -175,6 +175,9 @@ class PubSubService : public trantor::NonCopyable
/** /**
* @brief Subscribe to a topic. When a message is published to the topic, * @brief Subscribe to a topic. When a message is published to the topic,
* the handler is invoked by passing the topic and message as parameters. * the handler is invoked by passing the topic and message as parameters.
* @param topicName Topic name.
* @param handler The message handler.
* @return The subscriber ID.
*/ */
SubscriberID subscribe(const std::string &topicName, SubscriberID subscribe(const std::string &topicName,
MessageHandler &&handler) MessageHandler &&handler)
@ -189,7 +192,7 @@ class PubSubService : public trantor::NonCopyable
/** /**
* @brief Unsubscribe from a topic. * @brief Unsubscribe from a topic.
* *
* @param topic * @param topicName Topic name.
* @param id The subscriber ID returned from the subscribe method. * @param id The subscriber ID returned from the subscribe method.
*/ */
void unsubscribe(const std::string &topicName, SubscriberID id) void unsubscribe(const std::string &topicName, SubscriberID id)
@ -243,6 +246,31 @@ class PubSubService : public trantor::NonCopyable
topicMap_.erase(topicName); topicMap_.erase(topicName);
} }
/**
* @brief Check if a topic is empty.
*
* @param topicName The topic name.
* @return true means there are no subscribers.
* @return false means there are subscribers in the topic.
*/
bool isTopicEmpty(const std::string &topicName) const
{
std::shared_ptr<Topic<MessageType>> topicPtr;
{
std::shared_lock<SharedMutex> lock(mutex_);
auto iter = topicMap_.find(topicName);
if (iter != topicMap_.end())
{
topicPtr = iter->second;
}
else
{
return true;
}
}
return topicPtr->empty();
}
private: private:
std::unordered_map<std::string, std::shared_ptr<Topic<MessageType>>> std::unordered_map<std::string, std::shared_ptr<Topic<MessageType>>>
topicMap_; topicMap_;

View File

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

View File

@ -182,7 +182,7 @@ class Session
} }
/** /**
* @brief Retrun true if the data identified by the key exists. * @brief Return true if the data identified by the key exists.
*/ */
bool find(const std::string &key) bool find(const std::string &key)
{ {

View File

@ -28,8 +28,7 @@ class UploadFile
/** /**
* @param filePath The file location on local host, including file name. * @param filePath The file location on local host, including file name.
* @param fileName The file name provided to the server. If it is empty by * @param fileName The file name provided to the server. If it is empty by
* default, the file name in the @param filePath * default, the file name in the @p filePath is provided to the server.
* is provided to the server.
* @param itemName The item name on the browser form. * @param itemName The item name on the browser form.
* @param contentType The Mime content type for the part * @param contentType The Mime content type for the part
*/ */

View File

@ -65,7 +65,7 @@ class DROGON_EXPORT WebSocketClient
virtual WebSocketConnectionPtr getConnection() = 0; virtual WebSocketConnectionPtr getConnection() = 0;
/** /**
* @brief Set messages handler. When a message is recieved from the server, * @brief Set messages handler. When a message is received from the server,
* the callback is called. * the callback is called.
* *
* @param callback The function to call when a message is received. * @param callback The function to call when a message is received.
@ -76,8 +76,7 @@ class DROGON_EXPORT WebSocketClient
const WebSocketMessageType &)> &callback) = 0; const WebSocketMessageType &)> &callback) = 0;
/// Set the connection closing handler. When the connection is established /// Set the connection closing handler. When the connection is established
/// or closed, the @param callback is called with a bool parameter. /// or closed, the @p callback is called with a bool parameter.
/** /**
* @brief Set the connection closing handler. When the websocket connection * @brief Set the connection closing handler. When the websocket connection
* is closed, the callback is called * is closed, the callback is called
@ -91,9 +90,34 @@ class DROGON_EXPORT WebSocketClient
virtual void connectToServer(const HttpRequestPtr &request, virtual void connectToServer(const HttpRequestPtr &request,
const WebSocketRequestCallback &callback) = 0; const WebSocketRequestCallback &callback) = 0;
/**
* @brief Set the client certificate used by the HTTP connection
*
* @param cert Path to the certificate
* @param key Path to the certificate's private key
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
*/
virtual void setCertPath(const std::string &cert,
const std::string &key) = 0;
/**
* @brief Supplies command style options for `SSL_CONF_cmd`
*
* @param sslConfCmds options for SSL_CONF_cmd
* @note this method has no effect if the HTTP client is communicating via
* unencrypted HTTP
* @code
addSSLConfigs({{"-dhparam", "/path/to/dhparam"}, {"-strict", ""}});
* @endcode
*/
virtual void addSSLConfigs(
const std::vector<std::pair<std::string, std::string>>
&sslConfCmds) = 0;
#ifdef __cpp_impl_coroutine #ifdef __cpp_impl_coroutine
/** /**
* @brief Set messages handler. When a message is recieved from the server, * @brief Set messages handler. When a message is received from the server,
* the callback is called. * the callback is called.
* *
* @param callback The function to call when a message is received. * @param callback The function to call when a message is received.

View File

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

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