Compare commits

...

74 Commits

Author SHA1 Message Date
qicosmos 1f1ba5236e
[metric][improve]improve metric (#693) 2024-06-19 10:05:11 +08:00
qicosmos 4f4b010bcc
[metric][feat]improve metrics (#692) 2024-06-14 16:55:12 +08:00
qicosmos ef012e6327
fix url queries (#691) 2024-06-12 18:48:50 +08:00
Brian a66d642f02
Update easylog.hpp (#690) 2024-06-09 18:00:23 +08:00
qicosmos 7f2568ed24 Merge branch 'main' of https://github.com/alibaba/yalantinglibs into main 2024-06-07 23:29:19 +08:00
qicosmos 03245efb48 doc 2024-06-07 23:28:54 +08:00
qicosmos ea0d790355 update version to 0.3.3 2024-06-07 23:21:32 +08:00
qicosmos c1dcf9f8d7 update version to 0.3.0 2024-06-07 23:20:01 +08:00
qicosmos c0006099ab
[new feature]Add metrics (#674) 2024-06-07 23:15:51 +08:00
saipubw 9d2966424e
[struct_pack] support 256 field (#688) 2024-06-04 16:27:30 +08:00
qicosmos 9a7b36ac0a
fix ssl option (#687) 2024-05-31 15:57:06 +08:00
yinghaoyu 9c7cffe056
[easylog] fix last_tid (#686) 2024-05-29 14:57:00 +08:00
qicosmos 476b5842d4
[struct_pb]Refact struct pb (#680) 2024-05-29 11:43:20 +08:00
saipubw 350fbc4352
[no ci] fix doc (#685) 2024-05-27 14:36:34 +08:00
saipubw 2e5396cbc8
[no ci] fix doc (#684) 2024-05-24 12:13:38 +08:00
saipubw ca8f1341bb
[no ci] fix doc (#683) 2024-05-24 11:52:15 +08:00
saipubw 85a0d70bf5
[no ci] fix doc (#682) 2024-05-24 11:25:26 +08:00
saipubw 5f3bd02292
[doc] add coro_rpc doc (#681)
* add coro_rpc doc

* fix doc

* update en doc

* fix code
2024-05-24 10:58:27 +08:00
qicosmos f786c5a8b9
fix and update (#679) 2024-05-21 17:20:30 +08:00
saipubw a41f755d8e
[coro_rpc]rpc client support send_request without wait for response (#672) 2024-05-17 17:59:22 +08:00
qicosmos 6e684c01ef
fix and update (#677) 2024-05-15 17:48:22 +08:00
qicosmos 9968b5b2e9
fix and improve (#673) 2024-05-10 14:18:23 +08:00
qicosmos 13485454ee
revert some code (#671) 2024-04-28 10:23:47 +08:00
qicosmos 1bb022e500
[ylt]Add version (#670) 2024-04-26 13:45:52 +08:00
qicosmos 9c2588b026
[coro_http_server]fix content_view (#668) 2024-04-25 15:27:41 +08:00
qicosmos a7716eae9d
[coro_http]fix chunked and remote address (#665) 2024-04-24 21:03:10 +08:00
qicosmos 041d9b5a57
fix ci (#667) 2024-04-24 20:19:13 +08:00
qicosmos 47b781824e
[coro_http][fix]coro_http_client tcp_no_delay as default, fix response content view (#664) 2024-04-24 16:08:40 +08:00
qicosmos cc271bddf2
avoid copy headers (#663) 2024-04-23 14:02:01 +08:00
qicosmos 0f05b4fb35
[coro_http][fix][feat]fix and support ws deflate (#662) 2024-04-23 10:40:34 +08:00
qicosmos 9641d2a8d5
[coro_http][fix][feature]coro_http (#661) 2024-04-18 16:16:15 +08:00
qicosmos 7349bd0db8
[ci][coverage] show details (#659) 2024-04-16 10:34:32 +08:00
qicosmos e6342743f8
fix timeout (#658) 2024-04-15 17:47:47 +08:00
saipubw ab0fb6b4b5
[coro_io] fix client pool slow connect bug (#657) 2024-04-10 18:10:56 +08:00
qicosmos 31b6e46971
do some fix (#656) 2024-04-08 10:33:31 +08:00
saipubw dfe3544cc2
[coro_rpc] simply rpc logic (#653)
* [coro_rpc] simply response mode

* use asio::dispatch instead of asio::post in executor

* coroutine rpc funtion start with delay

* [coro_rpc] disable context<void> as lazy rpc function parameter.

* add support for context_info

* support throw exception to return error. add test.

* fix bug

* add example

* add test for async

* [coro_rpc] refact error handle

* [coro_rpc] context support get_rpc_function_name. add log when rpc error happened.

* move rpc_error to better namespace. add support for rpc function throw rpc_error

* remove support for throw errc/err_code

* fix format

* fix format

* add example

* add support for normal function get context

* fix compile

* fix format
2024-04-03 11:56:27 +08:00
qicosmos 09aefcb51e
[coro_http.websocket]Fix websocket example (#651) 2024-03-29 18:01:23 +08:00
qicosmos 38e2b7bd01
add more restrict for write log with data/str (#652) 2024-03-29 16:57:20 +08:00
qicosmos 119abbc8f7
[coro_rpc/coro_http][feat]support set server address (#649) 2024-03-27 15:38:43 +08:00
qicosmos b487b751a1
[ci][gcc asan]test gcc asan in ubuntu20.04 (#648) 2024-03-25 12:40:44 +08:00
qicosmos 83c5dbf642
support lazy callback (#647) 2024-03-25 11:27:54 +08:00
qicosmos b8e130c57f
[coro_rpc][improve]set tcp nodelay (#646) 2024-03-22 10:19:50 +08:00
qicosmos 3b73dfa989
[coro_rpc_client][improve]avoid create timer everytime when call rpc (#643) 2024-03-20 16:21:55 +08:00
saipubw a414eea434
move iguana and cinatra to standalone subdir (#639) 2024-03-19 17:00:57 +08:00
saipubw b7b01c5836
fix install by package manager (#638) 2024-03-19 14:31:26 +08:00
saipubw 288024013e
Release v0.3.1 (#637) 2024-03-19 11:36:25 +08:00
saipubw d8836aafdc
fix cmake option & doc (#636) 2024-03-18 22:54:18 +08:00
saipubw 05daeb27cd
Update CMakeLists.txt 2024-03-18 17:11:42 +08:00
saipubw 6f59fa254a
[struct_pack] fixed serialize std::unique_ptr<T> with T is derived class. (#634) 2024-03-18 15:58:44 +08:00
qicosmos ba4685d98e
allow single arg (#632) 2024-03-18 10:59:40 +08:00
saipubw 06d28e6c04
[struct_pack] fix msvc assert failed when string resize (#631) 2024-03-17 22:22:24 +08:00
saipubw a2b65ab689
[coro_rpc] fix context<T>::get_connection_id (#624) 2024-03-16 15:34:09 +08:00
qicosmos 5f627b73b4
[coro_io][feat]support select coroutine (#620) 2024-03-16 11:36:00 +08:00
qicosmos 076fc86a16
update async_simple 6be48e7b3edde61a8a4e7ca432d25a8d9840153c (#628) 2024-03-16 10:05:13 +08:00
qicosmos ac54ad04b1
[ci][fix]try to fix ci (#626) 2024-03-16 09:12:11 +08:00
saipubw 244c92a5da
[struct_json] support user-defined serialize/deserialize (#619) 2024-03-05 16:23:09 +08:00
Lingxuan Zuo 14d1323fa6
bazel build support (#613) 2024-03-01 17:21:54 +08:00
qicosmos 6aa85d5240
add virtual destructor (#612) 2024-02-29 18:03:12 +08:00
qicosmos 4592e6eaf4
[coro_http_server][feat]update http server (#611) 2024-02-29 15:31:56 +08:00
saipubw 8fceb3d185
[coro_io] add size() for channel/client_pool (#610) 2024-02-28 16:39:48 +08:00
qicosmos d28cd6725d
[no ci]fix link (#609) 2024-02-28 10:06:46 +08:00
ggyy a9ccad6855
call coro_rpc function return bool failed crossplatform (#608) 2024-02-25 22:42:12 +08:00
俞航 e352ffa246
[struct_pack][bugfix] fix undefined macro when compiling with standard greater than 20 (#605) 2024-02-24 21:50:53 +08:00
qicosmos 9b12c0e327
[coro_http_server][feat]support reverse proxy (#604) 2024-02-21 09:50:29 +08:00
saipubw b27fadf78c
[no ci][doc] fix tab (#603) 2024-02-20 12:56:05 +08:00
saipubw 437334424f
[cmake][doc] rename cmake option, export it to find_package(), add doc. (#601) 2024-02-20 12:52:32 +08:00
qicosmos 5389caa911
[coro_io][feat]channel support WRR (#598)
* channel support WRR

* improve

* avoid use same port
2024-02-19 12:26:07 +08:00
saipubw 86e7cfeec6
[struct_pack][bugfix] base class can't optimized as no container type in compile-time. (#599) 2024-02-19 12:24:55 +08:00
PikachuHy 1b5f5199d6
export cmake option when developer use CMake FetchContent (#591)
For example, use the following cmake config
```
include(FetchContent)
FetchContent_Declare(
        yalantinglibs
        GIT_REPOSITORY https://github.com/alibaba/yalantinglibs.git
        GIT_TAG xxx # use the least commit id
        GIT_SHALLOW 1 # optional ( --depth=1 )
)
FetchContent_MakeAvailable(yalantinglibs)
add_executable(demo main.cpp)
target_link_libraries(demo PRIVATE yalantinglibs)
```

you can use `cmake -DENABLE_STRUCT_PACK_UNPORTABLE_TYPE=ON ..`,
the option `ENABLE_STRUCT_PACK_UNPORTABLE_TYPE=ON` will be used when building yalantinglibs
2024-02-05 11:30:33 +08:00
qicosmos a920fce8b0
[coro_http_client][bug]fix ssl (#587) 2024-01-31 15:50:41 +08:00
qicosmos 06103b536f
[string_resize][bug]fix resize (#586) 2024-01-30 17:14:37 +08:00
qicosmos f27793b95e
[ci]test coverage (#585) 2024-01-30 16:29:30 +08:00
qicosmos d768e13687
[coro_http_server][feat]support gzip (#584) 2024-01-30 12:04:34 +08:00
qicosmos 638c4b2ec9
fix coverage (#582) 2024-01-30 11:12:44 +08:00
324 changed files with 26791 additions and 20823 deletions

52
.bazelrc Normal file
View File

@ -0,0 +1,52 @@
# Must be first. Enables build:windows, build:linux, build:macos, build:freebsd, build:openbsd
build --enable_platform_specific_config
###############################################################################
# On Windows, provide: BAZEL_SH, and BAZEL_LLVM (if using clang-cl)
# On all platforms, provide: PYTHON3_BIN_PATH=python
###############################################################################
build --action_env=PATH
# For --compilation_mode=dbg, consider enabling checks in the standard library as well (below).
build --compilation_mode=opt
# FIXME(lingxuan.zlx) TEST CASE: test wide string crash since cxx abi off.
build --cxxopt="-D_GLIBCXX_USE_CXX11_ABI=0"
# Using C++ 20 on all platforms.
build:linux --cxxopt="-std=c++20"
build:macos --cxxopt="-std=c++20"
build:clang-cl --cxxopt="-std=c++20"
build:gcc-cl --cxxopt="-std=c++20"
build:gcc-cl --cxxopt="-fcoroutines"
build:msvc-cl --cxxopt="/std:c++20"
build:windows --cxxopt="/std:c++20"
# This workaround is needed to prevent Bazel from compiling the same file twice (once PIC and once not).
build:linux --force_pic
build:macos --force_pic
build:clang-cl --compiler=clang-cl
build:msvc-cl --compiler=msvc-cl
# `LC_ALL` and `LANG` is needed for cpp worker tests, because they will call "ray start".
# If we don't add them, python's `click` library will raise an error.
build --action_env=LC_ALL
build --action_env=LANG
# Allow C++ worker tests to execute "ray start" with the correct version of Python.
build --action_env=VIRTUAL_ENV
build --action_env=PYENV_VIRTUAL_ENV
build --action_env=PYENV_VERSION
build --action_env=PYENV_SHELL
# This is needed for some core tests to run correctly
build:windows --enable_runfiles
build:linux --per_file_copt="-\\.(asm|S)$@-Werror"
build:macos --per_file_copt="-\\.(asm|S)$@-Werror"
build:clang-cl --per_file_copt="-\\.(asm|S)$@-Werror"
build:gcc-cl --per_file_copt="-\\.(asm|S)$@-Werror"
build:msvc-cl --per_file_copt="-\\.(asm|S)$@-WX"
# Ignore warnings for protobuf generated files and external projects.
build --per_file_copt="\\.pb\\.cc$@-w"
build --per_file_copt="-\\.(asm|S)$,external/.*@-w"
#build --per_file_copt="external/.*@-Wno-unused-result"
# Ignore minor warnings for host tools, which we generally can't control
build:clang-cl --host_copt="-Wno-inconsistent-missing-override"
build:clang-cl --host_copt="-Wno-microsoft-unqualified-friend"
# Ignore wchar_t -> char conversion warning on MSVC
build:msvc-cl --per_file_copt="external/boost/libs/regex/src/wc_regex_traits\\.cpp@-wd4244"
build --http_timeout_scaling=5.0
build --verbose_failures

View File

@ -1,4 +1,4 @@
name: Bazel name: Bazel-Clang
on: on:
push: push:
@ -16,14 +16,16 @@ jobs:
- name: Install newer Clang - name: Install newer Clang
run: | run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh chmod +x ./llvm.sh
sudo ./llvm.sh 17 sudo ./llvm.sh 17
- name: Build - name: Build
working-directory: ${{github.workspace}} working-directory: ${{github.workspace}}
run: bazel build --action_env=CXX=clang++-17 --action_env=CC=clang-17 ... run: bazel build --config=clang-cl --action_env=CXX=clang++-17 --action_env=CC=clang-17 ...
- name: Test - name: Test
working-directory: ${{github.workspace}} working-directory: ${{github.workspace}}
run: bazel test --action_env=CXX=clang++-17 --action_env=CC=clang-17 --test_output=errors ... run: bazel test --config=clang-cl --action_env=CXX=clang++-17 --action_env=CC=clang-17 --test_output=errors ...

24
.github/workflows/bazel_gcc.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Bazel-GCC
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Build
working-directory: ${{github.workspace}}
run: bazel build --config=gcc-cl ...
- name: Test
working-directory: ${{github.workspace}}
run: bazel test --config=gcc-cl --test_output=errors ...

40
.github/workflows/code-coverage.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Upload CodeCov Report
on: [ push, pull_request ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
with:
fetch-depth: 2
- name: Install newer Clang
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh 17
- name: Configure
run: |
cmake -B ${{github.workspace}}/build \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=OFF -DYLT_ENABLE_SSL=ON \
-DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17
- name: Build with ${{ matrix.compiler }}
run: cmake --build ${{github.workspace}}/build --config Debug -- -j
- name: Test
working-directory: ${{github.workspace}}/build
env:
CTEST_OUTPUT_ON_FAILURE: 1
run: ctest -C ${{ matrix.configuration }} -j 1 -V
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1

View File

@ -2,8 +2,9 @@ name: Ubuntu 22.04 (llvm cov)
on: on:
pull_request_target: pull_request_target:
branches: [ doc ] branches:
workflow_dispatch: - main
- fix_coverage_show
jobs: jobs:
build: build:
@ -19,37 +20,51 @@ jobs:
sudo apt-get install libssl-dev sudo apt-get install libssl-dev
sudo apt-get install llvm sudo apt-get install llvm
- name: Install newer Clang
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh
sudo ./llvm.sh 17
- name: Run Coverage - name: Run Coverage
run: | run: |
ls ls
cp -r src/coro_rpc/tests/openssl_files . cp -r src/coro_rpc/tests/openssl_files .
ls ls
mkdir build && cd build mkdir build && cd build
CC=clang CXX=clang++ cmake .. -DCOVERAGE_TEST=ON -DENABLE_SSL=ON CC=clang-17 CXX=clang++-17 cmake .. -DCOVERAGE_TEST=ON -DYLT_ENABLE_SSL=ON
make -j test_rpc make -j
export LLVM_PROFILE_FILE="test_rpc-%m.profraw" export LLVM_PROFILE_FILE="test_ylt-%m.profraw"
./tests/test_rpc cd output
llvm-profdata merge -sparse test_rpc-*.profraw -o test_rpc.profdata cd tests
llvm-cov show ./tests/test_rpc -instr-profile=test_rpc.profdata -format=html -output-dir=../.coverage_llvm_cov -ignore-filename-regex="async_simple|thirdparty|tests|asio|util|logging|struct_pack" -show-instantiations=false ./coro_io_test
./coro_rpc_test
./easylog_test
./struct_pack_test
./struct_pack_test_with_optimize
llvm-profdata merge -sparse test_ylt-*.profraw -o test_ylt.profdata
llvm-cov show coro_io_test -object coro_rpc_test -object easylog_test -object struct_pack_test -object struct_pack_test_with_optimize -instr-profile=test_ylt.profdata -format=html -output-dir=../../.coverage_llvm_cov -ignore-filename-regex="thirdparty|asio|src" -show-instantiations=false
echo "Done!" echo "Done!"
- name: Upload Coverage Results - name: Upload Coverage Results
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: llvm-cov name: llvm-cov
path: ${{ github.workspace }}/.coverage_llvm_cov path: ${{ github.workspace }}/build/.coverage_llvm_cov
- name: Create Code Coverage Report - name: Create Code Coverage Report
working-directory: ${{github.workspace}}/build working-directory: ${{github.workspace}}/build/output/tests
run: | run: |
echo "Code Coverage Report" > tmp.log echo "Code Coverage Report" > tmp.log
echo "for detail, [goto summary](https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/actions/runs/${{github.run_id}}) download Artifacts `llvm-cov`" >> tmp.log echo "for detail, [goto summary](https://github.com/${{ github.repository_owner }}/${{ github.event.repository.name }}/actions/runs/${{github.run_id}}) download Artifacts `llvm-cov`" >> tmp.log
echo "\`\`\`" >> tmp.log echo "\`\`\`" >> tmp.log
llvm-cov report ./tests/test_rpc -instr-profile=test_rpc.profdata -ignore-filename-regex="thirdparty|tests" -show-region-summary=false >> tmp.log llvm-cov report coro_io_test -object coro_rpc_test -object easylog_test -object struct_pack_test -object struct_pack_test_with_optimize -instr-profile=test_ylt.profdata -ignore-filename-regex="thirdparty|asio|src" -show-region-summary=false >> tmp.log
echo "\`\`\`" >> tmp.log echo "\`\`\`" >> tmp.log
- name: Create Comment - name: Create Comment
uses: peter-evans/create-or-update-comment@v2 uses: peter-evans/create-or-update-comment@v2
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
body-file: '${{github.workspace}}/build/tmp.log' body-file: '${{github.workspace}}/build/output/tests/tmp.log'

View File

@ -36,7 +36,7 @@ jobs:
key: ${{ github.job }}-${{ matrix.mode}}-ssl( ${{ matrix.ssl}} ) key: ${{ github.job }}-${{ matrix.mode}}-ssl( ${{ matrix.ssl}} )
- name: Configure CMake - name: Configure CMake
run: OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3 CXX=clang++ CC=clang cmake -B ${{github.workspace}}/build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.mode}} -DENABLE_SSL=${{matrix.ssl}} -DUSE_CCACHE=ON run: OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3 CXX=clang++ CC=clang cmake -B ${{github.workspace}}/build -G Ninja -DCMAKE_BUILD_TYPE=${{matrix.mode}} -DYLT_ENABLE_SSL=${{matrix.ssl}} -DUSE_CCACHE=ON
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}}

View File

@ -30,7 +30,7 @@ jobs:
CXX=g++ CC=gcc CXX=g++ CC=gcc
cmake -B ${{github.workspace}}/build \ cmake -B ${{github.workspace}}/build \
-DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_BUILD_TYPE=Debug \
-DBUILD_CORO_HTTP=OFF -DBUILD_CORO_IO=OFF -DBUILD_CORO_RPC=OFF -DBUILD_EASYLOG=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF -DBUILD_STRUCT_PB=OFF -DBUILD_EXAMPLES=OFF -DBUILD_BENCHMARK=OFF -DBUILD_CORO_HTTP=OFF -DBUILD_CORO_IO=OFF -DBUILD_CORO_RPC=OFF -DBUILD_EASYLOG=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF -DBUILD_EXAMPLES=OFF -DBUILD_BENCHMARK=OFF
cmake --build ${{github.workspace}}/build -j cmake --build ${{github.workspace}}/build -j
cd ${{github.workspace}}/build/output/tests cd ${{github.workspace}}/build/output/tests
./struct_pack_test ./struct_pack_test

View File

@ -33,6 +33,8 @@ jobs:
- name: Install newer Clang - name: Install newer Clang
run: | run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh chmod +x ./llvm.sh
sudo ./llvm.sh 17 sudo ./llvm.sh 17
@ -45,7 +47,7 @@ jobs:
- name: Configure - name: Configure
run: | run: |
cmake -B ${{github.workspace}}/build -G Ninja \ cmake -B ${{github.workspace}}/build -G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DENABLE_SSL=${{matrix.ssl}} \ -DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DYLT_ENABLE_SSL=${{matrix.ssl}} \
-DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}}
@ -77,6 +79,8 @@ jobs:
- name: Install newer Clang - name: Install newer Clang
run: | run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh chmod +x ./llvm.sh
sudo ./llvm.sh 17 sudo ./llvm.sh 17
@ -92,9 +96,9 @@ jobs:
run: | run: |
CXX=clang++ CC=clang CXX=clang++ CC=clang
cmake -B ${{github.workspace}}/build -G Ninja \ cmake -B ${{github.workspace}}/build -G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DENABLE_SSL=${{matrix.ssl}} \ -DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DYLT_ENABLE_SSL=${{matrix.ssl}} \
-DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17\ -DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17\
-DBUILD_CORO_HTTP=OFF -DBUILD_CORO_IO=OFF -DBUILD_CORO_RPC=OFF -DBUILD_EASYLOG=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF -DBUILD_CORO_HTTP=OFF -DBUILD_CORO_IO=OFF -DBUILD_STRUCT_PB=ON -DBUILD_CORO_RPC=OFF -DBUILD_EASYLOG=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}}
@ -120,6 +124,8 @@ jobs:
- name: Install newer Clang - name: Install newer Clang
run: | run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x ./llvm.sh chmod +x ./llvm.sh
sudo ./llvm.sh 17 sudo ./llvm.sh 17
@ -138,9 +144,9 @@ jobs:
cmake -B ${{github.workspace}}/build -G Ninja \ cmake -B ${{github.workspace}}/build -G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} \ -DCMAKE_BUILD_TYPE=${{matrix.mode}} \
-DBUILD_WITH_LIBCXX=${{matrix.libcxx}} \ -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} \
-DENABLE_IO_URING=${{matrix.io_uring}} \ -DYLT_ENABLE_IO_URING=${{matrix.io_uring}} \
-DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17\ -DUSE_CCACHE=${{env.ccache}} -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17\
-DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_PACK=OFF -DBUILD_STRUCT_PB=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_PACK=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}}

View File

@ -38,7 +38,7 @@ jobs:
run: | run: |
CXX=g++ CC=gcc CXX=g++ CC=gcc
cmake -B ${{github.workspace}}/build -G Ninja \ cmake -B ${{github.workspace}}/build -G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DENABLE_SSL=${{matrix.ssl}} \ -DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DYLT_ENABLE_SSL=${{matrix.ssl}} \
-DUSE_CCACHE=${{env.ccache}} -DUSE_CCACHE=${{env.ccache}}
- name: Build - name: Build
@ -78,9 +78,9 @@ jobs:
run: | run: |
CXX=g++ CC=gcc CXX=g++ CC=gcc
cmake -B ${{github.workspace}}/build -G Ninja \ cmake -B ${{github.workspace}}/build -G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DENABLE_SSL=${{matrix.ssl}} \ -DCMAKE_BUILD_TYPE=${{matrix.mode}} -DBUILD_WITH_LIBCXX=${{matrix.libcxx}} -DYLT_ENABLE_SSL=${{matrix.ssl}} \
-DUSE_CCACHE=${{env.ccache}} \ -DUSE_CCACHE=${{env.ccache}} \
-DBUILD_CORO_HTTP=OFF -DBUILD_CORO_IO=OFF -DBUILD_CORO_RPC=OFF -DBUILD_EASYLOG=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF -DBUILD_CORO_HTTP=OFF -DBUILD_CORO_IO=OFF -DBUILD_STRUCT_PB=ON -DBUILD_CORO_RPC=OFF -DBUILD_EASYLOG=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}}
@ -116,9 +116,9 @@ jobs:
CXX=g++ CC=gcc CXX=g++ CC=gcc
cmake -B ${{github.workspace}}/build -G Ninja \ cmake -B ${{github.workspace}}/build -G Ninja \
-DCMAKE_BUILD_TYPE=${{matrix.mode}} \ -DCMAKE_BUILD_TYPE=${{matrix.mode}} \
-DENABLE_IO_URING=${{matrix.io_uring}} \ -DYLT_ENABLE_IO_URING=${{matrix.io_uring}} \
-DUSE_CCACHE=${{env.ccache}} \ -DUSE_CCACHE=${{env.ccache}} \
-DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_PACK=OFF -DBUILD_STRUCT_PB=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF -DBUILD_STRUCT_JSON=OFF -DBUILD_STRUCT_XML=OFF -DBUILD_STRUCT_PACK=OFF -DBUILD_STRUCT_YAML=OFF -DBUILD_UTIL=OFF
- name: Build - name: Build
run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}} run: cmake --build ${{github.workspace}}/build --config ${{matrix.mode}}

View File

@ -9,41 +9,41 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
windows_msvc: # windows_msvc:
runs-on: windows-latest # runs-on: windows-latest
strategy: # strategy:
matrix: # matrix:
mode: [ Release ] #[ Release, Debug ] #Debug not support ccache # mode: [ Release ] #[ Release, Debug ] #Debug not support ccache
#https://github.com/ccache/ccache/wiki/MS-Visual-Studio # #https://github.com/ccache/ccache/wiki/MS-Visual-Studio
#https://github.com/ccache/ccache/issues/1040 # #https://github.com/ccache/ccache/issues/1040
arch: [ amd64, x86 ] #[ amd64,x86 ] # arch: [ amd64, x86 ] #[ amd64,x86 ]
ssl: [ OFF ] #[ ON, OFF ] # ssl: [ OFF ] #[ ON, OFF ]
steps: # steps:
- name: Checkout # - name: Checkout
uses: actions/checkout@v3 # uses: actions/checkout@v3
- name: Enable Developer Command Prompt # - name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.12.0 # uses: ilammy/msvc-dev-cmd@v1.12.0
with: # with:
arch: ${{ matrix.arch }} # arch: ${{ matrix.arch }}
- name: Install ninja-build tool # - name: Install ninja-build tool
uses: seanmiddleditch/gha-setup-ninja@master # uses: seanmiddleditch/gha-setup-ninja@master
with: # with:
version: 1.11.1 # version: 1.11.1
- name: latest ccache # - name: latest ccache
run: choco install ccache # run: choco install ccache
- name: ccache # - name: ccache
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
with: # with:
key: ${{ github.job }}-${{ matrix.mode}}-ssl( ${{ matrix.ssl}} )-arch-${{ matrix.arch}} # key: ${{ github.job }}-${{ matrix.mode}}-ssl( ${{ matrix.ssl}} )-arch-${{ matrix.arch}}
- name: Configure CMake # - name: Configure CMake
run: cmake -B ${{github.workspace}}\build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DENABLE_SSL=${{matrix.ssl}} -DUSE_CCACHE=ON # run: cmake -B ${{github.workspace}}\build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DYLT_ENABLE_SSL=${{matrix.ssl}} -DUSE_CCACHE=ON
- name: Build # - name: Build
run: cmake --build ${{github.workspace}}\build # run: cmake --build ${{github.workspace}}\build
- name: Test # - name: Test
working-directory: ${{github.workspace}}\build # working-directory: ${{github.workspace}}\build
run: ctest -C ${{matrix.mode}} -j 1 -V # run: ctest -C ${{matrix.mode}} -j 1 -V
windows_msvc_2019: windows_msvc_2019:
runs-on: windows-2019 runs-on: windows-2019
@ -72,7 +72,7 @@ jobs:
with: with:
key: ${{ github.job }}-${{ matrix.mode}}-arch-${{ matrix.arch}} key: ${{ github.job }}-${{ matrix.mode}}-arch-${{ matrix.arch}}
- name: Configure CMake - name: Configure CMake
run: cmake -B ${{github.workspace}}\build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DENABLE_SSL=${{matrix.ssl}} -DUSE_CCACHE=ON -DENABLE_CPP_20=OFF run: cmake -B ${{github.workspace}}\build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.mode }} -DYLT_ENABLE_SSL=${{matrix.ssl}} -DBUILD_STRUCT_PB=OFF -DUSE_CCACHE=ON -DENABLE_CPP_20=OFF
- name: Build - name: Build
run: cmake --build ${{github.workspace}}\build run: cmake --build ${{github.workspace}}\build
- name: Test - name: Test

3
.gitignore vendored
View File

@ -54,3 +54,6 @@ node_modules
bazel-* bazel-*
!BUILD.bazel !BUILD.bazel
cdb.json cdb.json
# website
website/docs/zh/*/images/

View File

@ -1,13 +1,101 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//bazel:defs.bzl", "YA_BIN_COPT", "YA_LT_COPT")
package(default_visibility = ["//visibility:public"]) package(default_visibility = ["//visibility:public"])
cc_library( cc_library(
name = "ylt", name = "ylt",
hdrs = glob([ srcs = glob([
"include/**", "include/ylt/**/*.hpp",
"src/include/**" "include/ylt/**/*.h",
"include/ylt/**/*.ipp",
"src/include/*.h",
]), ]),
includes = ["include", "include/ylt/thirdparty","src/include"], copts = YA_LT_COPT,
includes = [
"include",
"include/ylt",
"include/ylt/thirdparty",
"include/ylt/standalone",
"src/include",
],
linkopts = ["-lpthread"], linkopts = ["-lpthread"],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
# List one example for ylt tests.
cc_test(
name = "easylog_test",
srcs = [
"src/easylog/tests/main.cpp",
"src/easylog/tests/test_easylog.cpp",
],
copts = YA_BIN_COPT,
includes = [
"include",
"include/ylt/thirdparty",
"include/ylt/standalone",
"src/include",
],
deps = [":ylt"],
)
cc_binary(
name = "easylog_benchmark",
srcs = [
"src/easylog/benchmark/main.cpp",
],
copts = YA_BIN_COPT,
includes = [
"include",
"include/ylt/thirdparty",
"include/ylt/standalone",
"src/include",
],
deps = [":ylt"],
)
cc_binary(
name = "coro_http_example",
srcs = ["src/coro_http/examples/example.cpp"],
copts = YA_BIN_COPT,
includes = [
"include",
"include/ylt",
"include/ylt/thirdparty",
"include/ylt/standalone",
"src/include",
],
linkopts = ["-lpthread"],
deps = [":ylt"],
)
cc_binary(
name = "coro_http_channel",
srcs = ["src/coro_http/examples/channel.cpp"],
copts = YA_BIN_COPT,
includes = [
"include",
"include/ylt",
"include/ylt/thirdparty",
"include/ylt/standalone",
"src/include",
],
linkopts = ["-lpthread"],
deps = [":ylt"],
)
cc_binary(
name = "coro_http_chat_room",
srcs = ["src/coro_http/examples/chat_room.cpp"],
copts = YA_BIN_COPT,
includes = [
"include",
"include/ylt",
"include/ylt/thirdparty",
"include/ylt/standalone",
"src/include",
],
linkopts = ["-lpthread"],
deps = [":ylt"],
)

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
project(yaLanTingLibs project(yaLanTingLibs
VERSION 0.2.9 VERSION 0.3.2
DESCRIPTION "yaLanTingLibs" DESCRIPTION "yaLanTingLibs"
HOMEPAGE_URL "https://github.com/alibaba/yalantinglibs" HOMEPAGE_URL "https://github.com/alibaba/yalantinglibs"
LANGUAGES CXX LANGUAGES CXX
@ -18,15 +18,18 @@ if(CMAKE_PROJECT_NAME STREQUAL "yaLanTingLibs") # if ylt is top-level project
# add include path # add include path
include_directories(include) include_directories(include)
include_directories(include/ylt/thirdparty) include_directories(include/ylt/thirdparty)
include_directories(include/ylt/standalone)
include_directories(src/include) include_directories(src/include)
include(cmake/utils.cmake) include(cmake/utils.cmake)
include(cmake/struct_pb.cmake)
include(cmake/build.cmake) include(cmake/build.cmake)
include(cmake/develop.cmake) include(cmake/develop.cmake)
# add project config, such as enable_ssl. # add project config, such as enable_ssl.
include(cmake/config.cmake) include(cmake/config.cmake)
# add project's source such as unit test, example & benchmark # add project's source such as unit test, example & benchmark
include(cmake/subdir.cmake) include(cmake/subdir.cmake)
else ()
# add project config, such as enable_ssl.
include(cmake/config.cmake)
endif() endif()

152
README.md
View File

@ -64,6 +64,15 @@ cd build
cmake .. cmake ..
cmake --build . --config debug # add -j, if you have enough memory to parallel compile cmake --build . --config debug # add -j, if you have enough memory to parallel compile
ctest . # run tests ctest . # run tests
```
- Build in bazel:
```shell
bazel build ylt # Please make sure bazel in you bin path.
bazel build coro_http_example # Or replace in anyone you want to build and test.
# Actually you might take it in other project in prefix @com_alibaba_yalangtinglibs, like
bazel build @com_alibaba_yalangtinglibs://ylt
``` ```
You can see the test/example/benchmark executable file in `./build/output/`. You can see the test/example/benchmark executable file in `./build/output/`.
@ -73,7 +82,6 @@ You can see the test/example/benchmark executable file in `./build/output/`.
```shell ```shell
# You can use those option to skip build unit-test & benchmark & example: # You can use those option to skip build unit-test & benchmark & example:
cmake .. -DBUILD_EXAMPLES=OFF -DBUILD_BENCHMARK=OFF -DBUILD_UNIT_TESTS=OFF cmake .. -DBUILD_EXAMPLES=OFF -DBUILD_BENCHMARK=OFF -DBUILD_UNIT_TESTS=OFF
cmake --build .
``` ```
3. install 3. install
@ -121,11 +129,11 @@ target_compile_features(main PRIVATE cxx_std_20)
### Compile Manually: ### Compile Manually:
1. Add `include/` directory to include path(skip it if you have install ylt into system default path). 1. Add `include/` directory to include path(skip it if you have install ylt into default include path).
2. Add `include/ylt/thirdparty` to include path(skip it if you have install thirdparty independency by the cmake option -DINSTALL_INDEPENDENT_THIRDPARTY=ON). 2. Add `include/ylt/thirdparty` to include path(skip it if you have install ylt by cmake).
3. Enable `c++20` standard by option `-std=c++20`(g++/clang++) or `/std:c++20`(msvc) 3. Add `include/ylt/standalone` to include path(skip it if you have install ylt by cmake).
3. If you use any header with `coro_` prefix, add link option `-pthread` in linux and add option `-fcoroutines` when you use g++. 4. Enable `c++20` standard by option `-std=c++20`(g++/clang++) or `/std:c++20`(msvc)
4. That's all. We could find other options in `example/cmakelist.txt`. 5. If you use any header with `coro_` prefix, add link option `-pthread` in linux, add option `-fcoroutines` when you use g++10.
### More Details: ### More Details:
For more details, see the cmake file [here](https://github.com/alibaba/yalantinglibs/blob/main/CMakeLists.txt) and [there](https://github.com/alibaba/yalantinglibs/tree/main/cmake). For more details, see the cmake file [here](https://github.com/alibaba/yalantinglibs/blob/main/CMakeLists.txt) and [there](https://github.com/alibaba/yalantinglibs/tree/main/cmake).
@ -321,13 +329,40 @@ void basic_usage() {
## coro_http ## coro_http
coro_http is a C++20 coroutine http(https) client, include: get/post, websocket, multipart file upload, chunked and ranges download etc. coro_http is a C++20 coroutine http(https) library, include server and client, functions: get/post, websocket, multipart file upload, chunked and ranges download etc. [more examples](https://github.com/alibaba/yalantinglibs/blob/main/src/coro_http/examples/example.cpp)
### get/post ### get/post
```c++ ```cpp
#include "ylt/coro_http/coro_http_server.hpp"
#include "ylt/coro_http/coro_http_client.hpp" #include "ylt/coro_http/coro_http_client.hpp"
using namespace coro_http; using namespace coro_http;
async_simple::coro::Lazy<void> basic_usage() {
coro_http_server server(1, 9001);
server.set_http_handler<GET>(
"/get", [](coro_http_request &req, coro_http_response &resp) {
resp.set_status_and_content(status_type::ok, "ok");
});
server.set_http_handler<GET>(
"/coro",
[](coro_http_request &req,
coro_http_response &resp) -> async_simple::coro::Lazy<void> {
resp.set_status_and_content(status_type::ok, "ok");
co_return;
});
server.aync_start(); // aync_start() don't block, sync_start() will block.
std::this_thread::sleep_for(300ms); // wait for server start
coro_http_client client{};
auto result = co_await client.async_get("http://127.0.0.1:9001/get");
assert(result.status == 200);
assert(result.resp_body == "ok");
for (auto [key, val] : result.resp_headers) {
std::cout << key << ": " << val << "\n";
}
}
async_simple::coro::Lazy<void> get_post(coro_http_client &client) { async_simple::coro::Lazy<void> get_post(coro_http_client &client) {
std::string uri = "http://www.example.com"; std::string uri = "http://www.example.com";
auto result = co_await client.async_get(uri); auto result = co_await client.async_get(uri);
@ -344,30 +379,29 @@ int main() {
``` ```
### websocket ### websocket
```c++ ```cpp
async_simple::coro::Lazy<void> websocket(coro_http_client &client) { async_simple::coro::Lazy<void> websocket(coro_http_client &client) {
client.on_ws_close([](std::string_view reason) {
std::cout << "web socket close " << reason << std::endl;
});
client.on_ws_msg([](resp_data data) {
std::cout << data.resp_body << std::endl;
});
// connect to your websocket server. // connect to your websocket server.
bool r = co_await client.async_connect("ws://example.com/ws"); bool r = co_await client.async_connect("ws://example.com/ws");
if (!r) { if (!r) {
co_return; co_return;
} }
co_await client.async_send_ws("hello websocket"); co_await client.write_websocket("hello websocket");
co_await client.async_send_ws("test again", /*need_mask = */ false); auto data = co_await client.read_websocket();
co_await client.async_send_ws_close("ws close reason"); CHECK(data.resp_body == "hello websocket");
co_await client.write_websocket("test again");
data = co_await client.read_websocket();
CHECK(data.resp_body == "test again");
co_await client.write_websocket("ws close");
data = co_await client.read_websocket();
CHECK(data.net_err == asio::error::eof);
CHECK(data.resp_body == "ws close");
} }
``` ```
### upload/download ### upload/download
```c++ ```cpp
async_simple::coro::Lazy<void> upload_files(coro_http_client &client) { async_simple::coro::Lazy<void> upload_files(coro_http_client &client) {
std::string uri = "http://example.com"; std::string uri = "http://example.com";
@ -402,16 +436,39 @@ See [async_simple](https://github.com/alibaba/async_simple)
## CMAKE OPTION ## CMAKE OPTION
These CMake options is used for yalantinglibs developing/installing itself. They are not effected for your project, because ylt is a head-only. ## config option
### INSTALL OPTION These option maybe useful for your project. You can enable it in your project if you import ylt by cmake fetchContent or find_package.
|option|default value|description|
|----------|------------|------|
|YLT_ENABLE_SSL|OFF|enable optional ssl support for rpc/http|
|YLT_ENABLE_PMR|OFF|enable pmr optimize|
|YLT_ENABLE_IO_URING|OFF|enable io_uring in linux|
|YLT_ENABLE_FILE_IO_URING|OFF|enable file io_uring as backend in linux|
|YLT_ENABLE_STRUCT_PACK_UNPORTABLE_TYPE|OFF|enable unportable type(like wstring, int128_t) for struct_pack|
|YLT_ENABLE_STRUCT_PACK_OPTIMIZE|OFF|optimize struct_pack by radical template unwinding(will cost more compile time)|
## installation option
In default, yalantinglibs will install thirdparty librarys and standalone sublibrarires in your install path independently.
If you don't want to install the thirdparty librarys(you need install it manually), you can turn off cmake option `-DINSTALL_THIRDPARTY=OFF`.
If you want to install the thirdparty dependently. (install thirdparty librarys and standalone sublibrarires in `ylt/thirdparty` and `ylt/standalone` ), you can use turn off cmake option `-DINSTALL_INDEPENDENT_THIRDPARTY=OFF` and `-DINSTALL_INDEPENDENT_STANDALONE=OFF`.
|option|default value| |option|default value|
|----------|------------| |----------|------------|
|INSTALL_THIRDPARTY|ON| |INSTALL_THIRDPARTY|ON|
|INSTALL_INDEPENDENT_THIRDPARTY|OFF| |INSTALL_STANDALONE|ON|
|INSTALL_INDEPENDENT_THIRDPARTY|ON|
|INSTALL_INDEPENDENT_STANDALONE|ON|
### ylt develop option Those options only work in installation.
## develop option
These CMake options is used for yalantinglibs developing/installing itself. They are not effected for your project, because ylt is a head-only.
|option|default value| |option|default value|
|----------|------------| |----------|------------|
@ -423,61 +480,40 @@ These CMake options is used for yalantinglibs developing/installing itself. They
|GENERATE_BENCHMARK_DATA|ON| |GENERATE_BENCHMARK_DATA|ON|
|CORO_RPC_USE_OTHER_RPC|ON| |CORO_RPC_USE_OTHER_RPC|ON|
### ylt config option
These option maybe useful for your project. If you want to enable it in your project, see the cmake code [here](https://github.com/alibaba/yalantinglibs/tree/main/cmake/config.cmake) ## Thirdparty Dependency List
|option|default value|
|----------|------------|
|ENABLE_SSL|OFF|
|ENABLE_PMR|OFF|
|ENABLE_IO_URING|OFF|
|ENABLE_FILE_IO_URING|OFF|
|ENABLE_STRUCT_PACK_UNPORTABLE_TYPE|OFF|
|ENABLE_STRUCT_PACK_OPTIMIZE|OFF|
## Thirdparty Dependency
In default, yalantinglibs will install thirdparty librarys in `ylt/thirdparty`. You need add it to include path when compile.
If you don't want to install the thirdparty librarys, you can turn off cmake option `-DINSTALL_THIRDPARTY=OFF`.
If you want to install the thirdparty independently (direct install it in system include path so that you don't need add `ylt/thirdparty` to include path), you can use turn on cmake option `-DINSTALL_INDEPENDENT_THIRDPARTY=ON`.
Here are the thirdparty libraries we used(Although async_simple is a part of ylt, it open source first, so we import it as a independence thirdparty library). Here are the thirdparty libraries we used(Although async_simple is a part of ylt, it open source first, so we import it as a independence thirdparty library).
### coro_io ### coro_io/coro_rpc/coro_http
Those dependency will by install by default. you can control it by cmake option.
- [asio](https://think-async.com/Asio) - [asio](https://think-async.com/Asio)
- [async_simple](https://github.com/alibaba/async_simple) - [async_simple](https://github.com/alibaba/async_simple)
- [openssl](https://www.openssl.org/) (optional) - [openssl](https://www.openssl.org/) (optional)
### coro_rpc
- [asio](https://think-async.com/Asio)
- [async_simple](https://github.com/alibaba/async_simple)
- [openssl](https://www.openssl.org/) (optional)
### coro_http
- [asio](https://think-async.com/Asio)
- [async_simple](https://github.com/alibaba/async_simple)
- [cinatra](https://github.com/qicosmos/cinatra)
### easylog ### easylog
No dependency. No dependency.
### struct_pack ### struct_pack, struct_json, struct_xml, struct_yaml
No dependency. No dependency.
### struct_pb (optional) ### struct_pb
- [protobuf](https://protobuf.dev/) No dependency.
### struct_json、struct_xml、struct_yaml
- [iguana](https://github.com/qicosmos/iguana) ## Standalone sublibraries
coro_http is implemented by a standalone sublibrary [cinatra](https://github.com/qicosmos/cinatra)
struct_json、struct_xml、struct_yaml are implemented by a standalone sublibrary [iguana](https://github.com/qicosmos/iguana)
## Benchmark ## Benchmark

1
WORKSPACE Normal file
View File

@ -0,0 +1 @@
workspace(name = "com_alibaba_yalantinglibs")

15
bazel/defs.bzl Normal file
View File

@ -0,0 +1,15 @@
YA_LT_COPT = [
"-fno-tree-slp-vectorize", # -ftree-slp-vectorize with coroutine cause link error. disable it util gcc fix.
]
YA_BIN_COPT = [
"-fno-tree-slp-vectorize", # -ftree-slp-vectorize with coroutine cause link error. disable it util gcc fix.
"-Wno-unused-but-set-variable",
"-Wno-unused-value",
"-Wno-unused-variable",
"-Wno-sign-compare",
"-Wno-reorder",
"-Wno-unused-local-typedefs",
"-Wno-missing-braces",
"-Wno-uninitialized",
]

View File

@ -1,4 +1,4 @@
message(STATUS "-------------COMPILE SETTING-------------") message(STATUS "-------------YLT COMPILE SETTING------------")
# CPP Standard # CPP Standard
foreach(i ${CMAKE_CXX_COMPILE_FEATURES}) foreach(i ${CMAKE_CXX_COMPILE_FEATURES})
@ -79,3 +79,4 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/EHa>") add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/EHa>")
endif() endif()
message(STATUS "--------------------------------------------")

View File

@ -1,45 +1,69 @@
message(STATUS "-------------PROJECT SETTING-------------") message(STATUS "-------------YLT CONFIG SETTING-------------")
option(ENABLE_SSL "Enable ssl support" OFF) option(YLT_ENABLE_SSL "Enable ssl support" OFF)
message(STATUS "ENABLE_SSL: ${ENABLE_SSL}") message(STATUS "YLT_ENABLE_SSL: ${YLT_ENABLE_SSL}")
if (ENABLE_SSL) if (YLT_ENABLE_SSL)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
add_compile_definitions(YLT_ENABLE_SSL) if(CMAKE_PROJECT_NAME STREQUAL "yaLanTingLibs")
add_compile_definitions("YLT_ENABLE_SSL")
link_libraries(OpenSSL::SSL OpenSSL::Crypto) link_libraries(OpenSSL::SSL OpenSSL::Crypto)
else ()
target_compile_definitions(yalantinglibs INTERFACE "YLT_ENABLE_SSL")
target_link_libraries(yalantinglibs INTERFACE OpenSSL::SSL OpenSSL::Crypto)
endif ()
endif () endif ()
option(ENABLE_PMR "Enable pmr support" OFF) option(YLT_ENABLE_PMR "Enable pmr support" OFF)
message(STATUS "ENABLE_PMR: ${ENABLE_PMR}") message(STATUS "YLT_ENABLE_PMR: ${YLT_ENABLE_PMR}")
if (ENABLE_PMR) if (YLT_ENABLE_PMR)
add_compile_definitions(YLT_ENABLE_PMR IGUANA_ENABLE_PMR) if(CMAKE_PROJECT_NAME STREQUAL "yaLanTingLibs")
add_compile_definitions("YLT_ENABLE_PMR" IGUANA_ENABLE_PMR)
else ()
target_compile_definitions(yalantinglibs INTERFACE "YLT_ENABLE_PMR" IGUANA_ENABLE_PMR)
endif ()
endif () endif ()
option(ENABLE_IO_URING "Enable io_uring" OFF) option(YLT_ENABLE_IO_URING "Enable io_uring" OFF)
message(STATUS "ENABLE_IO_URING: ${ENABLE_IO_URING}") message(STATUS "YLT_ENABLE_IO_URING: ${YLT_ENABLE_IO_URING}")
if (ENABLE_IO_URING) if (YLT_ENABLE_IO_URING)
find_package(uring REQUIRED) find_package(uring REQUIRED)
message(STATUS "Use IO_URING for all I/O in linux") message(STATUS "Use IO_URING for all I/O in linux")
if(CMAKE_PROJECT_NAME STREQUAL "yaLanTingLibs")
add_compile_definitions(ASIO_HAS_IO_URING ASIO_DISABLE_EPOLL ASIO_HAS_FILE YLT_ENABLE_FILE_IO_URING) add_compile_definitions(ASIO_HAS_IO_URING ASIO_DISABLE_EPOLL ASIO_HAS_FILE YLT_ENABLE_FILE_IO_URING)
link_libraries(uring) link_libraries(uring)
else ()
target_compile_definitions(yalantinglibs INTERFACE ASIO_HAS_IO_URING ASIO_DISABLE_EPOLL ASIO_HAS_FILE YLT_ENABLE_FILE_IO_URING)
target_link_libraries(yalantinglibs INTERFACE uring)
endif ()
endif() endif()
option(ENABLE_FILE_IO_URING "Enable file io_uring" OFF) option(YLT_ENABLE_FILE_IO_URING "Enable file io_uring" OFF)
if (NOT ENABLE_IO_URING) if (NOT YLT_ENABLE_IO_URING)
if(ENABLE_FILE_IO_URING) if(YLT_ENABLE_FILE_IO_URING)
find_package(uring REQUIRED) find_package(uring REQUIRED)
message(STATUS "Enable io_uring for file I/O in linux") message(STATUS "YLT: Enable io_uring for file I/O in linux")
add_compile_definitions(ASIO_HAS_IO_URING ASIO_HAS_FILE YLT_ENABLE_FILE_IO_URING) if(CMAKE_PROJECT_NAME STREQUAL "yaLanTingLibs")
add_compile_definitions(ASIO_HAS_IO_URING ASIO_HAS_FILE "YLT_ENABLE_FILE_IO_URING")
link_libraries(uring) link_libraries(uring)
else ()
target_compile_definitions(yalantinglibs INTERFACE ASIO_HAS_IO_URING ASIO_HAS_FILE "YLT_ENABLE_FILE_IO_URING")
target_link_libraries(yalantinglibs INTERFACE uring)
endif ()
endif() endif()
endif() endif()
option(ENABLE_STRUCT_PACK_UNPORTABLE_TYPE "enable struct_pack unportable type(like wchar_t)" OFF) option(YLT_ENABLE_STRUCT_PACK_UNPORTABLE_TYPE "enable struct_pack unportable type(like wchar_t)" OFF)
message(STATUS "ENABLE_STRUCT_PACK_UNPORTABLE_TYPE: ${ENABLE_STRUCT_PACK_UNPORTABLE_TYPE}") message(STATUS "YLT_ENABLE_STRUCT_PACK_UNPORTABLE_TYPE: ${YLT_ENABLE_STRUCT_PACK_UNPORTABLE_TYPE}")
if(ENABLE_STRUCT_PACK_UNPORTABLE_TYPE) if(YLT_ENABLE_STRUCT_PACK_UNPORTABLE_TYPE)
add_compile_definitions(STRUCT_PACK_ENABLE_UNPORTABLE_TYPE) add_compile_definitions(STRUCT_PACK_ENABLE_UNPORTABLE_TYPE)
endif() endif()
option(ENABLE_STRUCT_PACK_OPTIMIZE "enable struct_pack optimize(but cost more compile time)" OFF) option(YLT_ENABLE_STRUCT_PACK_OPTIMIZE "enable struct_pack optimize(but cost more compile time)" OFF)
message(STATUS "ENABLE_STRUCT_PACK_OPTIMIZE: ${ENABLE_STRUCT_PACK_OPTIMIZE}") message(STATUS "YLT_ENABLE_STRUCT_PACK_OPTIMIZE: ${YLT_ENABLE_STRUCT_PACK_OPTIMIZE}")
if(ENABLE_STRUCT_PACK_OPTIMIZE) if(YLT_ENABLE_STRUCT_PACK_OPTIMIZE)
add_compile_definitions(ENABLE_STRUCT_PACK_OPTIMIZE) if(CMAKE_PROJECT_NAME STREQUAL "yaLanTingLibs")
add_compile_definitions(YLT_ENABLE_STRUCT_PACK_OPTIMIZE)
else ()
target_compile_definitions(yalantinglibs INTERFACE YLT_ENABLE_STRUCT_PACK_OPTIMIZE)
endif () endif ()
endif()
message(STATUS "--------------------------------------------")

View File

@ -1,4 +1,4 @@
message(STATUS "-------------DEVELOP SETTING-------------") message(STATUS "-------------YLT DEVELOP SETTING------------")
# extra # extra
option(BUILD_EXAMPLES "Build examples" ON) option(BUILD_EXAMPLES "Build examples" ON)
message(STATUS "BUILD_EXAMPLES: ${BUILD_EXAMPLES}") message(STATUS "BUILD_EXAMPLES: ${BUILD_EXAMPLES}")
@ -36,6 +36,7 @@ message(STATUS "CORO_RPC_USE_OTHER_RPC: ${CORO_RPC_USE_OTHER_RPC}")
# Enable address sanitizer # Enable address sanitizer
option(ENABLE_SANITIZER "Enable sanitizer(Debug+Gcc/Clang/AppleClang)" ON) option(ENABLE_SANITIZER "Enable sanitizer(Debug+Gcc/Clang/AppleClang)" ON)
if(ENABLE_SANITIZER AND NOT MSVC) if(ENABLE_SANITIZER AND NOT MSVC)
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
check_asan(HAS_ASAN) check_asan(HAS_ASAN)
@ -65,3 +66,4 @@ if(ENABLE_WARNING)
-Wfatal-errors) -Wfatal-errors)
endif() endif()
endif() endif()
message(STATUS "--------------------------------------------")

View File

@ -1,7 +1,11 @@
message(STATUS "-------------INSTALL SETTING-------------") message(STATUS "-------------YLT INSTALL SETTING------------")
option(INSTALL_THIRDPARTY "Install thirdparty" ON) option(INSTALL_THIRDPARTY "Install thirdparty" ON)
option(INSTALL_STANDALONE "Install standalone" ON)
message(STATUS "INSTALL_THIRDPARTY: " ${INSTALL_THIRDPARTY}) message(STATUS "INSTALL_THIRDPARTY: " ${INSTALL_THIRDPARTY})
message(STATUS "INSTALL_STANDALONE: " ${INSTALL_STANDALONE})
option(INSTALL_INDEPENDENT_THIRDPARTY "Install independent thirdparty" ON) option(INSTALL_INDEPENDENT_THIRDPARTY "Install independent thirdparty" ON)
option(INSTALL_INDEPENDENT_STANDALONE "Install independent standalone" ON)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
write_basic_package_version_file( write_basic_package_version_file(
@ -25,13 +29,26 @@ install(TARGETS yalantinglibs
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
) )
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/yalantinglibsConfig.cmake"
"include(\$\{CMAKE_CURRENT_LIST_DIR\}/yalantinglibsConfigImpl.cmake)\n"
"include(\$\{CMAKE_CURRENT_LIST_DIR\}/config.cmake)\n"
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/yalantinglibsConfig.cmake"
DESTINATION ${ConfigPackageLocation})
install(FILES "${yaLanTingLibs_SOURCE_DIR}/cmake/config.cmake"
DESTINATION ${ConfigPackageLocation}
)
install(EXPORT yalantinglibsTargets install(EXPORT yalantinglibsTargets
FILE yalantinglibsConfig.cmake FILE yalantinglibsConfigImpl.cmake
NAMESPACE yalantinglibs:: NAMESPACE yalantinglibs::
DESTINATION ${ConfigPackageLocation} DESTINATION ${ConfigPackageLocation}
) )
install(DIRECTORY "${yaLanTingLibs_SOURCE_DIR}/include/" DESTINATION include REGEX "${yaLanTingLibs_SOURCE_DIR}/include/ylt/thirdparty" EXCLUDE) install(DIRECTORY "${yaLanTingLibs_SOURCE_DIR}/include/" DESTINATION include REGEX "${yaLanTingLibs_SOURCE_DIR}/include/ylt/thirdparty" EXCLUDE REGEX "${yaLanTingLibs_SOURCE_DIR}/include/ylt/standalone" EXCLUDE)
if (INSTALL_THIRDPARTY) if (INSTALL_THIRDPARTY)
message(STATUS "INSTALL_INDEPENDENT_THIRDPARTY: " ${INSTALL_INDEPENDENT_THIRDPARTY}) message(STATUS "INSTALL_INDEPENDENT_THIRDPARTY: " ${INSTALL_INDEPENDENT_THIRDPARTY})
@ -44,3 +61,14 @@ if (INSTALL_THIRDPARTY)
) )
endif() endif()
endif() endif()
if(INSTALL_STANDALONE)
message(STATUS "INSTALL_INDEPENDENT_STANDALONE: " ${INSTALL_INDEPENDENT_STANDALONE})
if (INSTALL_INDEPENDENT_STANDALONE)
install(DIRECTORY "${yaLanTingLibs_SOURCE_DIR}/include/ylt/standalone/" DESTINATION include)
else()
install(DIRECTORY "${yaLanTingLibs_SOURCE_DIR}/include/ylt/standalone/" DESTINATION include/ylt/standalone)
target_include_directories(yalantinglibs INTERFACE
$<INSTALL_INTERFACE:include/ylt/standalone>)
endif()
endif()
message(STATUS "--------------------------------------------")

View File

@ -1,224 +0,0 @@
function(protobuf_generate_modified)
find_package(Protobuf REQUIRED)
set(_options APPEND_PATH DESCRIPTORS)
set(_singleargs LANGUAGE OUT_VAR EXPORT_MACRO PROTOC_OUT_DIR PLUGIN PROTOC_OPTION)
if(COMMAND target_sources)
list(APPEND _singleargs TARGET)
endif()
set(_multiargs PROTOS IMPORT_DIRS GENERATE_EXTENSIONS)
cmake_parse_arguments(protobuf_generate "${_options}" "${_singleargs}" "${_multiargs}" "${ARGN}")
if(NOT protobuf_generate_PROTOS AND NOT protobuf_generate_TARGET)
message(SEND_ERROR "Error: protobuf_generate called without any targets or source files")
return()
endif()
if(NOT protobuf_generate_OUT_VAR AND NOT protobuf_generate_TARGET)
message(SEND_ERROR "Error: protobuf_generate called without a target or output variable")
return()
endif()
if(NOT protobuf_generate_LANGUAGE)
set(protobuf_generate_LANGUAGE struct_pb)
endif()
string(TOLOWER ${protobuf_generate_LANGUAGE} protobuf_generate_LANGUAGE)
if(NOT protobuf_generate_PROTOC_OUT_DIR)
set(protobuf_generate_PROTOC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
endif()
set(_opt)
if(protobuf_generate_PROTOC_OPTION)
set(_opt "${protobuf_generate_PROTOC_OPTION}")
endif()
if(protobuf_generate_EXPORT_MACRO AND protobuf_generate_LANGUAGE STREQUAL cpp)
set(_opt "${opt},dllexport_decl=${protobuf_generate_EXPORT_MACRO}")
endif()
set(_opt "${_opt}:")
if(protobuf_generate_PLUGIN)
set(_plugin "--plugin=${protobuf_generate_PLUGIN}")
endif()
if(NOT protobuf_generate_GENERATE_EXTENSIONS)
if(protobuf_generate_LANGUAGE STREQUAL cpp)
set(protobuf_generate_GENERATE_EXTENSIONS .pb.h .pb.cc)
elseif(protobuf_generate_LANGUAGE STREQUAL python)
set(protobuf_generate_GENERATE_EXTENSIONS _pb2.py)
elseif(protobuf_generate_LANGUAGE STREQUAL struct_pb)
set(protobuf_generate_GENERATE_EXTENSIONS .struct_pb.h .struct_pb.cc)
else()
message(SEND_ERROR "Error: protobuf_generate given unknown Language ${LANGUAGE}, please provide a value for GENERATE_EXTENSIONS")
return()
endif()
endif()
if(protobuf_generate_TARGET)
get_target_property(_source_list ${protobuf_generate_TARGET} SOURCES)
foreach(_file ${_source_list})
if(_file MATCHES "proto$")
list(APPEND protobuf_generate_PROTOS ${_file})
endif()
endforeach()
endif()
if(NOT protobuf_generate_PROTOS)
message(SEND_ERROR "Error: protobuf_generate could not find any .proto files")
return()
endif()
if(protobuf_generate_APPEND_PATH)
# Create an include path for each file specified
foreach(_file ${protobuf_generate_PROTOS})
get_filename_component(_abs_file ${_file} ABSOLUTE)
get_filename_component(_abs_path ${_abs_file} PATH)
list(FIND _protobuf_include_path ${_abs_path} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${_abs_path})
endif()
endforeach()
else()
set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
endif()
foreach(DIR ${protobuf_generate_IMPORT_DIRS})
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if(${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
set(_generated_srcs_all)
foreach(_proto ${protobuf_generate_PROTOS})
get_filename_component(_abs_file ${_proto} ABSOLUTE)
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
get_filename_component(_basename ${_proto} NAME_WLE)
file(RELATIVE_PATH _rel_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_abs_dir})
set(_possible_rel_dir)
if (NOT protobuf_generate_APPEND_PATH)
set(_possible_rel_dir ${_rel_dir}/)
endif()
set(_generated_srcs)
foreach(_ext ${protobuf_generate_GENERATE_EXTENSIONS})
list(APPEND _generated_srcs "${protobuf_generate_PROTOC_OUT_DIR}/${_possible_rel_dir}${_basename}${_ext}")
endforeach()
if(protobuf_generate_DESCRIPTORS AND protobuf_generate_LANGUAGE STREQUAL cpp)
set(_descriptor_file "${CMAKE_CURRENT_BINARY_DIR}/${_basename}.desc")
set(_dll_desc_out "--descriptor_set_out=${_descriptor_file}")
list(APPEND _generated_srcs ${_descriptor_file})
endif()
list(APPEND _generated_srcs_all ${_generated_srcs})
add_custom_command(
OUTPUT ${_generated_srcs}
COMMAND protobuf::protoc
ARGS --${protobuf_generate_LANGUAGE}_out ${_opt}${protobuf_generate_PROTOC_OUT_DIR} ${_plugin} ${_dll_desc_out} ${_protobuf_include_path} ${_abs_file}
DEPENDS ${_abs_file} protobuf::protoc
COMMENT "Running ${protobuf_generate_LANGUAGE} protocol buffer compiler on ${_proto}"
VERBATIM )
endforeach()
set_source_files_properties(${_generated_srcs_all} PROPERTIES GENERATED TRUE)
if(protobuf_generate_OUT_VAR)
set(${protobuf_generate_OUT_VAR} ${_generated_srcs_all} PARENT_SCOPE)
endif()
if(protobuf_generate_TARGET)
target_sources(${protobuf_generate_TARGET} PRIVATE ${_generated_srcs_all})
target_include_directories(${protobuf_generate_TARGET} PUBLIC ${protobuf_generate_PROTOC_OUT_DIR})
endif()
endfunction()
function(protobuf_generate_struct_pb SRCS HDRS)
cmake_parse_arguments(protobuf_generate_struct_pb "" "EXPORT_MACRO;DESCRIPTORS;OPTION" "" ${ARGN})
set(_proto_files "${protobuf_generate_struct_pb_UNPARSED_ARGUMENTS}")
if(NOT _proto_files)
message(SEND_ERROR "Error: PROTOBUF_GENERATE_STRUCT_PB() called without any proto files")
return()
endif()
if(PROTOBUF_GENERATE_STRUCT_PB_APPEND_PATH)
set(_append_arg APPEND_PATH)
endif()
if(protobuf_generate_struct_pb_DESCRIPTORS)
set(_descriptors DESCRIPTORS)
endif()
if(protobuf_generate_struct_pb_OPTION)
set(_opt ${protobuf_generate_struct_pb_OPTION})
endif()
if(DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS)
set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}")
endif()
if(DEFINED Protobuf_IMPORT_DIRS)
set(_import_arg IMPORT_DIRS ${Protobuf_IMPORT_DIRS})
endif()
set(_outvar)
protobuf_generate_modified(${_append_arg} ${_descriptors}
LANGUAGE struct_pb EXPORT_MACRO ${protobuf_generate_struct_pb_EXPORT_MACRO}
PLUGIN $<TARGET_FILE:protoc-gen-struct_pb>
OUT_VAR _outvar ${_import_arg} PROTOS ${_proto_files}
PROTOC_OPTION ${_opt}
)
set(${SRCS})
set(${HDRS})
if(protobuf_generate_struct_pb_DESCRIPTORS)
set(${protobuf_generate_struct_pb_DESCRIPTORS})
endif()
foreach(_file ${_outvar})
if(_file MATCHES "cc$")
list(APPEND ${SRCS} ${_file})
elseif(_file MATCHES "desc$")
list(APPEND ${protobuf_generate_struct_pb_DESCRIPTORS} ${_file})
else()
list(APPEND ${HDRS} ${_file})
endif()
endforeach()
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
set(${HDRS} ${${HDRS}} PARENT_SCOPE)
if(protobuf_generate_struct_pb_DESCRIPTORS)
set(${protobuf_generate_struct_pb_DESCRIPTORS} "${${protobuf_generate_struct_pb_DESCRIPTORS}}" PARENT_SCOPE)
endif()
endfunction()
function(target_protos_struct_pb target)
set(_options APPEND_PATH DESCRIPTORS)
set(_singleargs LANGUAGE EXPORT_MACRO PROTOC_OUT_DIR PLUGIN OPTION)
set(_multiargs IMPORT_DIRS PRIVATE PUBLIC)
cmake_parse_arguments(target_protos_struct_pb "${_options}" "${_singleargs}" "${_multiargs}" "${ARGN}")
set(_proto_files "${target_protos_struct_pb_PRIVATE}" "${target_protos_struct_pb_PUBLIC}")
if (NOT _proto_files)
message(SEND_ERROR "Error: TARGET_PROTOS_STRUCT_PB() called without any proto files")
return()
endif ()
if (DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS)
set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}")
endif ()
if (DEFINED Protobuf_IMPORT_DIRS)
set(_import_arg IMPORT_DIRS ${Protobuf_IMPORT_DIRS})
endif ()
if (target_protos_struct_pb_OPTION)
set(_opt ${target_protos_struct_pb_OPTION})
endif ()
protobuf_generate_modified(
TARGET ${target}
LANGUAGE struct_pb EXPORT_MACRO ${target_protos_struct_pb_EXPORT_MACRO}
PLUGIN $<TARGET_FILE:protoc-gen-struct_pb>
${_import_arg} PROTOS ${_proto_files}
PROTOC_OPTION ${_opt}
)
endfunction()

View File

@ -1,3 +1,5 @@
message(STATUS "-------------YLT PROJECT SETTING------------")
file(GLOB children src/*) file(GLOB children src/*)
foreach(child ${children}) foreach(child ${children})
@ -20,13 +22,13 @@ endif()
foreach(child ${children}) foreach(child ${children})
get_filename_component(subdir_name ${child} NAME) get_filename_component(subdir_name ${child} NAME)
string(TOUPPER ${subdir_name} subdir_name) string(TOUPPER ${subdir_name} subdir_name)
message(STATUS "BUILD_${subdir_name}: ${BUILD_${subdir_name}}") if((${subdir_name} STREQUAL "STRUCT_PACK" OR ${subdir_name} STREQUAL "STRUCT_PB") AND (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC"))
endforeach() message(STATUS "skip ${subdir_name}")
continue()
endif()
foreach(child ${children})
get_filename_component(subdir_name ${child} NAME)
string(TOUPPER ${subdir_name} subdir_name)
if (BUILD_${subdir_name}) if (BUILD_${subdir_name})
message(STATUS "BUILD_${subdir_name}: ${BUILD_${subdir_name}}")
if(BUILD_EXAMPLES AND EXISTS ${child}/examples) if(BUILD_EXAMPLES AND EXISTS ${child}/examples)
add_subdirectory(${child}/examples) add_subdirectory(${child}/examples)
endif() endif()
@ -38,6 +40,4 @@ foreach(child ${children})
endif() endif()
endif() endif()
endforeach() endforeach()
if (BUILD_STRUCT_PB) message(STATUS "--------------------------------------------")
add_subdirectory(src/struct_pb)
endif()

View File

@ -25,18 +25,18 @@ mkdir build && cd build || exit 1
export CC=clang export CC=clang
export CXX=clang++ export CXX=clang++
cmake .. -DCOVERAGE_TEST=ON -DENABLE_SSL=ON cmake .. -DCOVERAGE_TEST=ON -DENABLE_SSL=ON
make -j test_rpc make -j
# warning: test_rpc.profraw: malformed instrumentation profile data # warning: test_ylt.profraw: malformed instrumentation profile data
# error: no profile can be merged # error: no profile can be merged
# add %m to fix the bug on ARM # add %m to fix the bug on ARM
# https://groups.google.com/g/llvm-dev/c/oaA58fbNMGg # https://groups.google.com/g/llvm-dev/c/oaA58fbNMGg
# https://github.com/llvm/llvm-project/issues/50966 # https://github.com/llvm/llvm-project/issues/50966
export LLVM_PROFILE_FILE="coro_rpc_test-%m.profraw" export LLVM_PROFILE_FILE="ylt_test-%m.profraw"
ls ls
cd tests cd output
./test_rpc ./tests/coro_io_test ./tests/coro_rpc_test ./tests/easylog_test ./tests/struct_pack_test ./tests/struct_pack_test_with_optimize
llvm-profdata merge -sparse coro_rpc_test-*.profraw -o coro_rpc_test.profdata llvm-profdata merge -sparse ylt_test-*.profraw -o ylt_test.profdata
llvm-cov show ./coro_rpc_test -instr-profile=test_rpc.profdata -format=html -output-dir=../../.coverage_llvm_cov -ignore-filename-regex="async_simple|thirdparty|tests|asio|util|logging|struct_pack" -show-instantiations=false llvm-cov show ./tests/coro_io_test ./tests/coro_rpc_test ./tests/easylog_test ./tests/struct_pack_test ./tests/struct_pack_test_with_optimize -instr-profile=test_ylt.profdata -format=html -output-dir=../../.coverage_llvm_cov -ignore-filename-regex="thirdparty|asio" -show-instantiations=false
echo 'Done!!!' echo 'Done!!!'
fi fi

View File

@ -14,7 +14,6 @@
* limitations under the License. * limitations under the License.
*/ */
#pragma once #pragma once
#include "cinatra/uri.hpp"
#ifdef YLT_ENABLE_SSL #ifdef YLT_ENABLE_SSL
#define CINATRA_ENABLE_SSL #define CINATRA_ENABLE_SSL
#endif #endif

View File

@ -18,6 +18,7 @@
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <numeric>
#include <random> #include <random>
#include "client_pool.hpp" #include "client_pool.hpp"
@ -26,7 +27,8 @@ namespace coro_io {
enum class load_blance_algorithm { enum class load_blance_algorithm {
RR = 0, // round-robin RR = 0, // round-robin
random = 1 WRR, // weight round-robin
random,
}; };
template <typename client_t, typename io_context_pool_t = io_context_pool> template <typename client_t, typename io_context_pool_t = io_context_pool>
@ -51,6 +53,89 @@ class channel {
co_return channel.client_pools_[i % channel.client_pools_.size()]; co_return channel.client_pools_[i % channel.client_pools_.size()];
} }
}; };
/*
Supposing that there is a server set ''S'' = {S0, S1, , Sn-1};
W(Si) indicates the weight of Si;
''i'' indicates the server selected last time, and ''i'' is initialized with
-1;
''cw'' is the current weight in scheduling, and cw is initialized with zero;
max(S) is the maximum weight of all the servers in S;
gcd(S) is the greatest common divisor of all server weights in S;
while (true) {
i = (i + 1) mod n;
if (i == 0) {
cw = cw - gcd(S);
if (cw <= 0) {
cw = max(S);
if (cw == 0)
return NULL;
}
}
if (W(Si) >= cw)
return Si;
}
*/
struct WRRLoadBlancer {
WRRLoadBlancer(const std::vector<int>& weights) : weights_(weights) {
max_gcd_ = get_max_weight_gcd();
max_weight_ = get_max_weight();
}
async_simple::coro::Lazy<std::shared_ptr<client_pool_t>> operator()(
const channel& channel) {
int selected = select_host_with_weight_round_robin();
if (selected == -1) {
selected = 0;
}
wrr_current_ = selected;
co_return channel.client_pools_[selected % channel.client_pools_.size()];
}
private:
int select_host_with_weight_round_robin() {
while (true) {
wrr_current_ = (wrr_current_ + 1) % weights_.size();
if (wrr_current_ == 0) {
weight_current_ = weight_current_ - max_gcd_;
if (weight_current_ <= 0) {
weight_current_ = max_weight_;
if (weight_current_ == 0) {
return -1; // can't find max weight server
}
}
}
if (weights_[wrr_current_] >= weight_current_) {
return wrr_current_;
}
}
}
int get_max_weight_gcd() {
int res = weights_[0];
int cur_max = 0, cur_min = 0;
for (size_t i = 0; i < weights_.size(); i++) {
cur_max = (std::max)(res, weights_[i]);
cur_min = (std::min)(res, weights_[i]);
res = std::gcd(cur_max, cur_min);
}
return res;
}
int get_max_weight() {
return *std::max_element(weights_.begin(), weights_.end());
}
std::vector<int> weights_;
int max_gcd_ = 0;
int max_weight_ = 0;
int wrr_current_ = -1;
int weight_current_ = 0;
};
struct RandomLoadBlancer { struct RandomLoadBlancer {
async_simple::coro::Lazy<std::shared_ptr<client_pool_t>> operator()( async_simple::coro::Lazy<std::shared_ptr<client_pool_t>> operator()(
const channel& channel) { const channel& channel) {
@ -97,20 +182,27 @@ class channel {
return send_request(std::move(op), config_.pool_config.client_config); return send_request(std::move(op), config_.pool_config.client_config);
} }
std::size_t size() const noexcept { return client_pools_.size(); }
static channel create(const std::vector<std::string_view>& hosts, static channel create(const std::vector<std::string_view>& hosts,
const channel_config& config = {}, const channel_config& config = {},
const std::vector<int>& weights = {},
client_pools_t& client_pools = client_pools_t& client_pools =
g_clients_pool<client_t, io_context_pool_t>()) { g_clients_pool<client_t, io_context_pool_t>()) {
channel ch; channel ch;
ch.init(hosts, config, client_pools); ch.init(hosts, config, weights, client_pools);
return ch; return ch;
} }
/**
* @brief return the channel's hosts size.
*
* @return std::size_t
*/
std::size_t size() const noexcept { return client_pools_.size(); }
private: private:
void init(const std::vector<std::string_view>& hosts, void init(const std::vector<std::string_view>& hosts,
const channel_config& config, client_pools_t& client_pools) { const channel_config& config, const std::vector<int>& weights,
client_pools_t& client_pools) {
config_ = config; config_ = config;
client_pools_.reserve(hosts.size()); client_pools_.reserve(hosts.size());
for (auto& host : hosts) { for (auto& host : hosts) {
@ -120,6 +212,15 @@ class channel {
case load_blance_algorithm::RR: case load_blance_algorithm::RR:
lb_worker = RRLoadBlancer{}; lb_worker = RRLoadBlancer{};
break; break;
case load_blance_algorithm::WRR: {
if (hosts.empty() || weights.empty()) {
throw std::invalid_argument("host/weight list is empty!");
}
if (hosts.size() != weights.size()) {
throw std::invalid_argument("hosts count is not equal with weights!");
}
lb_worker = WRRLoadBlancer(weights);
} break;
case load_blance_algorithm::random: case load_blance_algorithm::random:
default: default:
lb_worker = RandomLoadBlancer{}; lb_worker = RandomLoadBlancer{};
@ -127,7 +228,7 @@ class channel {
return; return;
} }
channel_config config_; channel_config config_;
std::variant<RRLoadBlancer, RandomLoadBlancer> lb_worker; std::variant<RRLoadBlancer, WRRLoadBlancer, RandomLoadBlancer> lb_worker;
std::vector<std::shared_ptr<client_pool_t>> client_pools_; std::vector<std::shared_ptr<client_pool_t>> client_pools_;
}; };

View File

@ -36,12 +36,14 @@
#include <random> #include <random>
#include <shared_mutex> #include <shared_mutex>
#include <string_view> #include <string_view>
#include <system_error>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <ylt/util/expected.hpp> #include <ylt/util/expected.hpp>
#include "async_simple/Common.h"
#include "async_simple/coro/Collect.h" #include "async_simple/coro/Collect.h"
#include "coro_io.hpp" #include "coro_io.hpp"
#include "detail/client_queue.hpp" #include "detail/client_queue.hpp"
@ -76,11 +78,11 @@ class client_pool : public std::enable_shared_from_this<
break; break;
} }
while (true) { while (true) {
ELOG_DEBUG << "start collect timeout client of pool{" ELOG_TRACE << "start collect timeout client of pool{"
<< self->host_name_ << self->host_name_
<< "}, now client count: " << clients.size(); << "}, now client count: " << clients.size();
std::size_t is_all_cleared = clients.clear_old(clear_cnt); std::size_t is_all_cleared = clients.clear_old(clear_cnt);
ELOG_DEBUG << "finish collect timeout client of pool{" ELOG_TRACE << "finish collect timeout client of pool{"
<< self->host_name_ << self->host_name_
<< "}, now client cnt: " << clients.size(); << "}, now client cnt: " << clients.size();
if (is_all_cleared != 0) [[unlikely]] { if (is_all_cleared != 0) [[unlikely]] {
@ -105,206 +107,151 @@ class client_pool : public std::enable_shared_from_this<
co_return; co_return;
} }
struct client_connect_helper { static auto rand_time(std::chrono::milliseconds ms) {
std::unique_ptr<client_t> client; static thread_local std::default_random_engine r;
std::weak_ptr<client_pool> pool_watcher; std::uniform_real_distribution e(1.0f, 1.2f);
std::weak_ptr<bool> spinlock_watcher; return std::chrono::milliseconds{static_cast<long>(e(r) * ms.count())};
client_connect_helper(std::unique_ptr<client_t>&& client,
std::weak_ptr<client_pool>&& pool_watcher,
std::weak_ptr<bool>&& spinlock_watcher)
: client(std::move(client)),
pool_watcher(std::move(pool_watcher)),
spinlock_watcher(std::move(spinlock_watcher)) {}
client_connect_helper(client_connect_helper&& o)
: client(std::move(o.client)),
pool_watcher(std::move(o.pool_watcher)),
spinlock_watcher(std::move(o.spinlock_watcher)) {}
client_connect_helper& operator=(client_connect_helper&& o) {
client = std::move(o.client);
pool_watcher = std::move(o.pool_watcher);
spinlock_watcher = std::move(o.spinlock_watcher);
return *this;
} }
~client_connect_helper() {
if (client) {
if (auto pool = pool_watcher.lock(); pool) {
int cnt = 0;
while (spinlock_watcher.lock()) {
std::this_thread::yield();
++cnt;
if (cnt % 10000 == 0) {
ELOG_WARN << "spinlock of client{" << client.get() << "},host:{"
<< client->get_host() << ":" << client->get_port()
<< "}cost too much time, spin count: " << cnt;
}
}
pool->collect_free_client(std::move(client));
}
}
}
};
async_simple::coro::Lazy<void> reconnect(std::unique_ptr<client_t>& client) { static async_simple::coro::Lazy<void> reconnect(
for (unsigned int i = 0; i < pool_config_.connect_retry_count; ++i) { std::unique_ptr<client_t>& client, std::weak_ptr<client_pool> watcher) {
ELOG_DEBUG << "try to reconnect client{" << client.get() << "},host:{" using namespace std::chrono_literals;
std::shared_ptr<client_pool> self = watcher.lock();
uint32_t i = UINT32_MAX; // (at least connect once)
do {
ELOG_TRACE << "try to reconnect client{" << client.get() << "},host:{"
<< client->get_host() << ":" << client->get_port() << client->get_host() << ":" << client->get_port()
<< "}, try count:" << i << "}, try count:" << i << "max retry limit:"
<< "max retry limit:" << pool_config_.connect_retry_count; << self->pool_config_.connect_retry_count;
auto pre_time_point = std::chrono::steady_clock::now(); auto pre_time_point = std::chrono::steady_clock::now();
bool ok = client_t::is_ok(co_await client->reconnect(host_name_)); bool ok = client_t::is_ok(co_await client->connect(self->host_name_));
auto post_time_point = std::chrono::steady_clock::now(); auto post_time_point = std::chrono::steady_clock::now();
auto cost_time = post_time_point - pre_time_point; auto cost_time = post_time_point - pre_time_point;
ELOG_DEBUG << "reconnect client{" << client.get() ELOG_TRACE << "reconnect client{" << client.get()
<< "} cost time: " << cost_time / std::chrono::milliseconds{1} << "} cost time: " << cost_time / std::chrono::milliseconds{1}
<< "ms"; << "ms";
if (ok) { if (ok) {
ELOG_DEBUG << "reconnect client{" << client.get() << "} success"; ELOG_TRACE << "reconnect client{" << client.get() << "} success";
co_return; co_return;
} }
ELOG_DEBUG << "reconnect client{" << client.get() ELOG_TRACE << "reconnect client{" << client.get()
<< "} failed. If client close:{" << client->has_closed() << "} failed. If client close:{" << client->has_closed()
<< "}"; << "}";
auto wait_time = pool_config_.reconnect_wait_time - cost_time; auto wait_time = rand_time(
(self->pool_config_.reconnect_wait_time - cost_time) / 1ms * 1ms);
self = nullptr;
if (wait_time.count() > 0) if (wait_time.count() > 0)
co_await coro_io::sleep_for(wait_time); co_await coro_io::sleep_for(wait_time, &client->get_executor());
} self = watcher.lock();
++i;
} while (i < self->pool_config_.connect_retry_count);
ELOG_WARN << "reconnect client{" << client.get() << "},host:{" ELOG_WARN << "reconnect client{" << client.get() << "},host:{"
<< client->get_host() << ":" << client->get_port() << client->get_host() << ":" << client->get_port()
<< "} out of max limit, stop retry. connect failed"; << "} out of max limit, stop retry. connect failed";
client = nullptr; client = nullptr;
} }
async_simple::coro::Lazy<client_connect_helper> connect_client( struct promise_handler {
client_connect_helper helper) { std::atomic<bool> flag_ = false;
ELOG_DEBUG << "try to connect client{" << helper.client.get() async_simple::Promise<std::unique_ptr<client_t>> promise_;
<< "} to host:" << host_name_; };
auto result = co_await helper.client->connect(host_name_);
if (!client_t::is_ok(result)) {
ELOG_DEBUG << "connect client{" << helper.client.get() << "} to failed. ";
co_await reconnect(helper.client);
}
if (helper.client) {
ELOG_DEBUG << "connect client{" << helper.client.get() << "} successful!";
}
co_return std::move(helper); static async_simple::coro::Lazy<void> connect_client(
std::unique_ptr<client_t> client, std::weak_ptr<client_pool> watcher,
std::shared_ptr<promise_handler> handler) {
co_await reconnect(client, watcher);
auto has_get_connect = handler->flag_.exchange(true);
if (!has_get_connect) {
handler->promise_.setValue(std::move(client));
}
else {
if (client) {
auto self = watcher.lock();
auto conn_lim =
std::min<unsigned>(10u, self->pool_config_.max_connection);
if (self && self->free_clients_.size() < conn_lim) {
self->enqueue(self->free_clients_, std::move(client),
self->pool_config_.idle_timeout);
}
}
} }
auto rand_time() {
static thread_local std::default_random_engine r;
std::uniform_int_distribution<int> e(-25, 25);
return std::chrono::milliseconds{100 + e(r)};
} }
async_simple::coro::Lazy<std::unique_ptr<client_t>> get_client( async_simple::coro::Lazy<std::unique_ptr<client_t>> get_client(
const typename client_t::config& client_config) { const typename client_t::config& client_config) {
std::unique_ptr<client_t> client; std::unique_ptr<client_t> client;
free_clients_.try_dequeue(client); free_clients_.try_dequeue(client);
if (!client) { if (!client) {
short_connect_clients_.try_dequeue(client); short_connect_clients_.try_dequeue(client);
} }
assert(client == nullptr || !client->has_closed());
if (client == nullptr) { if (client == nullptr) {
client = std::make_unique<client_t>(*io_context_pool_.get_executor());
if (!client->init_config(client_config)) {
ELOG_ERROR << "init client config{" << client.get() << "} failed.";
co_return nullptr;
}
auto spinlock = std::make_shared<bool>(false);
auto client_ptr = client.get();
auto result = co_await async_simple::coro::collectAny(
connect_client(client_connect_helper{
std::move(client), this->shared_from_this(), spinlock}),
coro_io::sleep_for(rand_time()));
if (result.index() == 0) { // connect finish in 100ms
co_return std::move(std::get<0>(result).value().client);
}
else if (result.index() == 1) { // connect time cost more than 100ms
ELOG_DEBUG << "slow connection of client{" << client_ptr
<< "}, try to get free client from pool.";
std::unique_ptr<client_t> cli; std::unique_ptr<client_t> cli;
if (short_connect_clients_.try_dequeue(cli) || auto executor = io_context_pool_.get_executor();
free_clients_.try_dequeue(cli)) { client = std::make_unique<client_t>(*executor);
spinlock = nullptr; if (!client->init_config(client_config))
ELOG_DEBUG << "get free client{" << cli.get() AS_UNLIKELY {
<< "} from pool. skip wait client{" << client_ptr ELOG_ERROR << "init client config failed.";
<< "} connect";
co_return std::move(cli);
}
else {
auto promise = std::make_unique<
async_simple::Promise<std::unique_ptr<client_t>>>();
auto* promise_address = promise.get();
promise_queue.enqueue(promise_address);
spinlock = nullptr;
if (short_connect_clients_.try_dequeue(cli) ||
free_clients_.try_dequeue(cli)) {
collect_free_client(std::move(cli));
}
ELOG_DEBUG << "wait for free client waiter promise{"
<< promise_address << "} response because slow client{"
<< client_ptr << "}";
auto res = co_await collectAny(
[](auto promise)
-> async_simple::coro::Lazy<std::unique_ptr<client_t>> {
co_return co_await promise->getFuture();
}(std::move(promise)),
coro_io::sleep_for(this->pool_config_.max_connection_time));
if (res.index() == 0) {
auto& res0 = std::get<0>(res);
if (!res0.hasError()) {
auto& cli = res0.value();
ELOG_DEBUG << "get free client{" << cli.get() << "} from promise{"
<< promise_address << "}. skip wait client{"
<< client_ptr << "} connect";
co_return std::move(cli);
}
else {
ELOG_ERROR << "Unexcepted branch";
co_return nullptr; co_return nullptr;
} }
} auto client_ptr = client.get();
else { auto handler = std::make_shared<promise_handler>();
ELOG_ERROR << "Unexcepted branch. Out of max limitation of connect " connect_client(std::move(client), this->weak_from_this(), handler)
.start([](auto&&) {
});
auto timer = std::make_shared<coro_io::period_timer>(
executor->get_asio_executor());
timer->expires_after(std::chrono::milliseconds{20});
timer->async_await().start([watcher = this->weak_from_this(), handler,
client_ptr, timer](auto&& res) {
if (res.value() && !handler->flag_) {
if (auto self = watcher.lock(); self) {
++self->promise_cnt_;
self->promise_queue_.enqueue(handler);
timer->expires_after(
(std::max)(std::chrono::milliseconds{0},
self->pool_config_.max_connection_time -
std::chrono::milliseconds{20}));
timer->async_await().start([handler = std::move(handler),
client_ptr = client_ptr](auto&& res) {
auto has_get_connect = handler->flag_.exchange(true);
if (!has_get_connect) {
ELOG_ERROR << "Out of max limitation of connect "
"time, connect " "time, connect "
"failed. skip wait client{" "failed. skip wait client{"
<< client_ptr << "} connect. " << client_ptr << "} connect. ";
<< "skip wait promise {" << promise_address handler->promise_.setValue(std::unique_ptr<client_t>{nullptr});
<< "} response"; }
co_return nullptr; });
} }
} }
} });
else { ELOG_TRACE << "wait client by promise {" << &handler->promise_ << "}";
ELOG_ERROR << "unknown collectAny index while wait client{" client = co_await handler->promise_.getFuture();
<< client_ptr << "} connect"; if (client) {
co_return nullptr; executor->schedule([timer] {
std::error_code ignore_ec;
timer->cancel(ignore_ec);
});
} }
} }
else { else {
ELOG_DEBUG << "get free client{" << client.get() << "}. from queue"; ELOG_TRACE << "get free client{" << client.get() << "}. from queue";
}
co_return std::move(client); co_return std::move(client);
} }
}
void enqueue( void enqueue(
coro_io::detail::client_queue<std::unique_ptr<client_t>>& clients, coro_io::detail::client_queue<std::unique_ptr<client_t>>& clients,
std::unique_ptr<client_t> client, bool is_short_client) { std::unique_ptr<client_t> client,
std::chrono::milliseconds collect_time) {
if (clients.enqueue(std::move(client)) == 1) { if (clients.enqueue(std::move(client)) == 1) {
std::size_t expected = 0; std::size_t expected = 0;
if (clients.collecter_cnt_.compare_exchange_strong(expected, 1)) { if (clients.collecter_cnt_.compare_exchange_strong(expected, 1)) {
ELOG_DEBUG << "start timeout client collecter of client_pool{" ELOG_TRACE << "start timeout client collecter of client_pool{"
<< host_name_ << "}"; << host_name_ << "}";
collect_idle_timeout_client( collect_idle_timeout_client(
this->shared_from_this(), clients, this->weak_from_this(), clients,
(std::max)( (std::max)(collect_time, std::chrono::milliseconds{50}),
(is_short_client
? (std::min)(pool_config_.idle_timeout,
pool_config_.short_connect_idle_timeout)
: pool_config_.idle_timeout),
std::chrono::milliseconds{50}),
pool_config_.idle_queue_per_max_clear_count) pool_config_.idle_queue_per_max_clear_count)
.via(coro_io::get_global_executor()) .via(coro_io::get_global_executor())
.start([](auto&&) { .start([](auto&&) {
@ -314,28 +261,41 @@ class client_pool : public std::enable_shared_from_this<
} }
void collect_free_client(std::unique_ptr<client_t> client) { void collect_free_client(std::unique_ptr<client_t> client) {
ELOG_DEBUG << "collect free client{" << client.get() << "}"; if (!client->has_closed()) {
if (client && !client->has_closed()) { std::shared_ptr<promise_handler> handler;
async_simple::Promise<std::unique_ptr<client_t>>* promise = nullptr; if (promise_cnt_) {
if (promise_queue.try_dequeue(promise)) { int cnt = 0;
promise->setValue(std::move(client)); while (promise_queue_.try_dequeue(handler)) {
ELOG_DEBUG << "collect free client{" << client.get() ++cnt;
<< "} wake up promise{" << promise << "}"; auto has_get_connect = handler->flag_.exchange(true);
if (!has_get_connect) {
handler->promise_.setValue(std::move(client));
promise_cnt_ -= cnt;
ELOG_TRACE << "collect free client{" << client.get()
<< "} and wake up promise{" << &handler->promise_ << "}";
return;
}
}
promise_cnt_ -= cnt;
}
if (free_clients_.size() < pool_config_.max_connection) {
if (client) {
ELOG_TRACE << "collect free client{" << client.get() << "} enqueue";
enqueue(free_clients_, std::move(client), pool_config_.idle_timeout);
} }
else if (free_clients_.size() < pool_config_.max_connection) {
ELOG_DEBUG << "collect free client{" << client.get() << "} enqueue";
enqueue(free_clients_, std::move(client), false);
} }
else { else {
ELOG_DEBUG << "out of max connection limit <<" ELOG_TRACE << "out of max connection limit <<"
<< pool_config_.max_connection << ", collect free client{" << pool_config_.max_connection << ", collect free client{"
<< client.get() << "} enqueue short connect queue"; << client.get() << "} enqueue short connect queue";
enqueue(short_connect_clients_, std::move(client), true); enqueue(short_connect_clients_, std::move(client),
pool_config_.short_connect_idle_timeout);
} }
} }
else { else {
ELOG_DEBUG << "client{" << client.get() ELOG_TRACE << "client{" << client.get()
<< "} is nullptr or is closed. we won't collect it"; << "} is closed. we won't collect it";
} }
return; return;
@ -429,10 +389,22 @@ class client_pool : public std::enable_shared_from_this<
return send_request(std::move(op), pool_config_.client_config); return send_request(std::move(op), pool_config_.client_config);
} }
/**
* @brief approx connection of client pools
*
* @return std::size_t
*/
std::size_t free_client_count() const noexcept { std::size_t free_client_count() const noexcept {
return free_clients_.size() + short_connect_clients_.size(); return free_clients_.size() + short_connect_clients_.size();
} }
/**
* @brief approx connection of client pools
*
* @return std::size_t
*/
std::size_t size() const noexcept { return free_client_count(); }
std::string_view get_host_name() const noexcept { return host_name_; } std::string_view get_host_name() const noexcept { return host_name_; }
private: private:
@ -477,8 +449,8 @@ class client_pool : public std::enable_shared_from_this<
coro_io::detail::client_queue<std::unique_ptr<client_t>> coro_io::detail::client_queue<std::unique_ptr<client_t>>
short_connect_clients_; short_connect_clients_;
client_pools_t* pools_manager_ = nullptr; client_pools_t* pools_manager_ = nullptr;
moodycamel::ConcurrentQueue<async_simple::Promise<std::unique_ptr<client_t>>*> std::atomic<int> promise_cnt_ = 0;
promise_queue; moodycamel::ConcurrentQueue<std::shared_ptr<promise_handler>> promise_queue_;
async_simple::Promise<async_simple::Unit> idle_timeout_waiter; async_simple::Promise<async_simple::Unit> idle_timeout_waiter;
std::string host_name_; std::string host_name_;
pool_config pool_config_; pool_config pool_config_;
@ -546,14 +518,6 @@ class client_pools {
iter->second = pool; iter->second = pool;
} }
} }
if (has_inserted) {
ELOG_DEBUG << "add new client pool of {" << host_name
<< "} to hash table";
}
else {
ELOG_DEBUG << "add new client pool of {" << host_name
<< "} failed, element existed.";
}
} }
return iter->second; return iter->second;
} }

View File

@ -16,6 +16,7 @@
#pragma once #pragma once
#include <async_simple/Executor.h> #include <async_simple/Executor.h>
#include <async_simple/coro/Collect.h>
#include <async_simple/coro/Lazy.h> #include <async_simple/coro/Lazy.h>
#include <async_simple/coro/Sleep.h> #include <async_simple/coro/Sleep.h>
#include <async_simple/coro/SyncAwait.h> #include <async_simple/coro/SyncAwait.h>
@ -26,6 +27,7 @@
#include <asio/connect.hpp> #include <asio/connect.hpp>
#include <asio/dispatch.hpp> #include <asio/dispatch.hpp>
#include <asio/experimental/channel.hpp>
#include <asio/ip/tcp.hpp> #include <asio/ip/tcp.hpp>
#include <asio/read.hpp> #include <asio/read.hpp>
#include <asio/read_at.hpp> #include <asio/read_at.hpp>
@ -36,16 +38,21 @@
#include <deque> #include <deque>
#include "io_context_pool.hpp" #include "io_context_pool.hpp"
#include "ylt/util/type_traits.h"
namespace coro_io { namespace coro_io {
template <typename T>
constexpr inline bool is_lazy_v =
util::is_specialization_v<std::remove_cvref_t<T>, async_simple::coro::Lazy>;
template <typename Arg, typename Derived> template <typename Arg, typename Derived>
class callback_awaitor_base { class callback_awaitor_base {
private: private:
template <typename Op> template <typename Op>
class callback_awaitor_impl { class callback_awaitor_impl {
public: public:
callback_awaitor_impl(Derived &awaitor, const Op &op) noexcept callback_awaitor_impl(Derived &awaitor, Op &op) noexcept
: awaitor(awaitor), op(op) {} : awaitor(awaitor), op(op) {}
constexpr bool await_ready() const noexcept { return false; } constexpr bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> handle) noexcept { void await_suspend(std::coroutine_handle<> handle) noexcept {
@ -66,7 +73,7 @@ class callback_awaitor_base {
private: private:
Derived &awaitor; Derived &awaitor;
const Op &op; Op &op;
}; };
public: public:
@ -94,7 +101,7 @@ class callback_awaitor_base {
Derived *obj; Derived *obj;
}; };
template <typename Op> template <typename Op>
callback_awaitor_impl<Op> await_resume(const Op &op) noexcept { callback_awaitor_impl<Op> await_resume(Op &&op) noexcept {
return callback_awaitor_impl<Op>{static_cast<Derived &>(*this), op}; return callback_awaitor_impl<Op>{static_cast<Derived &>(*this), op};
} }
@ -272,13 +279,11 @@ inline async_simple::coro::Lazy<std::error_code> async_handshake(
#endif #endif
class period_timer : public asio::steady_timer { class period_timer : public asio::steady_timer {
public: public:
using asio::steady_timer::steady_timer;
template <typename T> template <typename T>
period_timer(coro_io::ExecutorWrapper<T> *executor) period_timer(coro_io::ExecutorWrapper<T> *executor)
: asio::steady_timer(executor->get_asio_executor()) {} : asio::steady_timer(executor->get_asio_executor()) {}
template <typename executor_t, typename Rep, typename Period>
period_timer(const executor_t &executor,
const std::chrono::duration<Rep, Period> &timeout_duration)
: asio::steady_timer(executor, timeout_duration) {}
async_simple::coro::Lazy<bool> async_await() noexcept { async_simple::coro::Lazy<bool> async_await() noexcept {
callback_awaitor<bool> awaitor; callback_awaitor<bool> awaitor;
@ -309,10 +314,10 @@ inline async_simple::coro::Lazy<void> sleep_for(Duration d) {
} }
} }
template <typename R, typename Func> template <typename R, typename Func, typename Executor>
struct post_helper { struct post_helper {
void operator()(auto handler) const { void operator()(auto handler) {
asio::dispatch(e->get_asio_executor(), [this, handler]() { asio::post(e, [this, handler]() {
try { try {
if constexpr (std::is_same_v<R, async_simple::Try<void>>) { if constexpr (std::is_same_v<R, async_simple::Try<void>>) {
func(); func();
@ -329,22 +334,129 @@ struct post_helper {
} }
}); });
} }
coro_io::ExecutorWrapper<> *e; Executor e;
Func func; Func func;
}; };
template <typename Func, typename Executor>
inline async_simple::coro::Lazy<
async_simple::Try<typename util::function_traits<Func>::return_type>>
post(Func func, Executor executor) {
using R =
async_simple::Try<typename util::function_traits<Func>::return_type>;
callback_awaitor<R> awaitor;
post_helper<R, Func, Executor> helper{executor, std::move(func)};
co_return co_await awaitor.await_resume(helper);
}
template <typename Func> template <typename Func>
inline async_simple::coro::Lazy< inline async_simple::coro::Lazy<
async_simple::Try<typename util::function_traits<Func>::return_type>> async_simple::Try<typename util::function_traits<Func>::return_type>>
post(Func func, post(Func func,
coro_io::ExecutorWrapper<> *e = coro_io::get_global_block_executor()) { coro_io::ExecutorWrapper<> *e = coro_io::get_global_block_executor()) {
using R = co_return co_await post(std::move(func), e->get_asio_executor());
async_simple::Try<typename util::function_traits<Func>::return_type>; }
callback_awaitor<R> awaitor; template <typename R>
struct coro_channel
: public asio::experimental::channel<void(std::error_code, R)> {
using return_type = R;
using ValueType = std::pair<std::error_code, R>;
using asio::experimental::channel<void(std::error_code, R)>::channel;
};
post_helper<R, Func> helper{e, std::move(func)}; template <typename R>
co_return co_await awaitor.await_resume(helper); inline coro_channel<R> create_channel(
size_t capacity,
asio::io_context::executor_type executor =
coro_io::get_global_block_executor()->get_asio_executor()) {
return coro_channel<R>(executor, capacity);
}
template <typename T>
inline async_simple::coro::Lazy<std::error_code> async_send(
asio::experimental::channel<void(std::error_code, T)> &channel, T val) {
callback_awaitor<std::error_code> awaitor;
co_return co_await awaitor.await_resume(
[&, val = std::move(val)](auto handler) {
channel.async_send({}, std::move(val), [handler](const auto &ec) {
handler.set_value_then_resume(ec);
});
});
}
template <typename Channel>
async_simple::coro::Lazy<std::pair<
std::error_code,
typename Channel::return_type>> inline async_receive(Channel &channel) {
callback_awaitor<std::pair<std::error_code, typename Channel::return_type>>
awaitor;
co_return co_await awaitor.await_resume([&](auto handler) {
channel.async_receive([handler](auto ec, auto val) {
handler.set_value_then_resume(std::make_pair(ec, std::move(val)));
});
});
}
template <typename T>
inline decltype(auto) select_impl(T &pair) {
using Func = std::tuple_element_t<1, std::remove_cvref_t<T>>;
using ValueType =
typename std::tuple_element_t<0, std::remove_cvref_t<T>>::ValueType;
using return_type = std::invoke_result_t<Func, async_simple::Try<ValueType>>;
auto &callback = std::get<1>(pair);
if constexpr (coro_io::is_lazy_v<return_type>) {
auto executor = std::get<0>(pair).getExecutor();
return std::make_pair(
std::move(std::get<0>(pair)),
[executor, callback = std::move(callback)](auto &&val) {
if (executor) {
callback(std::move(val)).via(executor).start([](auto &&) {
});
}
else {
callback(std::move(val)).start([](auto &&) {
});
}
});
}
else {
return pair;
}
}
template <typename... T>
inline auto select(T &&...args) {
return async_simple::coro::collectAny(select_impl(args)...);
}
template <typename T, typename Callback>
inline auto select(std::vector<T> vec, Callback callback) {
if constexpr (coro_io::is_lazy_v<Callback>) {
std::vector<async_simple::Executor *> executors;
for (auto &lazy : vec) {
executors.push_back(lazy.getExecutor());
}
return async_simple::coro::collectAny(
std::move(vec),
[executors, callback = std::move(callback)](size_t index, auto &&val) {
auto executor = executors[index];
if (executor) {
callback(index, std::move(val)).via(executor).start([](auto &&) {
});
}
else {
callback(index, std::move(val)).start([](auto &&) {
});
}
});
}
else {
return async_simple::coro::collectAny(std::move(vec), std::move(callback));
}
} }
template <typename Socket, typename AsioBuffer> template <typename Socket, typename AsioBuffer>

View File

@ -17,17 +17,21 @@
#include <async_simple/Executor.h> #include <async_simple/Executor.h>
#include <async_simple/coro/Lazy.h> #include <async_simple/coro/Lazy.h>
#include <asio/dispatch.hpp>
#include <asio/io_context.hpp> #include <asio/io_context.hpp>
#include <asio/post.hpp>
#include <asio/steady_timer.hpp> #include <asio/steady_timer.hpp>
#include <atomic> #include <atomic>
#include <future> #include <future>
#include <iostream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
#include <ylt/easylog.hpp> #ifdef __linux__
#include <pthread.h>
#include <sched.h>
#endif
namespace coro_io { namespace coro_io {
@ -48,10 +52,10 @@ class ExecutorWrapper : public async_simple::Executor {
virtual bool schedule(Func func) override { virtual bool schedule(Func func) override {
if constexpr (requires(ExecutorImpl e) { e.post(std::move(func)); }) { if constexpr (requires(ExecutorImpl e) { e.post(std::move(func)); }) {
executor_.post(std::move(func)); executor_.dispatch(std::move(func));
} }
else { else {
asio::post(executor_, std::move(func)); asio::dispatch(executor_, std::move(func));
} }
return true; return true;
@ -64,7 +68,7 @@ class ExecutorWrapper : public async_simple::Executor {
executor.post(std::move(func)); executor.post(std::move(func));
} }
else { else {
asio::post(executor, std::move(func)); asio::dispatch(executor, std::move(func));
} }
return true; return true;
} }
@ -72,7 +76,7 @@ class ExecutorWrapper : public async_simple::Executor {
context_t &context() { return executor_.context(); } context_t &context() { return executor_.context(); }
auto get_asio_executor() { return executor_; } auto get_asio_executor() const { return executor_; }
operator ExecutorImpl() { return executor_; } operator ExecutorImpl() { return executor_; }
@ -108,12 +112,14 @@ get_current_executor() {
class io_context_pool { class io_context_pool {
public: public:
using executor_type = asio::io_context::executor_type; using executor_type = asio::io_context::executor_type;
explicit io_context_pool(std::size_t pool_size) : next_io_context_(0) { explicit io_context_pool(std::size_t pool_size, bool cpu_affinity = false)
: next_io_context_(0), cpu_affinity_(cpu_affinity) {
if (pool_size == 0) { if (pool_size == 0) {
pool_size = 1; // set default value as 1 pool_size = 1; // set default value as 1
} }
easylog::logger<>::instance(); total_thread_num_ += pool_size;
for (std::size_t i = 0; i < pool_size; ++i) { for (std::size_t i = 0; i < pool_size; ++i) {
io_context_ptr io_context(new asio::io_context(1)); io_context_ptr io_context(new asio::io_context(1));
work_ptr work(new asio::io_context::work(*io_context)); work_ptr work(new asio::io_context::work(*io_context));
@ -141,6 +147,19 @@ class io_context_pool {
svr->run(); svr->run();
}, },
io_contexts_[i])); io_contexts_[i]));
#ifdef __linux__
if (cpu_affinity_) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(i, &cpuset);
int rc = pthread_setaffinity_np(threads.back()->native_handle(),
sizeof(cpu_set_t), &cpuset);
if (rc != 0) {
std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n";
}
}
#endif
} }
for (std::size_t i = 0; i < threads.size(); ++i) { for (std::size_t i = 0; i < threads.size(); ++i) {
@ -188,6 +207,8 @@ class io_context_pool {
template <typename T> template <typename T>
friend io_context_pool &g_io_context_pool(); friend io_context_pool &g_io_context_pool();
static size_t get_total_thread_num() { return total_thread_num_; }
private: private:
using io_context_ptr = std::shared_ptr<asio::io_context>; using io_context_ptr = std::shared_ptr<asio::io_context>;
using work_ptr = std::shared_ptr<asio::io_context::work>; using work_ptr = std::shared_ptr<asio::io_context::work>;
@ -199,8 +220,14 @@ class io_context_pool {
std::promise<void> promise_; std::promise<void> promise_;
std::atomic<bool> has_run_or_stop_ = false; std::atomic<bool> has_run_or_stop_ = false;
std::once_flag flag_; std::once_flag flag_;
bool cpu_affinity_ = false;
inline static std::atomic<size_t> total_thread_num_ = 0;
}; };
inline size_t get_total_thread_num() {
return io_context_pool::get_total_thread_num();
}
class multithread_context_pool { class multithread_context_pool {
public: public:
multithread_context_pool(size_t thd_num = std::thread::hardware_concurrency()) multithread_context_pool(size_t thd_num = std::thread::hardware_concurrency())
@ -211,7 +238,7 @@ class multithread_context_pool {
~multithread_context_pool() { stop(); } ~multithread_context_pool() { stop(); }
void run() { void run() {
for (std::size_t i = 0; i < thd_num_; i++) { for (int i = 0; i < thd_num_; i++) {
thds_.emplace_back([this] { thds_.emplace_back([this] {
ioc_.run(); ioc_.run();
}); });
@ -248,7 +275,7 @@ template <typename T = io_context_pool>
inline T &g_io_context_pool( inline T &g_io_context_pool(
unsigned pool_size = std::thread::hardware_concurrency()) { unsigned pool_size = std::thread::hardware_concurrency()) {
static auto _g_io_context_pool = std::make_shared<T>(pool_size); static auto _g_io_context_pool = std::make_shared<T>(pool_size);
static bool run_helper = [](auto pool) { [[maybe_unused]] static bool run_helper = [](auto pool) {
std::thread thrd{[pool] { std::thread thrd{[pool] {
pool->run(); pool->run();
}}; }};
@ -258,11 +285,23 @@ inline T &g_io_context_pool(
return *_g_io_context_pool; return *_g_io_context_pool;
} }
template <typename T = io_context_pool>
inline std::shared_ptr<T> create_io_context_pool(
unsigned pool_size = std::thread::hardware_concurrency()) {
auto pool = std::make_shared<T>(pool_size);
std::thread thrd{[pool] {
pool->run();
}};
thrd.detach();
return pool;
}
template <typename T = io_context_pool> template <typename T = io_context_pool>
inline T &g_block_io_context_pool( inline T &g_block_io_context_pool(
unsigned pool_size = std::thread::hardware_concurrency()) { unsigned pool_size = std::thread::hardware_concurrency()) {
static auto _g_io_context_pool = std::make_shared<T>(pool_size); static auto _g_io_context_pool = std::make_shared<T>(pool_size);
static bool run_helper = [](auto pool) { [[maybe_unused]] static bool run_helper = [](auto pool) {
std::thread thrd{[pool] { std::thread thrd{[pool] {
pool->run(); pool->run();
}}; }};

View File

@ -42,6 +42,8 @@ class rate_limiter {
do_set_rate(permitsPerSecond, current_time_mills()); do_set_rate(permitsPerSecond, current_time_mills());
} }
virtual ~rate_limiter() {}
protected: protected:
virtual void do_set_rate( virtual void do_set_rate(
double permitsPerSecond, double permitsPerSecond,
@ -67,6 +69,9 @@ class rate_limiter {
}; };
class abstract_smooth_rate_limiter : public rate_limiter { class abstract_smooth_rate_limiter : public rate_limiter {
public:
virtual ~abstract_smooth_rate_limiter() {}
protected: protected:
virtual void do_set_rate(double permits_per_second, virtual void do_set_rate(double permits_per_second,
double stable_internal_micros) = 0; double stable_internal_micros) = 0;

View File

@ -14,5 +14,4 @@
* limitations under the License. * limitations under the License.
*/ */
#pragma once #pragma once
#include "impl/protocol/coro_rpc_protocol.hpp" #include "impl/protocol/coro_rpc_protocol.hpp"

View File

@ -29,6 +29,7 @@
#include "coro_connection.hpp" #include "coro_connection.hpp"
#include "ylt/coro_rpc/impl/errno.h" #include "ylt/coro_rpc/impl/errno.h"
#include "ylt/util/type_traits.h" #include "ylt/util/type_traits.h"
#include "ylt/util/utils.hpp"
namespace coro_rpc { namespace coro_rpc {
/*! /*!
@ -43,14 +44,14 @@ class context_base {
typename rpc_protocol::req_header &get_req_head() { return self_->req_head_; } typename rpc_protocol::req_header &get_req_head() { return self_->req_head_; }
bool check_status() { bool check_status() {
auto old_flag = self_->has_response_.exchange(true); auto old_flag = self_->status_.exchange(context_status::start_response);
if (old_flag != false) if (old_flag != context_status::init)
AS_UNLIKELY { AS_UNLIKELY {
ELOGV(ERROR, "response message more than one time"); ELOGV(ERROR, "response message more than one time");
return false; return false;
} }
if (has_closed()) if (self_->has_closed())
AS_UNLIKELY { AS_UNLIKELY {
ELOGV(DEBUG, "response_msg failed: connection has been closed"); ELOGV(DEBUG, "response_msg failed: connection has been closed");
return false; return false;
@ -67,8 +68,7 @@ class context_base {
context_base(std::shared_ptr<context_info_t<rpc_protocol>> context_info) context_base(std::shared_ptr<context_info_t<rpc_protocol>> context_info)
: self_(std::move(context_info)) { : self_(std::move(context_info)) {
if (self_->conn_) { if (self_->conn_) {
self_->conn_->set_rpc_call_type( self_->conn_->set_rpc_return_by_callback();
coro_connection::rpc_call_type::callback_started);
} }
}; };
context_base() = default; context_base() = default;
@ -79,8 +79,10 @@ class context_base {
std::string_view error_msg) { std::string_view error_msg) {
if (!check_status()) if (!check_status())
AS_UNLIKELY { return; }; AS_UNLIKELY { return; };
self_->conn_->template response_error<rpc_protocol>( ELOGI << "rpc error in function:" << self_->get_rpc_function_name()
error_code, error_msg, self_->req_head_, self_->is_delay_); << ". error code:" << error_code.ec << ". message : " << error_msg;
self_->conn_->template response_error<rpc_protocol>(error_code, error_msg,
self_->req_head_);
} }
void response_error(coro_rpc::err_code error_code) { void response_error(coro_rpc::err_code error_code) {
response_error(error_code, error_code.message()); response_error(error_code, error_code.message());
@ -98,16 +100,15 @@ class context_base {
*/ */
template <typename... Args> template <typename... Args>
void response_msg(Args &&...args) { void response_msg(Args &&...args) {
if constexpr (std::is_same_v<return_msg_type, void>) {
static_assert(sizeof...(args) == 0, "illegal args");
if (!check_status()) if (!check_status())
AS_UNLIKELY { return; }; AS_UNLIKELY { return; };
if constexpr (std::is_same_v<return_msg_type, void>) {
static_assert(sizeof...(args) == 0, "illegal args");
std::visit( std::visit(
[&]<typename serialize_proto>(const serialize_proto &) { [&]<typename serialize_proto>(const serialize_proto &) {
self_->conn_->template response_msg<rpc_protocol>( self_->conn_->template response_msg<rpc_protocol>(
serialize_proto::serialize(), serialize_proto::serialize(),
std::move(self_->resp_attachment_), self_->req_head_, std::move(self_->resp_attachment_), self_->req_head_);
self_->is_delay_);
}, },
*rpc_protocol::get_serialize_protocol(self_->req_head_)); *rpc_protocol::get_serialize_protocol(self_->req_head_));
} }
@ -115,83 +116,26 @@ class context_base {
static_assert( static_assert(
requires { return_msg_type{std::forward<Args>(args)...}; }, requires { return_msg_type{std::forward<Args>(args)...}; },
"constructed return_msg_type failed by illegal args"); "constructed return_msg_type failed by illegal args");
if (!check_status())
AS_UNLIKELY { return; };
return_msg_type ret{std::forward<Args>(args)...}; return_msg_type ret{std::forward<Args>(args)...};
std::visit( std::visit(
[&]<typename serialize_proto>(const serialize_proto &) { [&]<typename serialize_proto>(const serialize_proto &) {
self_->conn_->template response_msg<rpc_protocol>( self_->conn_->template response_msg<rpc_protocol>(
serialize_proto::serialize(ret), serialize_proto::serialize(ret),
std::move(self_->resp_attachment_), self_->req_head_, std::move(self_->resp_attachment_), self_->req_head_);
self_->is_delay_);
}, },
*rpc_protocol::get_serialize_protocol(self_->req_head_)); *rpc_protocol::get_serialize_protocol(self_->req_head_));
// response_handler_(std::move(conn_), std::move(ret)); // response_handler_(std::move(conn_), std::move(ret));
} }
/*finish here*/
self_->status_ = context_status::finish_response;
} }
const context_info_t<rpc_protocol> *get_context_info() const noexcept {
/*! return self_.get();
* Check connection closed or not
*
* @return true if closed, otherwise false
*/
bool has_closed() const { return self_->conn_->has_closed(); }
/*!
* Close connection
*/
void close() { return self_->conn_->async_close(); }
/*!
* Get the unique connection ID
* @return connection id
*/
uint64_t get_connection_id() { return self_->conn_->conn_id_; }
/*!
* Set the response_attachment
* @return a ref of response_attachment
*/
void set_response_attachment(std::string attachment) {
set_response_attachment([attachment = std::move(attachment)] {
return std::string_view{attachment};
});
} }
context_info_t<rpc_protocol> *get_context_info() noexcept {
/*! return self_.get();
* Set the response_attachment
* @return a ref of response_attachment
*/
void set_response_attachment(std::function<std::string_view()> attachment) {
self_->resp_attachment_ = std::move(attachment);
} }
/*!
* Get the request attachment
* @return connection id
*/
std::string_view get_request_attachment() const {
return self_->req_attachment_;
}
/*!
* Release the attachment
* @return connection id
*/
std::string release_request_attachment() {
return std::move(self_->req_attachment_);
}
void set_delay() {
self_->is_delay_ = true;
self_->conn_->set_rpc_call_type(
coro_connection::rpc_call_type::callback_with_delay);
}
std::any &tag() { return self_->conn_->tag(); }
const std::any &tag() const { return self_->conn_->tag(); }
}; };
template <typename T> template <typename T>

View File

@ -26,21 +26,29 @@
#include <memory> #include <memory>
#include <string_view> #include <string_view>
#include <system_error> #include <system_error>
#include <thread>
#include <utility> #include <utility>
#include <ylt/easylog.hpp> #include <ylt/easylog.hpp>
#include "async_simple/Common.h"
#include "ylt/coro_io/coro_io.hpp" #include "ylt/coro_io/coro_io.hpp"
#include "ylt/coro_rpc/impl/errno.h" #include "ylt/coro_rpc/impl/errno.h"
#include "ylt/util/utils.hpp"
#ifdef UNIT_TEST_INJECT #ifdef UNIT_TEST_INJECT
#include "inject_action.hpp" #include "inject_action.hpp"
#endif #endif
namespace coro_rpc { namespace coro_rpc {
class coro_connection; class coro_connection;
using rpc_conn = std::shared_ptr<coro_connection>; using rpc_conn = std::shared_ptr<coro_connection>;
enum class context_status : int { init, start_response, finish_response };
template <typename rpc_protocol> template <typename rpc_protocol>
struct context_info_t { struct context_info_t {
#ifndef CORO_RPC_TEST
private:
#endif
typename rpc_protocol::route_key_t key_;
typename rpc_protocol::router &router_;
std::shared_ptr<coro_connection> conn_; std::shared_ptr<coro_connection> conn_;
typename rpc_protocol::req_header req_head_; typename rpc_protocol::req_header req_head_;
std::string req_body_; std::string req_body_;
@ -48,11 +56,46 @@ struct context_info_t {
std::function<std::string_view()> resp_attachment_ = [] { std::function<std::string_view()> resp_attachment_ = [] {
return std::string_view{}; return std::string_view{};
}; };
std::atomic<bool> has_response_ = false; std::atomic<context_status> status_ = context_status::init;
bool is_delay_ = false;
context_info_t(std::shared_ptr<coro_connection> &&conn) public:
: conn_(std::move(conn)) {} template <typename, typename>
friend class context_base;
friend class coro_connection;
context_info_t(typename rpc_protocol::router &r,
std::shared_ptr<coro_connection> &&conn)
: router_(r), conn_(std::move(conn)) {}
context_info_t(typename rpc_protocol::router &r,
std::shared_ptr<coro_connection> &&conn,
std::string &&req_body_buf, std::string &&req_attachment_buf)
: router_(r),
conn_(std::move(conn)),
req_body_(std::move(req_body_buf)),
req_attachment_(std::move(req_attachment_buf)) {}
uint64_t get_connection_id() noexcept;
uint64_t has_closed() const noexcept;
void close();
uint64_t get_connection_id() const noexcept;
void set_response_attachment(std::string_view attachment);
void set_response_attachment(std::string attachment);
void set_response_attachment(std::function<std::string_view()> attachment);
std::string_view get_request_attachment() const;
std::string release_request_attachment();
std::any &tag() noexcept;
const std::any &tag() const noexcept;
asio::ip::tcp::endpoint get_local_endpoint() const noexcept;
asio::ip::tcp::endpoint get_remote_endpoint() const noexcept;
uint64_t get_request_id() const noexcept;
std::string_view get_rpc_function_name() const {
return router_.get_name(key_);
}
}; };
namespace detail {
template <typename rpc_protocol>
context_info_t<rpc_protocol> *&set_context();
}
/*! /*!
* TODO: add doc * TODO: add doc
*/ */
@ -70,13 +113,6 @@ struct context_info_t {
class coro_connection : public std::enable_shared_from_this<coro_connection> { class coro_connection : public std::enable_shared_from_this<coro_connection> {
public: public:
enum rpc_call_type {
non_callback,
callback_with_delay,
callback_finished,
callback_started
};
/*! /*!
* *
* @param io_context * @param io_context
@ -89,7 +125,6 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
std::chrono::seconds(0)) std::chrono::seconds(0))
: executor_(executor), : executor_(executor),
socket_(std::move(socket)), socket_(std::move(socket)),
resp_err_(),
timer_(executor->get_asio_executor()) { timer_(executor->get_asio_executor()) {
if (timeout_duration == std::chrono::seconds(0)) { if (timeout_duration == std::chrono::seconds(0)) {
return; return;
@ -148,16 +183,13 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
template <typename rpc_protocol, typename Socket> template <typename rpc_protocol, typename Socket>
async_simple::coro::Lazy<void> start_impl( async_simple::coro::Lazy<void> start_impl(
typename rpc_protocol::router &router, Socket &socket) noexcept { typename rpc_protocol::router &router, Socket &socket) noexcept {
auto context_info = auto context_info = std::make_shared<context_info_t<rpc_protocol>>(
std::make_shared<context_info_t<rpc_protocol>>(shared_from_this()); router, shared_from_this());
std::string resp_error_msg;
while (true) {
auto &req_head = context_info->req_head_;
auto &body = context_info->req_body_;
auto &req_attachment = context_info->req_attachment_;
reset_timer(); reset_timer();
auto ec = co_await rpc_protocol::read_head(socket, req_head); while (true) {
cancel_timer(); typename rpc_protocol::req_header req_head_tmp;
// timer will be reset after rpc call response
auto ec = co_await rpc_protocol::read_head(socket, req_head_tmp);
// `co_await async_read` uses asio::async_read underlying. // `co_await async_read` uses asio::async_read underlying.
// If eof occurred, the bytes_transferred of `co_await async_read` must // If eof occurred, the bytes_transferred of `co_await async_read` must
// less than RPC_HEAD_LEN. Incomplete data will be discarded. // less than RPC_HEAD_LEN. Incomplete data will be discarded.
@ -169,7 +201,7 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
} }
#ifdef UNIT_TEST_INJECT #ifdef UNIT_TEST_INJECT
client_id_ = req_head.seq_num; client_id_ = req_head_tmp.seq_num;
ELOGV(INFO, "conn_id %d, client_id %d", conn_id_, client_id_); ELOGV(INFO, "conn_id %d, client_id %d", conn_id_, client_id_);
#endif #endif
@ -183,6 +215,28 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
break; break;
} }
#endif #endif
// try to reuse context
if (is_rpc_return_by_callback_) {
// cant reuse context,make shared new one
is_rpc_return_by_callback_ = false;
if (context_info->status_ != context_status::finish_response) {
// cant reuse buffer
context_info = std::make_shared<context_info_t<rpc_protocol>>(
router, shared_from_this());
}
else {
// reuse string buffer
context_info = std::make_shared<context_info_t<rpc_protocol>>(
router, shared_from_this(), std::move(context_info->req_body_),
std::move(context_info->req_attachment_));
}
}
auto &req_head = context_info->req_head_;
auto &body = context_info->req_body_;
auto &req_attachment = context_info->req_attachment_;
auto &key = context_info->key_;
req_head = std::move(req_head_tmp);
auto serialize_proto = rpc_protocol::get_serialize_protocol(req_head); auto serialize_proto = rpc_protocol::get_serialize_protocol(req_head);
if (!serialize_proto.has_value()) if (!serialize_proto.has_value())
@ -197,6 +251,7 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
ec = co_await rpc_protocol::read_payload(socket, req_head, body, ec = co_await rpc_protocol::read_payload(socket, req_head, body,
req_attachment); req_attachment);
cancel_timer();
payload = std::string_view{body}; payload = std::string_view{body};
if (ec) if (ec)
@ -207,85 +262,77 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
break; break;
} }
std::pair<coro_rpc::errc, std::string> pair{}; key = rpc_protocol::get_route_key(req_head);
auto key = rpc_protocol::get_route_key(req_head);
auto handler = router.get_handler(key); auto handler = router.get_handler(key);
++rpc_processing_cnt_;
if (!handler) { if (!handler) {
auto coro_handler = router.get_coro_handler(key); auto coro_handler = router.get_coro_handler(key);
pair = co_await router.route_coro(coro_handler, payload, context_info, set_rpc_return_by_callback();
serialize_proto.value(), key); router.route_coro(coro_handler, payload, serialize_proto.value(), key)
.via(executor_)
.setLazyLocal((void *)context_info.get())
.start([context_info](auto &&result) mutable {
std::pair<coro_rpc::err_code, std::string> &ret = result.value();
if (ret.first)
AS_UNLIKELY {
ELOGI << "rpc error in function:"
<< context_info->get_rpc_function_name()
<< ". error code:" << ret.first.ec
<< ". message : " << ret.second;
}
auto executor = context_info->conn_->get_executor();
executor->schedule([context_info = std::move(context_info),
ret = std::move(ret)]() mutable {
context_info->conn_->template direct_response_msg<rpc_protocol>(
ret.first, ret.second, context_info->req_head_,
std::move(context_info->resp_attachment_));
});
});
} }
else { else {
pair = router.route(handler, payload, context_info, coro_rpc::detail::set_context<rpc_protocol>() = context_info.get();
serialize_proto.value(), key); auto &&[resp_err, resp_buf] = router.route(
} handler, payload, context_info, serialize_proto.value(), key);
if (is_rpc_return_by_callback_) {
auto &[resp_err, resp_buf] = pair; if (!resp_err) {
switch (rpc_call_type_) {
default:
unreachable();
case rpc_call_type::non_callback:
break;
case rpc_call_type::callback_with_delay:
++delay_resp_cnt;
rpc_call_type_ = rpc_call_type::non_callback;
continue;
case rpc_call_type::callback_finished:
continue;
case rpc_call_type::callback_started:
coro_io::callback_awaitor<void> awaitor;
rpc_call_type_ = rpc_call_type::callback_finished;
co_await awaitor.await_resume([this](auto handler) {
this->callback_awaitor_handler_ = std::move(handler);
});
context_info->has_response_ = false;
context_info->resp_attachment_ = []() -> std::string_view {
return {};
};
rpc_call_type_ = rpc_call_type::non_callback;
continue; continue;
} }
resp_error_msg.clear(); else {
if (!!resp_err) ELOGI << "rpc error in function:"
AS_UNLIKELY { std::swap(resp_buf, resp_error_msg); } << context_info->get_rpc_function_name()
std::string header_buf = rpc_protocol::prepare_response( << ". error code:" << resp_err.ec
resp_buf, req_head, 0, resp_err, resp_error_msg); << ". message : " << resp_buf;
is_rpc_return_by_callback_ = false;
}
}
#ifdef UNIT_TEST_INJECT #ifdef UNIT_TEST_INJECT
if (g_action == inject_action::close_socket_after_send_length) { if (g_action == inject_action::close_socket_after_send_length) {
ELOGV(WARN, ELOGV(WARN, "inject action: close_socket_after_send_length", conn_id_,
"inject action: close_socket_after_send_length conn_id %d, " client_id_);
"client_id %d", std::string header_buf = rpc_protocol::prepare_response(
conn_id_, client_id_); resp_buf, req_head, 0, resp_err, "");
co_await coro_io::async_write(socket, asio::buffer(header_buf)); co_await coro_io::async_write(socket, asio::buffer(header_buf));
close(); close();
break; break;
} }
if (g_action == inject_action::server_send_bad_rpc_result) { if (g_action == inject_action::server_send_bad_rpc_result) {
ELOGV(WARN, ELOGV(
WARN,
"inject action: server_send_bad_rpc_result conn_id %d, client_id " "inject action: server_send_bad_rpc_result conn_id %d, client_id "
"%d", "%d",
conn_id_, client_id_); conn_id_, client_id_);
resp_buf[0] = resp_buf[0] + 1; resp_buf[0] = resp_buf[0] + 1;
} }
#endif #endif
if (!resp_err_) direct_response_msg<rpc_protocol>(
AS_LIKELY { resp_err, resp_buf, req_head,
if (!resp_err) std::move(context_info->resp_attachment_));
AS_UNLIKELY { resp_err_ = resp_err; } context_info->resp_attachment_ = [] {
write_queue_.emplace_back(std::move(header_buf), std::move(resp_buf),
[] {
return std::string_view{}; return std::string_view{};
}); };
if (write_queue_.size() == 1) {
send_data().start([self = shared_from_this()](auto &&) {
});
}
if (!!resp_err)
AS_UNLIKELY { break; }
} }
} }
cancel_timer();
} }
/*! /*!
* send `ret` to RPC client * send `ret` to RPC client
@ -293,37 +340,54 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
* @tparam R message type * @tparam R message type
* @param ret object of message type * @param ret object of message type
*/ */
template <typename rpc_protocol>
void direct_response_msg(coro_rpc::err_code &resp_err, std::string &resp_buf,
const typename rpc_protocol::req_header &req_head,
std::function<std::string_view()> &&attachment) {
std::string resp_error_msg;
if (resp_err) {
resp_error_msg = std::move(resp_buf);
resp_buf = {};
ELOGV(WARNING, "rpc route/execute error, error msg: %s",
resp_error_msg.data());
}
std::string header_buf = rpc_protocol::prepare_response(
resp_buf, req_head, attachment().length(), resp_err, resp_error_msg);
response(std::move(header_buf), std::move(resp_buf), std::move(attachment),
nullptr)
.start([](auto &&) {
});
}
template <typename rpc_protocol> template <typename rpc_protocol>
void response_msg(std::string &&body_buf, void response_msg(std::string &&body_buf,
std::function<std::string_view()> &&resp_attachment, std::function<std::string_view()> &&resp_attachment,
const typename rpc_protocol::req_header &req_head, const typename rpc_protocol::req_header &req_head) {
bool is_delay) {
std::string header_buf = rpc_protocol::prepare_response( std::string header_buf = rpc_protocol::prepare_response(
body_buf, req_head, resp_attachment().size()); body_buf, req_head, resp_attachment().size());
response(std::move(header_buf), std::move(body_buf), response(std::move(header_buf), std::move(body_buf),
std::move(resp_attachment), shared_from_this(), is_delay) std::move(resp_attachment), shared_from_this())
.via(executor_) .via(executor_)
.detach(); .detach();
} }
template <typename rpc_protocol> template <typename rpc_protocol>
void response_error(coro_rpc::errc ec, std::string_view error_msg, void response_error(coro_rpc::errc ec, std::string_view error_msg,
const typename rpc_protocol::req_header &req_head, const typename rpc_protocol::req_header &req_head) {
bool is_delay) {
std::function<std::string_view()> attach_ment = []() -> std::string_view { std::function<std::string_view()> attach_ment = []() -> std::string_view {
return {}; return {};
}; };
std::string body_buf; std::string body_buf;
std::string header_buf = rpc_protocol::prepare_response( std::string header_buf =
body_buf, req_head, 0, ec, error_msg, true); rpc_protocol::prepare_response(body_buf, req_head, 0, ec, error_msg);
response(std::move(header_buf), std::move(body_buf), std::move(attach_ment), response(std::move(header_buf), std::move(body_buf), std::move(attach_ment),
shared_from_this(), is_delay) shared_from_this())
.via(executor_) .via(executor_)
.detach(); .detach();
} }
void set_rpc_call_type(enum rpc_call_type r) { rpc_call_type_ = r; } void set_rpc_return_by_callback() { is_rpc_return_by_callback_ = true; }
/*! /*!
* Check the connection has closed or not * Check the connection has closed or not
@ -348,24 +412,32 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
}); });
} }
void close_coro() {}
using QuitCallback = std::function<void(const uint64_t &conn_id)>; using QuitCallback = std::function<void(const uint64_t &conn_id)>;
void set_quit_callback(QuitCallback callback, uint64_t conn_id) { void set_quit_callback(QuitCallback callback, uint64_t conn_id) {
quit_callback_ = std::move(callback); quit_callback_ = std::move(callback);
conn_id_ = conn_id; conn_id_ = conn_id;
} }
uint64_t get_connection_id() const noexcept { return conn_id_; }
std::any &tag() { return tag_; } std::any &tag() { return tag_; }
const std::any &tag() const { return tag_; } const std::any &tag() const { return tag_; }
auto &get_executor() { return *executor_; } auto get_executor() { return executor_; }
asio::ip::tcp::endpoint get_remote_endpoint() {
return socket_.remote_endpoint();
}
asio::ip::tcp::endpoint get_local_endpoint() {
return socket_.local_endpoint();
}
private: private:
async_simple::coro::Lazy<void> response( async_simple::coro::Lazy<void> response(
std::string header_buf, std::string body_buf, std::string header_buf, std::string body_buf,
std::function<std::string_view()> resp_attachment, rpc_conn self, std::function<std::string_view()> resp_attachment,
bool is_delay) noexcept { rpc_conn self) noexcept {
if (has_closed()) if (has_closed())
AS_UNLIKELY { AS_UNLIKELY {
ELOGV(DEBUG, "response_msg failed: connection has been closed"); ELOGV(DEBUG, "response_msg failed: connection has been closed");
@ -379,25 +451,14 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
#endif #endif
write_queue_.emplace_back(std::move(header_buf), std::move(body_buf), write_queue_.emplace_back(std::move(header_buf), std::move(body_buf),
std::move(resp_attachment)); std::move(resp_attachment));
if (is_delay) { --rpc_processing_cnt_;
--delay_resp_cnt; assert(rpc_processing_cnt_ >= 0);
assert(delay_resp_cnt >= 0);
reset_timer(); reset_timer();
}
if (write_queue_.size() == 1) { if (write_queue_.size() == 1) {
if (self == nullptr)
self = shared_from_this();
co_await send_data(); co_await send_data();
} }
if (!is_delay) {
if (rpc_call_type_ == rpc_call_type::callback_finished) {
// the function start_impl is waiting for resume.
callback_awaitor_handler_.resume();
}
else {
assert(rpc_call_type_ == rpc_call_type::callback_started);
// the function start_impl is not waiting for resume.
rpc_call_type_ = rpc_call_type::callback_finished;
}
}
} }
async_simple::coro::Lazy<void> send_data() { async_simple::coro::Lazy<void> send_data() {
@ -456,12 +517,6 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
} }
write_queue_.pop_front(); write_queue_.pop_front();
} }
if (!!resp_err_)
AS_UNLIKELY {
ELOGV(ERROR, "%s, %s", make_error_message(resp_err_), "resp_err_");
close();
co_return;
}
#ifdef UNIT_TEST_INJECT #ifdef UNIT_TEST_INJECT
if (g_action == inject_action::close_socket_after_send_length) { if (g_action == inject_action::close_socket_after_send_length) {
ELOGV(INFO, ELOGV(INFO,
@ -477,6 +532,7 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
} }
void close() { void close() {
ELOGV(TRACE, "connection closed");
if (has_closed_) { if (has_closed_) {
return; return;
} }
@ -490,7 +546,7 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
} }
void reset_timer() { void reset_timer() {
if (!enable_check_timeout_ || delay_resp_cnt != 0) { if (!enable_check_timeout_ || rpc_processing_cnt_ != 0) {
return; return;
} }
@ -518,17 +574,13 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
asio::error_code ec; asio::error_code ec;
timer_.cancel(ec); timer_.cancel(ec);
} }
coro_io::callback_awaitor<void>::awaitor_handler callback_awaitor_handler_{
nullptr};
async_simple::Executor *executor_; async_simple::Executor *executor_;
asio::ip::tcp::socket socket_; asio::ip::tcp::socket socket_;
// FIXME: queue's performance can be imporved. // FIXME: queue's performance can be imporved.
std::deque< std::deque<
std::tuple<std::string, std::string, std::function<std::string_view()>>> std::tuple<std::string, std::string, std::function<std::string_view()>>>
write_queue_; write_queue_;
coro_rpc::errc resp_err_; bool is_rpc_return_by_callback_{false};
rpc_call_type rpc_call_type_{non_callback};
// if don't get any message in keep_alive_timeout_duration_, the connection // if don't get any message in keep_alive_timeout_duration_, the connection
// will be closed when enable_check_timeout_ is true. // will be closed when enable_check_timeout_ is true.
@ -539,7 +591,7 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
QuitCallback quit_callback_{nullptr}; QuitCallback quit_callback_{nullptr};
uint64_t conn_id_{0}; uint64_t conn_id_{0};
uint64_t delay_resp_cnt{0}; uint64_t rpc_processing_cnt_{0};
std::any tag_; std::any tag_;
@ -553,4 +605,85 @@ class coro_connection : public std::enable_shared_from_this<coro_connection> {
#endif #endif
}; };
template <typename rpc_protocol>
uint64_t context_info_t<rpc_protocol>::get_connection_id() noexcept {
return conn_->get_connection_id();
}
template <typename rpc_protocol>
uint64_t context_info_t<rpc_protocol>::has_closed() const noexcept {
return conn_->has_closed();
}
template <typename rpc_protocol>
void context_info_t<rpc_protocol>::close() {
return conn_->async_close();
}
template <typename rpc_protocol>
uint64_t context_info_t<rpc_protocol>::get_connection_id() const noexcept {
return conn_->get_connection_id();
}
template <typename rpc_protocol>
void context_info_t<rpc_protocol>::set_response_attachment(
std::string attachment) {
set_response_attachment([attachment = std::move(attachment)] {
return std::string_view{attachment};
});
}
template <typename rpc_protocol>
void context_info_t<rpc_protocol>::set_response_attachment(
std::string_view attachment) {
set_response_attachment([attachment] {
return attachment;
});
}
template <typename rpc_protocol>
void context_info_t<rpc_protocol>::set_response_attachment(
std::function<std::string_view()> attachment) {
resp_attachment_ = std::move(attachment);
}
template <typename rpc_protocol>
std::string_view context_info_t<rpc_protocol>::get_request_attachment() const {
return req_attachment_;
}
template <typename rpc_protocol>
std::string context_info_t<rpc_protocol>::release_request_attachment() {
return std::move(req_attachment_);
}
template <typename rpc_protocol>
std::any &context_info_t<rpc_protocol>::tag() noexcept {
return conn_->tag();
}
template <typename rpc_protocol>
const std::any &context_info_t<rpc_protocol>::tag() const noexcept {
return conn_->tag();
}
template <typename rpc_protocol>
asio::ip::tcp::endpoint context_info_t<rpc_protocol>::get_local_endpoint()
const noexcept {
return conn_->get_local_endpoint();
}
template <typename rpc_protocol>
asio::ip::tcp::endpoint context_info_t<rpc_protocol>::get_remote_endpoint()
const noexcept {
return conn_->get_remote_endpoint();
}
namespace protocol {
template <typename rpc_protocol>
uint64_t get_request_id(const typename rpc_protocol::req_header &) noexcept;
}
template <typename rpc_protocol>
uint64_t context_info_t<rpc_protocol>::get_request_id() const noexcept {
return coro_rpc::protocol::get_request_id<rpc_protocol>(req_head_);
}
} // namespace coro_rpc } // namespace coro_rpc

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
#include <asio/error_code.hpp> #include <asio/error_code.hpp>
#include <asio/io_context.hpp> #include <asio/io_context.hpp>
#include <atomic> #include <atomic>
#include <charconv>
#include <condition_variable> #include <condition_variable>
#include <cstdint> #include <cstdint>
#include <exception> #include <exception>
@ -28,6 +29,7 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <system_error> #include <system_error>
#include <thread>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <ylt/easylog.hpp> #include <ylt/easylog.hpp>
@ -67,24 +69,53 @@ class coro_rpc_server_base {
* TODO: add doc * TODO: add doc
* @param thread_num the number of io_context. * @param thread_num the number of io_context.
* @param port the server port to listen. * @param port the server port to listen.
* @param listen address of server
* @param conn_timeout_duration client connection timeout. 0 for no timeout. * @param conn_timeout_duration client connection timeout. 0 for no timeout.
* default no timeout. * default no timeout.
* @param is_enable_tcp_no_delay is tcp socket allow
*/ */
coro_rpc_server_base(size_t thread_num, unsigned short port, coro_rpc_server_base(size_t thread_num = std::thread::hardware_concurrency(),
unsigned short port = 9001,
std::string address = "0.0.0.0",
std::chrono::steady_clock::duration std::chrono::steady_clock::duration
conn_timeout_duration = std::chrono::seconds(0)) conn_timeout_duration = std::chrono::seconds(0),
bool is_enable_tcp_no_delay = true)
: pool_(thread_num), : pool_(thread_num),
acceptor_(pool_.get_executor()->get_asio_executor()), acceptor_(pool_.get_executor()->get_asio_executor()),
port_(port), port_(port),
conn_timeout_duration_(conn_timeout_duration), conn_timeout_duration_(conn_timeout_duration),
flag_{stat::init} {} flag_{stat::init},
is_enable_tcp_no_delay_(is_enable_tcp_no_delay) {
init_address(std::move(address));
}
coro_rpc_server_base(size_t thread_num = std::thread::hardware_concurrency(),
std::string address = "0.0.0.0:9001",
std::chrono::steady_clock::duration
conn_timeout_duration = std::chrono::seconds(0),
bool is_enable_tcp_no_delay = true)
: pool_(thread_num),
acceptor_(pool_.get_executor()->get_asio_executor()),
conn_timeout_duration_(conn_timeout_duration),
flag_{stat::init},
is_enable_tcp_no_delay_(is_enable_tcp_no_delay) {
init_address(std::move(address));
}
coro_rpc_server_base(const server_config &config = server_config{}) coro_rpc_server_base(const server_config &config = server_config{})
: pool_(config.thread_num), : pool_(config.thread_num),
acceptor_(pool_.get_executor()->get_asio_executor()), acceptor_(pool_.get_executor()->get_asio_executor()),
port_(config.port), port_(config.port),
conn_timeout_duration_(config.conn_timeout_duration), conn_timeout_duration_(config.conn_timeout_duration),
flag_{stat::init} {} flag_{stat::init},
is_enable_tcp_no_delay_(config.is_enable_tcp_no_delay) {
#ifdef YLT_ENABLE_SSL
if (config.ssl_config) {
init_ssl_context_helper(context_, config.ssl_config.value());
}
#endif
init_address(config.address);
}
~coro_rpc_server_base() { ~coro_rpc_server_base() {
ELOGV(INFO, "coro_rpc_server will quit"); ELOGV(INFO, "coro_rpc_server will quit");
@ -92,7 +123,7 @@ class coro_rpc_server_base {
} }
#ifdef YLT_ENABLE_SSL #ifdef YLT_ENABLE_SSL
void init_ssl_context(const ssl_configure &conf) { void init_ssl(const ssl_configure &conf) {
use_ssl_ = init_ssl_context_helper(context_, conf); use_ssl_ = init_ssl_context_helper(context_, conf);
} }
#endif #endif
@ -105,20 +136,19 @@ class coro_rpc_server_base {
* @return error code if start failed, otherwise block until server stop. * @return error code if start failed, otherwise block until server stop.
*/ */
[[nodiscard]] coro_rpc::err_code start() noexcept { [[nodiscard]] coro_rpc::err_code start() noexcept {
auto ret = async_start(); return async_start().get();
if (ret) {
ret.value().wait();
return ret.value().value();
}
else {
return ret.error();
}
} }
[[nodiscard]] coro_rpc::expected<async_simple::Future<coro_rpc::err_code>, private:
coro_rpc::err_code> async_simple::Future<coro_rpc::err_code> make_error_future(
async_start() noexcept { coro_rpc::err_code &&err) {
coro_rpc::err_code ec{}; async_simple::Promise<coro_rpc::err_code> p;
p.setValue(std::move(err));
return p.getFuture();
}
public:
async_simple::Future<coro_rpc::err_code> async_start() noexcept {
{ {
std::unique_lock lock(start_mtx_); std::unique_lock lock(start_mtx_);
if (flag_ != stat::init) { if (flag_ != stat::init) {
@ -128,11 +158,11 @@ class coro_rpc_server_base {
else if (flag_ == stat::stop) { else if (flag_ == stat::stop) {
ELOGV(INFO, "has stoped"); ELOGV(INFO, "has stoped");
} }
return coro_rpc::unexpected<coro_rpc::err_code>{ return make_error_future(
coro_rpc::errc::server_has_ran}; coro_rpc::err_code{coro_rpc::errc::server_has_ran});
} }
ec = listen(); errc_ = listen();
if (!ec) { if (!errc_) {
if constexpr (requires(typename server_config::executor_pool_t & pool) { if constexpr (requires(typename server_config::executor_pool_t & pool) {
pool.run(); pool.run();
}) { }) {
@ -146,12 +176,13 @@ class coro_rpc_server_base {
flag_ = stat::stop; flag_ = stat::stop;
} }
} }
if (!ec) { if (!errc_) {
async_simple::Promise<coro_rpc::err_code> promise; async_simple::Promise<coro_rpc::err_code> promise;
auto future = promise.getFuture(); auto future = promise.getFuture();
accept().start([p = std::move(promise)](auto &&res) mutable { accept().start([this, p = std::move(promise)](auto &&res) mutable {
if (res.hasError()) { if (res.hasError()) {
p.setValue(coro_rpc::err_code{coro_rpc::errc::io_error}); errc_ = coro_rpc::err_code{coro_rpc::errc::io_error};
p.setValue(errc_);
} }
else { else {
p.setValue(res.value()); p.setValue(res.value());
@ -160,7 +191,7 @@ class coro_rpc_server_base {
return std::move(future); return std::move(future);
} }
else { else {
return coro_rpc::unexpected<coro_rpc::err_code>{ec}; return make_error_future(coro_rpc::err_code{errc_});
} }
} }
@ -207,6 +238,8 @@ class coro_rpc_server_base {
* @return * @return
*/ */
uint16_t port() const { return port_; }; uint16_t port() const { return port_; };
std::string_view address() const { return address_; }
coro_rpc::err_code get_errc() const { return errc_; }
/*! /*!
* Register RPC service functions (member function) * Register RPC service functions (member function)
@ -288,30 +321,52 @@ class coro_rpc_server_base {
coro_rpc::err_code listen() { coro_rpc::err_code listen() {
ELOGV(INFO, "begin to listen"); ELOGV(INFO, "begin to listen");
using asio::ip::tcp; using asio::ip::tcp;
auto endpoint = tcp::endpoint(tcp::v4(), port_);
acceptor_.open(endpoint.protocol());
#ifdef __GNUC__
acceptor_.set_option(tcp::acceptor::reuse_address(true));
#endif
asio::error_code ec; asio::error_code ec;
asio::ip::tcp::resolver::query query(address_, std::to_string(port_));
asio::ip::tcp::resolver resolver(acceptor_.get_executor());
asio::ip::tcp::resolver::iterator it = resolver.resolve(query, ec);
asio::ip::tcp::resolver::iterator it_end;
if (ec || it == it_end) {
ELOGV(ERROR, "resolve address %s error : %s", address_.data(),
ec.message().data());
return coro_rpc::errc::bad_address;
}
auto endpoint = it->endpoint();
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
ELOGV(ERROR, "open failed, error : %s", ec.message().data());
return coro_rpc::errc::open_error;
}
#ifdef __GNUC__
acceptor_.set_option(tcp::acceptor::reuse_address(true), ec);
#endif
acceptor_.bind(endpoint, ec); acceptor_.bind(endpoint, ec);
if (ec) { if (ec) {
ELOGV(ERROR, "bind port %d error : %s", port_.load(), ELOGV(ERROR, "bind port %d error : %s", port_.load(),
ec.message().data()); ec.message().data());
acceptor_.cancel(ec); acceptor_.cancel(ec);
acceptor_.close(ec); acceptor_.close(ec);
return coro_rpc::errc::address_in_use; return coro_rpc::errc::address_in_used;
} }
#ifdef _MSC_VER #ifdef _MSC_VER
acceptor_.set_option(tcp::acceptor::reuse_address(true)); acceptor_.set_option(tcp::acceptor::reuse_address(true));
#endif #endif
acceptor_.listen(); acceptor_.listen(asio::socket_base::max_listen_connections, ec);
if (ec) {
ELOGV(ERROR, "port %d listen error : %s", port_.load(),
ec.message().data());
acceptor_.cancel(ec);
acceptor_.close(ec);
return coro_rpc::errc::listen_error;
}
auto end_point = acceptor_.local_endpoint(ec); auto end_point = acceptor_.local_endpoint(ec);
if (ec) { if (ec) {
ELOGV(ERROR, "get local endpoint port %d error : %s", port_.load(), ELOGV(ERROR, "get local endpoint port %d error : %s", port_.load(),
ec.message().data()); ec.message().data());
return coro_rpc::errc::address_in_use; return coro_rpc::errc::address_in_used;
} }
port_ = end_point.port(); port_ = end_point.port();
@ -346,6 +401,9 @@ class coro_rpc_server_base {
int64_t conn_id = ++conn_id_; int64_t conn_id = ++conn_id_;
ELOGV(INFO, "new client conn_id %d coming", conn_id); ELOGV(INFO, "new client conn_id %d coming", conn_id);
if (is_enable_tcp_no_delay_) {
socket.set_option(asio::ip::tcp::no_delay(true), error);
}
auto conn = std::make_shared<coro_connection>(executor, std::move(socket), auto conn = std::make_shared<coro_connection>(executor, std::move(socket),
conn_timeout_duration_); conn_timeout_duration_);
conn->set_quit_callback( conn->set_quit_callback(
@ -359,7 +417,7 @@ class coro_rpc_server_base {
std::unique_lock lock(conns_mtx_); std::unique_lock lock(conns_mtx_);
conns_.emplace(conn_id, conn); conns_.emplace(conn_id, conn);
} }
start_one(conn).via(&conn->get_executor()).detach(); start_one(conn).via(conn->get_executor()).detach();
} }
} }
@ -382,6 +440,25 @@ class coro_rpc_server_base {
acceptor_close_waiter_.get_future().wait(); acceptor_close_waiter_.get_future().wait();
} }
void init_address(std::string address) {
if (size_t pos = address.find(':'); pos != std::string::npos) {
auto port_sv = std::string_view(address).substr(pos + 1);
uint16_t port;
auto [ptr, ec] = std::from_chars(
port_sv.data(), port_sv.data() + port_sv.size(), port, 10);
if (ec != std::errc{}) {
address_ = std::move(address);
return;
}
port_ = port;
address = address.substr(0, pos);
}
address_ = std::move(address);
}
typename server_config::executor_pool_t pool_; typename server_config::executor_pool_t pool_;
asio::ip::tcp::acceptor acceptor_; asio::ip::tcp::acceptor acceptor_;
std::promise<void> acceptor_close_waiter_; std::promise<void> acceptor_close_waiter_;
@ -397,6 +474,9 @@ class coro_rpc_server_base {
typename server_config::rpc_protocol::router router_; typename server_config::rpc_protocol::router router_;
std::atomic<uint16_t> port_; std::atomic<uint16_t> port_;
std::string address_;
bool is_enable_tcp_no_delay_;
coro_rpc::err_code errc_ = {};
std::chrono::steady_clock::duration conn_timeout_duration_; std::chrono::steady_clock::duration conn_timeout_duration_;
#ifdef YLT_ENABLE_SSL #ifdef YLT_ENABLE_SSL

View File

@ -25,19 +25,22 @@
namespace coro_rpc { namespace coro_rpc {
namespace config { struct config_base {
struct coro_rpc_config_base { bool is_enable_tcp_no_delay = true;
uint16_t port = 8801; uint16_t port = 9001;
unsigned thread_num = std::thread::hardware_concurrency(); unsigned thread_num = std::thread::hardware_concurrency();
std::chrono::steady_clock::duration conn_timeout_duration = std::chrono::steady_clock::duration conn_timeout_duration =
std::chrono::seconds{0}; std::chrono::seconds{0};
std::string address = "0.0.0.0";
#ifdef YLT_ENABLE_SSL
std::optional<ssl_configure> ssl_config = std::nullopt;
#endif
}; };
struct coro_rpc_default_config : public coro_rpc_config_base { struct config_t : public config_base {
using rpc_protocol = coro_rpc::protocol::coro_rpc_protocol; using rpc_protocol = coro_rpc::protocol::coro_rpc_protocol;
using executor_pool_t = coro_io::io_context_pool; using executor_pool_t = coro_io::io_context_pool;
}; };
} // namespace config
using coro_rpc_server = coro_rpc_server_base<config::coro_rpc_default_config>; using coro_rpc_server = coro_rpc_server_base<config_t>;
} // namespace coro_rpc } // namespace coro_rpc

View File

@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <ylt/struct_pack/util.h>
#include <cstdint> #include <cstdint>
#include <ylt/struct_pack.hpp>
#pragma once #pragma once
namespace coro_rpc { namespace coro_rpc {
enum class errc : uint16_t { enum class errc : uint16_t {
@ -23,44 +22,59 @@ enum class errc : uint16_t {
io_error, io_error,
not_connected, not_connected,
timed_out, timed_out,
invalid_argument, invalid_rpc_arguments,
address_in_use, address_in_used,
bad_address,
open_error,
listen_error,
operation_canceled, operation_canceled,
interrupted, rpc_throw_exception,
function_not_registered, function_not_registered,
protocol_error, protocol_error,
unknown_protocol_version, unknown_protocol_version,
message_too_large, message_too_large,
server_has_ran, server_has_ran,
invalid_rpc_result,
serial_number_conflict,
}; };
inline constexpr std::string_view make_error_message(errc ec) noexcept { inline constexpr std::string_view make_error_message(errc ec) noexcept {
switch (ec) { switch (ec) {
case errc::ok: case errc::ok:
return "ok"; return "ok";
case errc::io_error: case errc::io_error:
return "io_error"; return "io error";
case errc::not_connected: case errc::not_connected:
return "not_connected"; return "not connected";
case errc::timed_out: case errc::timed_out:
return "timed_out"; return "time out";
case errc::invalid_argument: case errc::invalid_rpc_arguments:
return "invalid_argument"; return "invalid rpc arg";
case errc::address_in_use: case errc::address_in_used:
return "address_in_use"; return "address in used";
case errc::bad_address:
return "bad_address";
case errc::open_error:
return "open_error";
case errc::listen_error:
return "listen_error";
case errc::operation_canceled: case errc::operation_canceled:
return "operation_canceled"; return "operation canceled";
case errc::interrupted: case errc::rpc_throw_exception:
return "interrupted"; return "rpc throw exception";
case errc::function_not_registered: case errc::function_not_registered:
return "function_not_registered"; return "function not registered";
case errc::protocol_error: case errc::protocol_error:
return "protocol_error"; return "protocol error";
case errc::message_too_large: case errc::message_too_large:
return "message_too_large"; return "message too large";
case errc::server_has_ran: case errc::server_has_ran:
return "server_has_ran"; return "server has ran";
case errc::invalid_rpc_result:
return "invalid rpc result";
case errc::serial_number_conflict:
return "serial number conflict";
default: default:
return "unknown_user-defined_error"; return "unknown user-defined error";
} }
} }
struct err_code { struct err_code {
@ -89,4 +103,18 @@ struct err_code {
inline bool operator!(err_code ec) noexcept { return ec == errc::ok; } inline bool operator!(err_code ec) noexcept { return ec == errc::ok; }
inline bool operator!(errc ec) noexcept { return ec == errc::ok; } inline bool operator!(errc ec) noexcept { return ec == errc::ok; }
struct rpc_error {
coro_rpc::err_code code; //!< error code
std::string msg; //!< error message
rpc_error() {}
rpc_error(coro_rpc::err_code code, std::string_view msg)
: code(code), msg(std::string{msg}) {}
rpc_error(coro_rpc::err_code code)
: code(code), msg(std::string{make_error_message(code)}) {}
uint16_t& val() { return *(uint16_t*)&(code.ec); }
const uint16_t& val() const { return *(uint16_t*)&(code.ec); }
constexpr operator bool() const noexcept { return code; }
};
STRUCT_PACK_REFL(rpc_error, val(), msg);
}; // namespace coro_rpc }; // namespace coro_rpc

View File

@ -44,7 +44,11 @@ using unexpected = tl::unexpected<T>;
using unexpect_t = tl::unexpect_t; using unexpect_t = tl::unexpect_t;
#endif #endif
template <typename T, typename rpc_protocol> namespace protocol {
using rpc_result = expected<T, typename rpc_protocol::rpc_error>; struct coro_rpc_protocol;
}
template <typename T>
using rpc_result = expected<T, coro_rpc::rpc_error>;
} // namespace coro_rpc } // namespace coro_rpc

View File

@ -21,6 +21,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include <system_error> #include <system_error>
#include <type_traits>
#include <variant> #include <variant>
#include <ylt/easylog.hpp> #include <ylt/easylog.hpp>
#include <ylt/struct_pack.hpp> #include <ylt/struct_pack.hpp>
@ -61,7 +62,7 @@ struct coro_rpc_protocol {
uint32_t seq_num; //!< sequence number uint32_t seq_num; //!< sequence number
uint32_t function_id; //!< rpc function ID uint32_t function_id; //!< rpc function ID
uint32_t length; //!< length of RPC body uint32_t length; //!< length of RPC body
uint32_t attach_length; //!< reserved field uint32_t attach_length; //!< attachment length
}; };
struct resp_header { struct resp_header {
@ -71,7 +72,7 @@ struct coro_rpc_protocol {
uint8_t msg_type; //!< message type uint8_t msg_type; //!< message type
uint32_t seq_num; //!< sequence number uint32_t seq_num; //!< sequence number
uint32_t length; //!< length of RPC body uint32_t length; //!< length of RPC body
uint32_t attach_length; //!< reserved field uint32_t attach_length; //!< attachment length
}; };
using supported_serialize_protocols = std::variant<struct_pack_protocol>; using supported_serialize_protocols = std::variant<struct_pack_protocol>;
@ -133,8 +134,7 @@ struct coro_rpc_protocol {
const req_header& req_header, const req_header& req_header,
std::size_t attachment_len, std::size_t attachment_len,
coro_rpc::errc rpc_err_code = {}, coro_rpc::errc rpc_err_code = {},
std::string_view err_msg = {}, std::string_view err_msg = {}) {
bool is_user_defined_error = false) {
std::string err_msg_buf; std::string err_msg_buf;
std::string header_buf; std::string header_buf;
header_buf.resize(RESP_HEAD_LEN); header_buf.resize(RESP_HEAD_LEN);
@ -150,7 +150,6 @@ struct coro_rpc_protocol {
err_msg_buf = err_msg_buf =
"attachment larger than 4G:" + std::to_string(attachment_len) + "B"; "attachment larger than 4G:" + std::to_string(attachment_len) + "B";
err_msg = err_msg_buf; err_msg = err_msg_buf;
is_user_defined_error = false;
} }
else if (rpc_result.size() > UINT32_MAX) else if (rpc_result.size() > UINT32_MAX)
AS_UNLIKELY { AS_UNLIKELY {
@ -160,12 +159,11 @@ struct coro_rpc_protocol {
err_msg_buf = err_msg_buf =
"body larger than 4G:" + std::to_string(attachment_len) + "B"; "body larger than 4G:" + std::to_string(attachment_len) + "B";
err_msg = err_msg_buf; err_msg = err_msg_buf;
is_user_defined_error = false;
} }
if (rpc_err_code != coro_rpc::errc{}) if (rpc_err_code != coro_rpc::errc{})
AS_UNLIKELY { AS_UNLIKELY {
rpc_result.clear(); rpc_result.clear();
if (is_user_defined_error) { if (static_cast<uint16_t>(rpc_err_code) > UINT8_MAX) {
struct_pack::serialize_to( struct_pack::serialize_to(
rpc_result, rpc_result,
std::pair{static_cast<uint16_t>(rpc_err_code), err_msg}); std::pair{static_cast<uint16_t>(rpc_err_code), err_msg});
@ -186,12 +184,6 @@ struct coro_rpc_protocol {
* The `rpc_error` struct holds the error code `code` and error message * The `rpc_error` struct holds the error code `code` and error message
* `msg`. * `msg`.
*/ */
struct rpc_error {
coro_rpc::err_code code; //!< error code
std::string msg; //!< error message
uint16_t& val() { return *(uint16_t*)&(code.ec); }
const uint16_t& val() const { return *(uint16_t*)&(code.ec); }
};
// internal variable // internal variable
constexpr static inline int8_t magic_number = 21; constexpr static inline int8_t magic_number = 21;
@ -203,10 +195,40 @@ struct coro_rpc_protocol {
static_assert(RESP_HEAD_LEN == 16); static_assert(RESP_HEAD_LEN == 16);
}; };
STRUCT_PACK_REFL(coro_rpc_protocol::rpc_error, val(), msg); template <typename rpc_protocol = coro_rpc::protocol::coro_rpc_protocol>
uint64_t get_request_id(
const typename rpc_protocol::req_header& header) noexcept {
if constexpr (std::is_same_v<rpc_protocol,
coro_rpc::protocol::coro_rpc_protocol>) {
return header.seq_num;
}
else {
return 0;
}
}
} // namespace protocol } // namespace protocol
template <typename return_msg_type> template <typename return_msg_type>
using context = coro_rpc::context_base<return_msg_type, using context = coro_rpc::context_base<return_msg_type,
coro_rpc::protocol::coro_rpc_protocol>; coro_rpc::protocol::coro_rpc_protocol>;
using rpc_error = protocol::coro_rpc_protocol::rpc_error;
template <typename rpc_protocol = coro_rpc::protocol::coro_rpc_protocol>
async_simple::coro::Lazy<context_info_t<rpc_protocol>*> get_context_in_coro() {
auto* ctx = co_await async_simple::coro::LazyLocals{};
assert(ctx != nullptr);
co_return (context_info_t<rpc_protocol>*) ctx;
}
namespace detail {
template <typename rpc_protocol>
context_info_t<rpc_protocol>*& set_context() {
thread_local static context_info_t<rpc_protocol>* ctx;
return ctx;
}
} // namespace detail
template <typename rpc_protocol = coro_rpc::protocol::coro_rpc_protocol>
context_info_t<rpc_protocol>* get_context() {
return detail::set_context<rpc_protocol>();
}
} // namespace coro_rpc } // namespace coro_rpc

View File

@ -30,6 +30,8 @@
#include <ylt/struct_pack/md5_constexpr.hpp> #include <ylt/struct_pack/md5_constexpr.hpp>
#include "rpc_execute.hpp" #include "rpc_execute.hpp"
#include "ylt/coro_rpc/impl/expected.hpp"
#include "ylt/coro_rpc/impl/protocol/coro_rpc_protocol.hpp"
namespace coro_rpc { namespace coro_rpc {
@ -46,21 +48,19 @@ template <typename rpc_protocol,
template <typename...> typename map_t = std::unordered_map> template <typename...> typename map_t = std::unordered_map>
class router { class router {
using router_handler_t = std::function<std::optional<std::string>( public:
using router_handler_t =
std::function<std::pair<coro_rpc::err_code, std::string>(
std::string_view, rpc_context<rpc_protocol> &context_info, std::string_view, rpc_context<rpc_protocol> &context_info,
typename rpc_protocol::supported_serialize_protocols protocols)>; typename rpc_protocol::supported_serialize_protocols protocols)>;
using coro_router_handler_t = using coro_router_handler_t = std::function<
std::function<async_simple::coro::Lazy<std::optional<std::string>>( async_simple::coro::Lazy<std::pair<coro_rpc::err_code, std::string>>(
std::string_view, rpc_context<rpc_protocol> &context_info, std::string_view,
typename rpc_protocol::supported_serialize_protocols protocols)>; typename rpc_protocol::supported_serialize_protocols protocols)>;
using route_key = typename rpc_protocol::route_key_t; using route_key = typename rpc_protocol::route_key_t;
std::unordered_map<route_key, router_handler_t> handlers_;
std::unordered_map<route_key, coro_router_handler_t> coro_handlers_;
std::unordered_map<route_key, std::string> id2name_;
private:
const std::string &get_name(const route_key &key) { const std::string &get_name(const route_key &key) {
static std::string empty_string; static std::string empty_string;
if (auto it = id2name_.find(key); it != id2name_.end()) { if (auto it = id2name_.find(key); it != id2name_.end()) {
@ -70,30 +70,33 @@ class router {
return empty_string; return empty_string;
} }
private:
std::unordered_map<route_key, router_handler_t> handlers_;
std::unordered_map<route_key, coro_router_handler_t> coro_handlers_;
std::unordered_map<route_key, std::string> id2name_;
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100611 // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100611
// We use this struct instead of lambda for workaround // We use this struct instead of lambda for workaround
template <auto Func, typename Self> template <auto Func, typename Self>
struct execute_visitor { struct execute_visitor {
std::string_view data; std::string_view data;
rpc_context<rpc_protocol> &context_info;
Self *self; Self *self;
template <typename serialize_protocol> template <typename serialize_protocol>
async_simple::coro::Lazy<std::optional<std::string>> operator()( async_simple::coro::Lazy<std::pair<coro_rpc::err_code, std::string>>
const serialize_protocol &) { operator()(const serialize_protocol &) {
return internal::execute_coro<rpc_protocol, serialize_protocol, Func>( return internal::execute_coro<rpc_protocol, serialize_protocol, Func>(
data, context_info, self); data, self);
} }
}; };
template <auto Func> template <auto Func>
struct execute_visitor<Func, void> { struct execute_visitor<Func, void> {
std::string_view data; std::string_view data;
rpc_context<rpc_protocol> &context_info;
template <typename serialize_protocol> template <typename serialize_protocol>
async_simple::coro::Lazy<std::optional<std::string>> operator()( async_simple::coro::Lazy<std::pair<coro_rpc::err_code, std::string>>
const serialize_protocol &) { operator()(const serialize_protocol &) {
return internal::execute_coro<rpc_protocol, serialize_protocol, Func>( return internal::execute_coro<rpc_protocol, serialize_protocol, Func>(
data, context_info); data);
} }
}; };
@ -134,9 +137,9 @@ class router {
auto it = coro_handlers_.emplace( auto it = coro_handlers_.emplace(
key, key,
[self]( [self](
std::string_view data, rpc_context<rpc_protocol> &context_info, std::string_view data,
typename rpc_protocol::supported_serialize_protocols protocols) { typename rpc_protocol::supported_serialize_protocols protocols) {
execute_visitor<func, Self> visitor{data, context_info, self}; execute_visitor<func, Self> visitor{data, self};
return std::visit(visitor, protocols); return std::visit(visitor, protocols);
}); });
if (!it.second) { if (!it.second) {
@ -188,9 +191,9 @@ class router {
async_simple::coro::Lazy>) { async_simple::coro::Lazy>) {
auto it = coro_handlers_.emplace( auto it = coro_handlers_.emplace(
key, key,
[](std::string_view data, rpc_context<rpc_protocol> &context_info, [](std::string_view data,
typename rpc_protocol::supported_serialize_protocols protocols) { typename rpc_protocol::supported_serialize_protocols protocols) {
execute_visitor<func, void> visitor{data, context_info}; execute_visitor<func, void> visitor{data};
return std::visit(visitor, protocols); return std::visit(visitor, protocols);
}); });
if (!it.second) { if (!it.second) {
@ -232,9 +235,8 @@ class router {
return nullptr; return nullptr;
} }
async_simple::coro::Lazy<std::pair<coro_rpc::errc, std::string>> route_coro( async_simple::coro::Lazy<std::pair<coro_rpc::err_code, std::string>>
auto handler, std::string_view data, route_coro(auto handler, std::string_view data,
rpc_context<rpc_protocol> &context_info,
typename rpc_protocol::supported_serialize_protocols protocols, typename rpc_protocol::supported_serialize_protocols protocols,
const typename rpc_protocol::route_key_t &route_key) { const typename rpc_protocol::route_key_t &route_key) {
using namespace std::string_literals; using namespace std::string_literals;
@ -246,41 +248,23 @@ class router {
#endif #endif
// clang-format off // clang-format off
auto res = co_await (*handler)(data, context_info, protocols); co_return co_await (*handler)(data, protocols);
// clang-format on } catch (coro_rpc::rpc_error& err) {
if (res.has_value()) co_return std::make_pair(err.code, std::move(err.msg));
AS_LIKELY {
co_return std::make_pair(coro_rpc::errc{},
std::move(res.value()));
}
else { // deserialize failed
ELOGV(ERROR, "payload deserialize failed in rpc function: %s",
get_name(route_key).data());
co_return std::make_pair(coro_rpc::errc::invalid_argument,
"invalid rpc function arguments"s);
}
} catch (const std::exception &e) { } catch (const std::exception &e) {
ELOGV(ERROR, "exception: %s in rpc function: %s", e.what(), co_return std::make_pair(coro_rpc::errc::rpc_throw_exception, e.what());
get_name(route_key).data());
co_return std::make_pair(coro_rpc::errc::interrupted, e.what());
} catch (...) { } catch (...) {
ELOGV(ERROR, "unknown exception in rpc function: %s", co_return std::make_pair(coro_rpc::errc::rpc_throw_exception,
get_name(route_key).data()); "unknown rpc function exception"s);
co_return std::make_pair(coro_rpc::errc::interrupted,
"unknown exception"s);
} }
} }
else { else {
std::ostringstream ss;
ss << route_key;
ELOGV(ERROR, "the rpc function not registered, function ID: %s",
ss.str().data());
co_return std::make_pair(coro_rpc::errc::function_not_registered, co_return std::make_pair(coro_rpc::errc::function_not_registered,
"the rpc function not registered"s); "the rpc function not registered"s);
} }
} }
std::pair<coro_rpc::errc, std::string> route( std::pair<coro_rpc::err_code, std::string> route(
auto handler, std::string_view data, auto handler, std::string_view data,
rpc_context<rpc_protocol> &context_info, rpc_context<rpc_protocol> &context_info,
typename rpc_protocol::supported_serialize_protocols protocols, typename rpc_protocol::supported_serialize_protocols protocols,
@ -292,35 +276,20 @@ class router {
#ifndef NDEBUG #ifndef NDEBUG
ELOGV(INFO, "route function name: %s", get_name(route_key).data()); ELOGV(INFO, "route function name: %s", get_name(route_key).data());
#endif #endif
auto res = (*handler)(data, context_info, protocols); return (*handler)(data, context_info, protocols);
if (res.has_value()) } catch (coro_rpc::rpc_error& err) {
AS_LIKELY { return std::make_pair(err.code, std::move(err.msg));
return std::make_pair(coro_rpc::errc{}, std::move(res.value()));
}
else { // deserialize failed
ELOGV(ERROR, "payload deserialize failed in rpc function: %s",
get_name(route_key).data());
return std::make_pair(coro_rpc::errc::invalid_argument,
"invalid rpc function arguments"s);
}
} catch (const std::exception &e) { } catch (const std::exception &e) {
ELOGV(ERROR, "exception: %s in rpc function: %s", e.what(), return std::make_pair(err_code{coro_rpc::errc::rpc_throw_exception}, e.what());
get_name(route_key).data());
return std::make_pair(coro_rpc::errc::interrupted, e.what());
} catch (...) { } catch (...) {
ELOGV(ERROR, "unknown exception in rpc function: %s", return std::make_pair(err_code{errc::rpc_throw_exception},
get_name(route_key).data());
return std::make_pair(coro_rpc::errc::interrupted,
"unknown rpc function exception"s); "unknown rpc function exception"s);
} }
} }
else { else {
std::ostringstream ss; using namespace std;
ss << route_key;
ELOGV(ERROR, "the rpc function not registered, function ID: %s",
ss.str().data());
return std::make_pair(coro_rpc::errc::function_not_registered, return std::make_pair(coro_rpc::errc::function_not_registered,
"the rpc function not registered"s); "the rpc function not registered");
} }
} }

View File

@ -24,6 +24,8 @@
#include "context.hpp" #include "context.hpp"
#include "coro_connection.hpp" #include "coro_connection.hpp"
#include "ylt/coro_rpc/impl/errno.h"
#include "ylt/easylog.hpp"
#include "ylt/util/type_traits.h" #include "ylt/util/type_traits.h"
namespace coro_rpc::internal { namespace coro_rpc::internal {
@ -43,16 +45,16 @@ auto get_return_type() {
return First{}; return First{};
} }
} }
template <typename rpc_protocol> template <typename rpc_protocol>
using rpc_context = std::shared_ptr<context_info_t<rpc_protocol>>; using rpc_context = std::shared_ptr<context_info_t<rpc_protocol>>;
using rpc_conn = std::shared_ptr<coro_connection>; using rpc_conn = std::shared_ptr<coro_connection>;
template <typename rpc_protocol, typename serialize_proto, auto func, template <typename rpc_protocol, typename serialize_proto, auto func,
typename Self = void> typename Self = void>
inline std::optional<std::string> execute( inline std::pair<coro_rpc::err_code, std::string> execute(
std::string_view data, rpc_context<rpc_protocol> &context_info, std::string_view data, rpc_context<rpc_protocol> &context_info,
Self *self = nullptr) { Self *self = nullptr) {
using namespace std::string_literals;
using T = decltype(func); using T = decltype(func);
using param_type = util::function_parameters_t<T>; using param_type = util::function_parameters_t<T>;
using return_type = util::function_return_type_t<T>; using return_type = util::function_return_type_t<T>;
@ -78,7 +80,10 @@ inline std::optional<std::string> execute(
} }
if (!is_ok) if (!is_ok)
AS_UNLIKELY { return std::nullopt; } AS_UNLIKELY {
return std::pair{err_code{errc::invalid_rpc_arguments},
"invalid rpc arg"s};
}
if constexpr (std::is_void_v<return_type>) { if constexpr (std::is_void_v<return_type>) {
if constexpr (std::is_void_v<Self>) { if constexpr (std::is_void_v<Self>) {
@ -96,35 +101,37 @@ inline std::optional<std::string> execute(
} }
} }
else { else {
auto &o = *self;
if constexpr (has_coro_conn_v) { if constexpr (has_coro_conn_v) {
// call void o.func(coro_conn, args...) // call void self->func(coro_conn, args...)
std::apply(func, std::apply(
std::tuple_cat( func, std::tuple_cat(
std::forward_as_tuple( std::forward_as_tuple(
o, context_base<conn_return_type, rpc_protocol>( *self, context_base<conn_return_type, rpc_protocol>(
context_info)), context_info)),
std::move(args))); std::move(args)));
} }
else { else {
// call void o.func(args...) // call void self->func(args...)
std::apply(func, std::apply(func, std::tuple_cat(std::forward_as_tuple(*self),
std::tuple_cat(std::forward_as_tuple(o), std::move(args))); std::move(args)));
} }
} }
return std::pair{err_code{}, serialize_proto::serialize()};
} }
else { else {
if constexpr (std::is_void_v<Self>) { if constexpr (std::is_void_v<Self>) {
// call return_type func(args...) // call return_type func(args...)
return serialize_proto::serialize(std::apply(func, std::move(args))); return std::pair{err_code{}, serialize_proto::serialize(
std::apply(func, std::move(args)))};
} }
else { else {
auto &o = *self; // call return_type self->func(args...)
// call return_type o.func(args...)
return serialize_proto::serialize(std::apply( return std::pair{err_code{},
func, std::tuple_cat(std::forward_as_tuple(o), std::move(args)))); serialize_proto::serialize(std::apply(
func, std::tuple_cat(std::forward_as_tuple(*self),
std::move(args))))};
} }
} }
} }
@ -136,24 +143,25 @@ inline std::optional<std::string> execute(
else { else {
(self->*func)(); (self->*func)();
} }
return std::pair{err_code{}, serialize_proto::serialize()};
} }
else { else {
if constexpr (std::is_void_v<Self>) { if constexpr (std::is_void_v<Self>) {
return serialize_proto::serialize(func()); return std::pair{err_code{}, serialize_proto::serialize(func())};
} }
else { else {
return serialize_proto::serialize((self->*func)()); return std::pair{err_code{},
serialize_proto::serialize((self->*func)())};
} }
} }
} }
return serialize_proto::serialize();
} }
template <typename rpc_protocol, typename serialize_proto, auto func, template <typename rpc_protocol, typename serialize_proto, auto func,
typename Self = void> typename Self = void>
inline async_simple::coro::Lazy<std::optional<std::string>> execute_coro( inline async_simple::coro::Lazy<std::pair<coro_rpc::err_code, std::string>>
std::string_view data, rpc_context<rpc_protocol> &context_info, execute_coro(std::string_view data, Self *self = nullptr) {
Self *self = nullptr) { using namespace std::string_literals;
using T = decltype(func); using T = decltype(func);
using param_type = util::function_parameters_t<T>; using param_type = util::function_parameters_t<T>;
using return_type = typename get_type_t< using return_type = typename get_type_t<
@ -162,67 +170,46 @@ inline async_simple::coro::Lazy<std::optional<std::string>> execute_coro(
if constexpr (!std::is_void_v<param_type>) { if constexpr (!std::is_void_v<param_type>) {
using First = std::tuple_element_t<0, param_type>; using First = std::tuple_element_t<0, param_type>;
constexpr bool is_conn = requires { typename First::return_type; }; constexpr bool is_conn = requires { typename First::return_type; };
if constexpr (is_conn) { static_assert(
static_assert(std::is_void_v<return_type>, !is_conn,
"The return_type must be void"); "context<T> is not allowed as parameter in coroutine function");
}
using conn_return_type = decltype(get_return_type<is_conn, First>());
constexpr bool has_coro_conn_v =
std::is_same_v<context_base<conn_return_type, rpc_protocol>, First>;
auto args = util::get_args<has_coro_conn_v, param_type>();
bool is_ok = true; bool is_ok = true;
constexpr size_t size = std::tuple_size_v<decltype(args)>; constexpr size_t size = std::tuple_size_v<param_type>;
param_type args;
if constexpr (size > 0) { if constexpr (size > 0) {
is_ok = serialize_proto::deserialize_to(args, data); is_ok = serialize_proto::deserialize_to(args, data);
} }
if (!is_ok)
AS_UNLIKELY {
co_return std::make_pair(coro_rpc::errc::invalid_rpc_arguments,
"invalid rpc function arguments"s);
}
if constexpr (std::is_void_v<return_type>) { if constexpr (std::is_void_v<return_type>) {
if constexpr (std::is_void_v<Self>) { if constexpr (std::is_void_v<Self>) {
if constexpr (has_coro_conn_v) {
// call void func(coro_conn, args...)
co_await std::apply(
func,
std::tuple_cat(std::forward_as_tuple(
context_base<conn_return_type, rpc_protocol>(
context_info)),
std::move(args)));
}
else {
// call void func(args...) // call void func(args...)
co_await std::apply(func, std::move(args)); co_await std::apply(func, std::move(args));
} }
}
else { else {
auto &o = *self; // call void self->func(args...)
if constexpr (has_coro_conn_v) { co_await std::apply(func, std::tuple_cat(std::forward_as_tuple(*self),
// call void o.func(coro_conn, args...)
co_await std::apply(
func, std::tuple_cat(
std::forward_as_tuple(
o, context_base<conn_return_type, rpc_protocol>(
context_info)),
std::move(args))); std::move(args)));
} }
else { co_return std::pair{err_code{}, serialize_proto::serialize()};
// call void o.func(args...)
co_await std::apply(
func, std::tuple_cat(std::forward_as_tuple(o), std::move(args)));
}
}
} }
else { else {
if constexpr (std::is_void_v<Self>) { if constexpr (std::is_void_v<Self>) {
// call return_type func(args...) // call return_type func(args...)
co_return serialize_proto::serialize( co_return std::pair{err_code{},
co_await std::apply(func, std::move(args))); serialize_proto::serialize(
co_await std::apply(func, std::move(args)))};
} }
else { else {
auto &o = *self; // call return_type self->func(args...)
// call return_type o.func(args...) co_return std::pair{
co_return serialize_proto::serialize(co_await std::apply( err_code{}, serialize_proto::serialize(co_await std::apply(
func, std::tuple_cat(std::forward_as_tuple(o), std::move(args)))); func, std::tuple_cat(std::forward_as_tuple(*self),
std::move(args))))};
} }
} }
} }
@ -236,18 +223,19 @@ inline async_simple::coro::Lazy<std::optional<std::string>> execute_coro(
co_await (self->*func)(); co_await (self->*func)();
// clang-format on // clang-format on
} }
co_return std::pair{err_code{}, serialize_proto::serialize()};
} }
else { else {
if constexpr (std::is_void_v<Self>) { if constexpr (std::is_void_v<Self>) {
co_return serialize_proto::serialize(co_await func()); co_return std::pair{err_code{},
serialize_proto::serialize(co_await func())};
} }
else { else {
// clang-format off // clang-format off
co_return serialize_proto::serialize(co_await (self->*func)()); co_return std::pair{err_code{},serialize_proto::serialize(co_await (self->*func)())};
// clang-format on // clang-format on
} }
} }
} }
co_return serialize_proto::serialize();
} }
} // namespace coro_rpc::internal } // namespace coro_rpc::internal

View File

@ -231,7 +231,7 @@ inline void add_appender(std::function<void(std::string_view)> fn) {
#if __has_include(<fmt/format.h>) || __has_include(<format>) #if __has_include(<fmt/format.h>) || __has_include(<format>)
#define ELOGFMT_IMPL0(severity, Id, prefix, format_str, ...) \ #define ELOGFMT_IMPL0(severity, Id, prefix, ...) \
if (!easylog::logger<Id>::instance().check_severity(severity)) { \ if (!easylog::logger<Id>::instance().check_severity(severity)) { \
; \ ; \
} \ } \
@ -239,7 +239,7 @@ inline void add_appender(std::function<void(std::string_view)> fn) {
easylog::logger<Id>::instance() += \ easylog::logger<Id>::instance() += \
easylog::record_t(std::chrono::system_clock::now(), severity, \ easylog::record_t(std::chrono::system_clock::now(), severity, \
GET_STRING(__FILE__, __LINE__)) \ GET_STRING(__FILE__, __LINE__)) \
.format(prefix::format(format_str, __VA_ARGS__)); \ .format(prefix::format(__VA_ARGS__)); \
if constexpr (severity == easylog::Severity::CRITICAL) { \ if constexpr (severity == easylog::Severity::CRITICAL) { \
easylog::flush<Id>(); \ easylog::flush<Id>(); \
std::exit(EXIT_FAILURE); \ std::exit(EXIT_FAILURE); \
@ -288,7 +288,7 @@ inline void add_appender(std::function<void(std::string_view)> fn) {
#endif #endif
#ifndef MELOG_TRACE #ifndef MELOG_TRACE
#define MELOG_TRACE(id) ELOG(INFO, id) #define MELOG_TRACE(id) ELOG(TRACE, id)
#endif #endif
#ifndef MELOG_DEBUG #ifndef MELOG_DEBUG
#define MELOG_DEBUG(id) ELOG(DEBUG, id) #define MELOG_DEBUG(id) ELOG(DEBUG, id)

View File

@ -145,6 +145,7 @@ class appender {
auto [ptr, ec] = std::to_chars(buf + 1, buf + 21, tid); auto [ptr, ec] = std::to_chars(buf + 1, buf + 21, tid);
buf[22] = ']'; buf[22] = ']';
buf[23] = ' '; buf[23] = ' ';
last_tid = tid;
last_len = ptr - buf; last_len = ptr - buf;
buf[last_len++] = ']'; buf[last_len++] = ']';
buf[last_len++] = ' '; buf[last_len++] = ' ';

View File

@ -63,8 +63,8 @@ template <typename Type, typename = void>
struct has_data : std::false_type {}; struct has_data : std::false_type {};
template <typename T> template <typename T>
struct has_data<T, std::void_t<decltype(std::declval<T>().data())>> struct has_data<T, std::void_t<decltype(std::declval<std::string>().append(
: std::true_type {}; std::declval<T>().data()))>> : std::true_type {};
template <typename T> template <typename T>
constexpr inline bool has_data_v = has_data<std::remove_cvref_t<T>>::value; constexpr inline bool has_data_v = has_data<std::remove_cvref_t<T>>::value;
@ -73,8 +73,8 @@ template <typename Type, typename = void>
struct has_str : std::false_type {}; struct has_str : std::false_type {};
template <typename T> template <typename T>
struct has_str<T, std::void_t<decltype(std::declval<T>().str())>> struct has_str<T, std::void_t<decltype(std::declval<std::string>().append(
: std::true_type {}; std::declval<T>().str()))>> : std::true_type {};
template <typename T> template <typename T>
constexpr inline bool has_str_v = has_str<std::remove_cvref_t<T>>::value; constexpr inline bool has_str_v = has_str<std::remove_cvref_t<T>>::value;
@ -187,7 +187,7 @@ class record_t {
else { else {
std::stringstream ss; std::stringstream ss;
ss << data; ss << data;
ss_.append(ss.str()); ss_.append(std::move(ss).str());
} }
return *this; return *this;

22
include/ylt/metric.hpp Normal file
View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024, Alibaba Group Holding Limited;
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define CINATRA_ENABLE_METRIC_JSON
#include "metric/gauge.hpp"
#include "metric/histogram.hpp"
#include "metric/metric.hpp"
#include "metric/summary.hpp"
#include "ylt/struct_json/json_writer.h"

View File

@ -0,0 +1,301 @@
#pragma once
#include <atomic>
#include <chrono>
#include "metric.hpp"
namespace ylt::metric {
enum class op_type_t { INC, DEC, SET };
#ifdef CINATRA_ENABLE_METRIC_JSON
struct json_counter_metric_t {
std::unordered_multimap<std::string, std::string> labels;
int64_t value;
};
REFLECTION(json_counter_metric_t, labels, value);
struct json_counter_t {
std::string name;
std::string help;
std::string type;
std::vector<json_counter_metric_t> metrics;
};
REFLECTION(json_counter_t, name, help, type, metrics);
#endif
class counter_t : public metric_t {
public:
// default, no labels, only contains an atomic value.
counter_t(std::string name, std::string help)
: metric_t(MetricType::Counter, std::move(name), std::move(help)) {
use_atomic_ = true;
}
// static labels value, contains a map with atomic value.
counter_t(std::string name, std::string help,
std::map<std::string, std::string> labels)
: metric_t(MetricType::Counter, std::move(name), std::move(help),
std::move(labels)) {
atomic_value_map_.emplace(labels_value_, 0);
use_atomic_ = true;
}
// dynamic labels value
counter_t(std::string name, std::string help,
std::vector<std::string> labels_name)
: metric_t(MetricType::Counter, std::move(name), std::move(help),
std::move(labels_name)) {}
virtual ~counter_t() {}
double value() { return default_lable_value_; }
double value(const std::vector<std::string> &labels_value) {
if (use_atomic_) {
double val = atomic_value_map_[labels_value];
return val;
}
else {
std::lock_guard lock(mtx_);
return value_map_[labels_value];
}
}
metric_hash_map<double> value_map() override {
metric_hash_map<double> map;
if (use_atomic_) {
map = {atomic_value_map_.begin(), atomic_value_map_.end()};
}
else {
std::lock_guard lock(mtx_);
map = value_map_;
}
return map;
}
void serialize(std::string &str) override {
if (labels_name_.empty()) {
if (default_lable_value_ == 0) {
return;
}
serialize_head(str);
serialize_default_label(str);
return;
}
serialize_head(str);
std::string s;
if (use_atomic_) {
serialize_map(atomic_value_map_, s);
}
else {
serialize_map(value_map_, s);
}
if (s.empty()) {
str.clear();
}
else {
str.append(s);
}
}
#ifdef CINATRA_ENABLE_METRIC_JSON
void serialize_to_json(std::string &str) override {
std::string s;
if (labels_name_.empty()) {
if (default_lable_value_ == 0) {
return;
}
json_counter_t counter{name_, help_, std::string(metric_name())};
int64_t value = default_lable_value_;
counter.metrics.push_back({{}, value});
iguana::to_json(counter, str);
return;
}
json_counter_t counter{name_, help_, std::string(metric_name())};
if (use_atomic_) {
to_json(counter, atomic_value_map_, str);
}
else {
to_json(counter, value_map_, str);
}
}
template <typename T>
void to_json(json_counter_t &counter, T &map, std::string &str) {
for (auto &[k, v] : map) {
json_counter_metric_t metric;
size_t index = 0;
for (auto &label_value : k) {
metric.labels.emplace(labels_name_[index++], label_value);
}
metric.value = (int64_t)v;
counter.metrics.push_back(std::move(metric));
}
iguana::to_json(counter, str);
}
#endif
void inc(double val = 1) {
if (val < 0) {
throw std::invalid_argument("the value is less than zero");
}
#ifdef __APPLE__
mac_os_atomic_fetch_add(&default_lable_value_, val);
#else
default_lable_value_ += val;
#endif
}
void inc(const std::vector<std::string> &labels_value, double value = 1) {
if (value == 0) {
return;
}
validate(labels_value, value);
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument(
"the given labels_value is not match with origin labels_value");
}
set_value<true>(atomic_value_map_[labels_value], value, op_type_t::INC);
}
else {
std::lock_guard lock(mtx_);
set_value<false>(value_map_[labels_value], value, op_type_t::INC);
}
}
void update(double value) { default_lable_value_ = value; }
void update(const std::vector<std::string> &labels_value, double value) {
if (labels_value.empty() || labels_name_.size() != labels_value.size()) {
throw std::invalid_argument(
"the number of labels_value name and labels_value is not match");
}
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument(
"the given labels_value is not match with origin labels_value");
}
set_value<true>(atomic_value_map_[labels_value], value, op_type_t::SET);
}
else {
std::lock_guard lock(mtx_);
set_value<false>(value_map_[labels_value], value, op_type_t::SET);
}
}
metric_hash_map<std::atomic<double>> &atomic_value_map() {
return atomic_value_map_;
}
protected:
void serialize_default_label(std::string &str) {
str.append(name_);
if (labels_name_.empty()) {
str.append(" ");
}
if (type_ == MetricType::Counter) {
str.append(std::to_string((int64_t)default_lable_value_));
}
else {
str.append(std::to_string(default_lable_value_));
}
str.append("\n");
}
template <typename T>
void serialize_map(T &value_map, std::string &str) {
for (auto &[labels_value, value] : value_map) {
if (value == 0) {
continue;
}
str.append(name_);
str.append("{");
build_string(str, labels_name_, labels_value);
str.append("} ");
if (type_ == MetricType::Counter) {
str.append(std::to_string((int64_t)value));
}
else {
str.append(std::to_string(value));
}
str.append("\n");
}
}
void build_string(std::string &str, const std::vector<std::string> &v1,
const std::vector<std::string> &v2) {
for (size_t i = 0; i < v1.size(); i++) {
str.append(v1[i]).append("=\"").append(v2[i]).append("\"").append(",");
}
str.pop_back();
}
void validate(const std::vector<std::string> &labels_value, double value) {
if (value < 0) {
throw std::invalid_argument("the value is less than zero");
}
if (labels_value.empty() || labels_name_.size() != labels_value.size()) {
throw std::invalid_argument(
"the number of labels_value name and labels_value is not match");
}
}
template <bool is_atomic = false, typename T>
void set_value(T &label_val, double value, op_type_t type) {
switch (type) {
case op_type_t::INC: {
#ifdef __APPLE__
if constexpr (is_atomic) {
mac_os_atomic_fetch_add(&label_val, value);
}
else {
label_val += value;
}
#else
if constexpr (is_atomic) {
label_val.fetch_add(value, std::memory_order_relaxed);
}
else {
label_val += value;
}
#endif
} break;
case op_type_t::DEC:
#ifdef __APPLE__
if constexpr (is_atomic) {
mac_os_atomic_fetch_sub(&label_val, value);
}
else {
label_val -= value;
}
#else
if constexpr (is_atomic) {
label_val.fetch_sub(value, std::memory_order_relaxed);
}
else {
label_val -= value;
}
#endif
break;
case op_type_t::SET:
label_val = value;
break;
}
}
metric_hash_map<std::atomic<double>> atomic_value_map_;
std::atomic<double> default_lable_value_ = 0;
std::mutex mtx_;
metric_hash_map<double> value_map_;
};
} // namespace ylt::metric

View File

@ -0,0 +1,175 @@
#pragma once
#include <array>
#include <cmath>
#include <vector>
// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/ckms_quantiles.h
namespace ylt::metric {
class CKMSQuantiles {
public:
struct Quantile {
Quantile(double quantile, double error)
: quantile(quantile),
error(error),
u(2.0 * error / (1.0 - quantile)),
v(2.0 * error / quantile) {}
double quantile;
double error;
double u;
double v;
};
private:
struct Item {
double value;
int g;
int delta;
Item(double value, int lower_delta, int delta)
: value(value), g(lower_delta), delta(delta) {}
};
public:
explicit CKMSQuantiles(const std::vector<Quantile>& quantiles)
: quantiles_(quantiles), count_(0), buffer_{}, buffer_count_(0) {}
void insert(double value) {
buffer_[buffer_count_] = value;
++buffer_count_;
if (buffer_count_ == buffer_.size()) {
insertBatch();
compress();
}
}
double get(double q) {
insertBatch();
compress();
if (sample_.empty()) {
return std::numeric_limits<double>::quiet_NaN();
}
int rankMin = 0;
const auto desired = static_cast<int>(q * count_);
const auto bound = desired + (allowableError(desired) / 2);
auto it = sample_.begin();
decltype(it) prev;
auto cur = it++;
while (it != sample_.end()) {
prev = cur;
cur = it++;
rankMin += prev->g;
if (rankMin + cur->g + cur->delta > bound) {
return prev->value;
}
}
return sample_.back().value;
}
void reset() {
count_ = 0;
sample_.clear();
buffer_count_ = 0;
}
private:
double allowableError(int rank) {
auto size = sample_.size();
double minError = size + 1;
for (const auto& q : quantiles_.get()) {
double error;
if (rank <= q.quantile * size) {
error = q.u * (size - rank);
}
else {
error = q.v * rank;
}
if (error < minError) {
minError = error;
}
}
return minError;
}
bool insertBatch() {
if (buffer_count_ == 0) {
return false;
}
std::sort(buffer_.begin(), buffer_.begin() + buffer_count_);
std::size_t start = 0;
if (sample_.empty()) {
sample_.emplace_back(buffer_[0], 1, 0);
++start;
++count_;
}
std::size_t idx = 0;
std::size_t item = idx++;
for (std::size_t i = start; i < buffer_count_; ++i) {
double v = buffer_[i];
while (idx < sample_.size() && sample_[item].value < v) {
item = idx++;
}
if (sample_[item].value > v) {
--idx;
}
int delta;
if (idx - 1 == 0 || idx + 1 == sample_.size()) {
delta = 0;
}
else {
delta = static_cast<int>(std::floor(allowableError(idx + 1))) + 1;
}
sample_.emplace(sample_.begin() + idx, v, 1, delta);
count_++;
item = idx++;
}
buffer_count_ = 0;
return true;
}
void compress() {
if (sample_.size() < 2) {
return;
}
std::size_t idx = 0;
std::size_t prev;
std::size_t next = idx++;
while (idx < sample_.size()) {
prev = next;
next = idx++;
if (sample_[prev].g + sample_[next].g + sample_[next].delta <=
allowableError(idx - 1)) {
sample_[next].g += sample_[prev].g;
sample_.erase(sample_.begin() + prev);
}
}
}
private:
const std::reference_wrapper<const std::vector<Quantile>> quantiles_;
std::size_t count_;
std::vector<Item> sample_;
std::array<double, 500> buffer_;
std::size_t buffer_count_;
};
} // namespace ylt::metric

View File

@ -0,0 +1,52 @@
#pragma once
#include "ckms_quantiles.hpp"
// https://github.com/jupp0r/prometheus-cpp/blob/master/core/include/prometheus/detail/time_window_quantiles.h
namespace ylt::metric {
class TimeWindowQuantiles {
using Clock = std::chrono::steady_clock;
public:
TimeWindowQuantiles(const std::vector<CKMSQuantiles::Quantile>& quantiles,
Clock::duration max_age_seconds, int age_buckets)
: quantiles_(quantiles),
ckms_quantiles_(age_buckets, CKMSQuantiles(quantiles_)),
current_bucket_(0),
last_rotation_(Clock::now()),
rotation_interval_(max_age_seconds / age_buckets) {}
double get(double q) const {
CKMSQuantiles& current_bucket = rotate();
return current_bucket.get(q);
}
void insert(double value) {
rotate();
for (auto& bucket : ckms_quantiles_) {
bucket.insert(value);
}
}
private:
CKMSQuantiles& rotate() const {
auto delta = Clock::now() - last_rotation_;
while (delta > rotation_interval_) {
ckms_quantiles_[current_bucket_].reset();
if (++current_bucket_ >= ckms_quantiles_.size()) {
current_bucket_ = 0;
}
delta -= rotation_interval_;
last_rotation_ += rotation_interval_;
}
return ckms_quantiles_[current_bucket_];
}
const std::vector<CKMSQuantiles::Quantile>& quantiles_;
mutable std::vector<CKMSQuantiles> ckms_quantiles_;
mutable std::size_t current_bucket_;
mutable Clock::time_point last_rotation_;
const Clock::duration rotation_interval_;
};
} // namespace ylt::metric

View File

@ -0,0 +1,52 @@
#pragma once
#include <chrono>
#include "counter.hpp"
namespace ylt::metric {
class gauge_t : public counter_t {
public:
gauge_t(std::string name, std::string help)
: counter_t(std::move(name), std::move(help)) {
set_metric_type(MetricType::Gauge);
}
gauge_t(std::string name, std::string help,
std::vector<std::string> labels_name)
: counter_t(std::move(name), std::move(help), std::move(labels_name)) {
set_metric_type(MetricType::Gauge);
}
gauge_t(std::string name, std::string help,
std::map<std::string, std::string> labels)
: counter_t(std::move(name), std::move(help), std::move(labels)) {
set_metric_type(MetricType::Gauge);
}
void dec(double value = 1) {
#ifdef __APPLE__
mac_os_atomic_fetch_sub(&default_lable_value_, value);
#else
default_lable_value_ -= value;
#endif
}
void dec(const std::vector<std::string>& labels_value, double value = 1) {
if (value == 0) {
return;
}
validate(labels_value, value);
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument(
"the given labels_value is not match with origin labels_value");
}
set_value<true>(atomic_value_map_[labels_value], value, op_type_t::DEC);
}
else {
std::lock_guard lock(mtx_);
set_value<false>(value_map_[labels_value], value, op_type_t::DEC);
}
}
};
} // namespace ylt::metric

View File

@ -0,0 +1,283 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <memory>
#include <vector>
#include "counter.hpp"
#include "metric.hpp"
namespace ylt::metric {
#ifdef CINATRA_ENABLE_METRIC_JSON
struct json_histogram_metric_t {
std::map<std::string, std::string> labels;
std::map<double, int64_t> quantiles;
int64_t count;
double sum;
};
REFLECTION(json_histogram_metric_t, labels, quantiles, count, sum);
struct json_histogram_t {
std::string name;
std::string help;
std::string type;
std::vector<json_histogram_metric_t> metrics;
};
REFLECTION(json_histogram_t, name, help, type, metrics);
#endif
class histogram_t : public metric_t {
public:
histogram_t(std::string name, std::string help, std::vector<double> buckets)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, std::move(name), std::move(help)),
sum_(std::make_shared<gauge_t>("", "")) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
for (size_t i = 0; i < buckets.size() + 1; i++) {
bucket_counts_.push_back(std::make_shared<counter_t>("", ""));
}
use_atomic_ = true;
}
histogram_t(std::string name, std::string help, std::vector<double> buckets,
std::vector<std::string> labels_name)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, name, help, labels_name),
sum_(std::make_shared<gauge_t>(name, help, labels_name)) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
for (size_t i = 0; i < buckets.size() + 1; i++) {
bucket_counts_.push_back(
std::make_shared<counter_t>(name, help, labels_name));
}
}
histogram_t(std::string name, std::string help, std::vector<double> buckets,
std::map<std::string, std::string> labels)
: bucket_boundaries_(buckets),
metric_t(MetricType::Histogram, name, help, labels),
sum_(std::make_shared<gauge_t>(name, help, labels)) {
if (!is_strict_sorted(begin(bucket_boundaries_), end(bucket_boundaries_))) {
throw std::invalid_argument("Bucket Boundaries must be strictly sorted");
}
for (size_t i = 0; i < buckets.size() + 1; i++) {
bucket_counts_.push_back(std::make_shared<counter_t>(name, help, labels));
}
use_atomic_ = true;
}
void observe(double value) {
if (!use_atomic_ || !labels_name_.empty()) {
throw std::invalid_argument("not a default label metric");
}
const auto bucket_index = static_cast<std::size_t>(
std::distance(bucket_boundaries_.begin(),
std::lower_bound(bucket_boundaries_.begin(),
bucket_boundaries_.end(), value)));
sum_->inc(value);
bucket_counts_[bucket_index]->inc();
}
void observe(const std::vector<std::string> &labels_value, double value) {
if (sum_->labels_name().empty()) {
throw std::invalid_argument("not a label metric");
}
const auto bucket_index = static_cast<std::size_t>(
std::distance(bucket_boundaries_.begin(),
std::lower_bound(bucket_boundaries_.begin(),
bucket_boundaries_.end(), value)));
sum_->inc(labels_value, value);
bucket_counts_[bucket_index]->inc(labels_value);
}
auto get_bucket_counts() { return bucket_counts_; }
metric_hash_map<double> value_map() override { return sum_->value_map(); }
void serialize(std::string &str) override {
if (!sum_->labels_name().empty()) {
serialize_with_labels(str);
return;
}
serialize_head(str);
double count = 0;
auto bucket_counts = get_bucket_counts();
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
str.append(name_).append("_bucket{");
if (i == bucket_boundaries_.size()) {
str.append("le=\"").append("+Inf").append("\"} ");
}
else {
str.append("le=\"")
.append(std::to_string(bucket_boundaries_[i]))
.append("\"} ");
}
count += counter->value();
str.append(std::to_string(count));
str.append("\n");
}
str.append(name_)
.append("_sum ")
.append(std::to_string(sum_->value()))
.append("\n");
str.append(name_)
.append("_count ")
.append(std::to_string(count))
.append("\n");
}
#ifdef CINATRA_ENABLE_METRIC_JSON
void serialize_to_json(std::string &str) override {
if (!sum_->labels_name().empty()) {
serialize_to_json_with_labels(str);
return;
}
json_histogram_t hist{name_, help_, std::string(metric_name())};
double count = 0;
auto bucket_counts = get_bucket_counts();
json_histogram_metric_t metric{};
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
count += counter->value();
if (i == bucket_boundaries_.size()) {
metric.quantiles.emplace(std::numeric_limits<int>::max(),
(int64_t)count);
}
else {
metric.quantiles.emplace(bucket_boundaries_[i],
(int64_t)counter->value());
}
}
metric.count = (int64_t)count;
metric.sum = sum_->value();
hist.metrics.push_back(std::move(metric));
iguana::to_json(hist, str);
}
#endif
private:
template <class ForwardIterator>
bool is_strict_sorted(ForwardIterator first, ForwardIterator last) {
return std::adjacent_find(first, last,
std::greater_equal<typename std::iterator_traits<
ForwardIterator>::value_type>()) == last;
}
void serialize_with_labels(std::string &str) {
serialize_head(str);
auto bucket_counts = get_bucket_counts();
auto value_map = sum_->value_map();
for (auto &[labels_value, value] : value_map) {
if (value == 0) {
continue;
}
double count = 0;
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
str.append(name_).append("_bucket{");
build_label_string(str, sum_->labels_name(), labels_value);
str.append(",");
if (i == bucket_boundaries_.size()) {
str.append("le=\"").append("+Inf").append("\"} ");
}
else {
str.append("le=\"")
.append(std::to_string(bucket_boundaries_[i]))
.append("\"} ");
}
count += counter->value(labels_value);
str.append(std::to_string(count));
str.append("\n");
}
str.append(name_);
str.append("_sum{");
build_label_string(str, sum_->labels_name(), labels_value);
str.append("} ");
if (type_ == MetricType::Counter) {
str.append(std::to_string((int64_t)value));
}
else {
str.append(std::to_string(value));
}
str.append("\n");
str.append(name_).append("_count{");
build_label_string(str, sum_->labels_name(), labels_value);
str.append("} ");
str.append(std::to_string(count));
str.append("\n");
}
}
#ifdef CINATRA_ENABLE_METRIC_JSON
void serialize_to_json_with_labels(std::string &str) {
json_histogram_t hist{name_, help_, std::string(metric_name())};
auto bucket_counts = get_bucket_counts();
auto value_map = sum_->value_map();
for (auto &[labels_value, value] : value_map) {
if (value == 0) {
continue;
}
size_t count = 0;
json_histogram_metric_t metric{};
for (size_t i = 0; i < bucket_counts.size(); i++) {
auto counter = bucket_counts[i];
count += counter->value(labels_value);
if (i == bucket_boundaries_.size()) {
metric.quantiles.emplace(std::numeric_limits<int>::max(),
(int64_t)count);
}
else {
metric.quantiles.emplace(bucket_boundaries_[i],
(int64_t)counter->value(labels_value));
}
}
metric.count = (int64_t)count;
metric.sum = sum_->value(labels_value);
for (size_t i = 0; i < labels_value.size(); i++) {
metric.labels[sum_->labels_name()[i]] = labels_value[i];
}
hist.metrics.push_back(std::move(metric));
}
iguana::to_json(hist, str);
}
#endif
std::vector<double> bucket_boundaries_;
std::vector<std::shared_ptr<counter_t>> bucket_counts_; // readonly
std::shared_ptr<gauge_t> sum_;
};
} // namespace ylt::metric

View File

@ -0,0 +1,584 @@
#pragma once
#include <algorithm>
#include <atomic>
#include <cassert>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
#include "async_simple/coro/Lazy.h"
#include "async_simple/coro/SyncAwait.h"
#include "cinatra/cinatra_log_wrapper.hpp"
#if __has_include("ylt/coro_io/coro_io.hpp")
#include "ylt/coro_io/coro_io.hpp"
#else
#include "cinatra/ylt/coro_io/coro_io.hpp"
#endif
#ifdef CINATRA_ENABLE_METRIC_JSON
namespace iguana {
template <typename T>
inline char* to_chars_float(T value, char* buffer) {
return buffer + snprintf(buffer, 65, "%g", value);
}
} // namespace iguana
#include <iguana/json_writer.hpp>
#endif
namespace ylt::metric {
enum class MetricType {
Counter,
Gauge,
Histogram,
Summary,
Nil,
};
struct metric_filter_options {
std::optional<std::regex> name_regex{};
std::optional<std::regex> label_regex{};
bool is_white = true;
};
struct vector_hash {
size_t operator()(const std::vector<std::string>& vec) const {
unsigned int seed = 131;
unsigned int hash = 0;
for (const auto& str : vec) {
for (auto ch : str) {
hash = hash * seed + ch;
}
}
return (hash & 0x7FFFFFFF);
}
};
template <typename T>
using metric_hash_map =
std::unordered_map<std::vector<std::string>, T, vector_hash>;
class metric_t {
public:
metric_t() = default;
metric_t(MetricType type, std::string name, std::string help)
: type_(type),
name_(std::move(name)),
help_(std::move(help)),
metric_created_time_(std::chrono::system_clock::now()) {}
metric_t(MetricType type, std::string name, std::string help,
std::vector<std::string> labels_name)
: metric_t(type, std::move(name), std::move(help)) {
labels_name_ = std::move(labels_name);
}
metric_t(MetricType type, std::string name, std::string help,
std::map<std::string, std::string> static_labels)
: metric_t(type, std::move(name), std::move(help)) {
static_labels_ = std::move(static_labels);
for (auto& [k, v] : static_labels_) {
labels_name_.push_back(k);
labels_value_.push_back(v);
}
}
virtual ~metric_t() {}
std::string_view name() { return name_; }
std::string_view help() { return help_; }
MetricType metric_type() { return type_; }
auto get_created_time() { return metric_created_time_; }
std::string_view metric_name() {
switch (type_) {
case MetricType::Counter:
return "counter";
case MetricType::Gauge:
return "gauge";
case MetricType::Histogram:
return "histogram";
case MetricType::Summary:
return "summary";
case MetricType::Nil:
default:
return "unknown";
}
}
const std::vector<std::string>& labels_name() { return labels_name_; }
const std::map<std::string, std::string>& get_static_labels() {
return static_labels_;
}
virtual metric_hash_map<double> value_map() { return {}; }
virtual void serialize(std::string& str) {}
#ifdef CINATRA_ENABLE_METRIC_JSON
virtual void serialize_to_json(std::string& str) {}
#endif
// only for summary
virtual async_simple::coro::Lazy<void> serialize_async(std::string& out) {
co_return;
}
#ifdef CINATRA_ENABLE_METRIC_JSON
// only for summary
virtual async_simple::coro::Lazy<void> serialize_to_json_async(
std::string& out) {
co_return;
}
#endif
bool is_atomic() const { return use_atomic_; }
template <typename T>
T* as() {
return dynamic_cast<T*>(this);
}
protected:
void set_metric_type(MetricType type) { type_ = type; }
void serialize_head(std::string& str) {
str.append("# HELP ").append(name_).append(" ").append(help_).append("\n");
str.append("# TYPE ")
.append(name_)
.append(" ")
.append(metric_name())
.append("\n");
}
void build_label_string(std::string& str,
const std::vector<std::string>& label_name,
const std::vector<std::string>& label_value) {
for (size_t i = 0; i < label_name.size(); i++) {
str.append(label_name[i])
.append("=\"")
.append(label_value[i])
.append("\"")
.append(",");
}
str.pop_back();
}
#ifdef __APPLE__
double mac_os_atomic_fetch_add(std::atomic<double>* obj, double arg) {
double v;
do {
v = obj->load();
} while (!std::atomic_compare_exchange_weak(obj, &v, v + arg));
return v;
}
double mac_os_atomic_fetch_sub(std::atomic<double>* obj, double arg) {
double v;
do {
v = obj->load();
} while (!std::atomic_compare_exchange_weak(obj, &v, v - arg));
return v;
}
#endif
MetricType type_ = MetricType::Nil;
std::string name_;
std::string help_;
std::map<std::string, std::string> static_labels_;
std::vector<std::string> labels_name_; // read only
std::vector<std::string> labels_value_; // read only
bool use_atomic_ = false;
std::chrono::system_clock::time_point metric_created_time_{};
};
template <size_t ID = 0>
struct metric_manager_t {
struct null_mutex_t {
void lock() {}
void unlock() {}
};
// create and register metric
template <typename T, typename... Args>
static std::shared_ptr<T> create_metric_static(const std::string& name,
const std::string& help,
Args&&... args) {
auto m = std::make_shared<T>(name, help, std::forward<Args>(args)...);
bool r = register_metric_static(m);
if (!r) {
return nullptr;
}
return m;
}
template <typename T, typename... Args>
static std::shared_ptr<T> create_metric_dynamic(const std::string& name,
const std::string& help,
Args&&... args) {
auto m = std::make_shared<T>(name, help, std::forward<Args>(args)...);
bool r = register_metric_dynamic(m);
if (!r) {
return nullptr;
}
return m;
}
static bool register_metric_static(std::shared_ptr<metric_t> metric) {
return register_metric_impl<false>(metric);
}
static bool register_metric_dynamic(std::shared_ptr<metric_t> metric) {
return register_metric_impl<true>(metric);
}
static bool remove_metric_static(const std::string& name) {
return remove_metric_impl<false>(name);
}
static bool remove_metric_dynamic(const std::string& name) {
return remove_metric_impl<true>(name);
}
template <typename... Metrics>
static bool register_metric_dynamic(Metrics... metrics) {
bool r = true;
((void)(r && (r = register_metric_impl<true>(metrics), true)), ...);
return r;
}
template <typename... Metrics>
static bool register_metric_static(Metrics... metrics) {
bool r = true;
((void)(r && (r = register_metric_impl<false>(metrics), true)), ...);
return r;
}
static auto metric_map_static() { return metric_map_impl<false>(); }
static auto metric_map_dynamic() { return metric_map_impl<true>(); }
static size_t metric_count_static() { return metric_count_impl<false>(); }
static size_t metric_count_dynamic() { return metric_count_impl<true>(); }
static std::vector<std::string> metric_keys_static() {
return metric_keys_impl<false>();
}
static std::vector<std::string> metric_keys_dynamic() {
return metric_keys_impl<true>();
}
// static labels: {{"method", "GET"}, {"url", "/"}}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_labels_static(
const std::map<std::string, std::string>& labels) {
std::vector<std::shared_ptr<metric_t>> vec;
auto map = metric_map_static();
for (auto& [name, m] : map) {
const auto& static_labels = m->get_static_labels();
if (static_labels == labels) {
vec.push_back(m);
}
}
return vec;
}
// static label: {"method", "GET"}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_label_static(
const std::pair<std::string, std::string>& label) {
std::vector<std::shared_ptr<metric_t>> vec;
auto map = metric_map_static();
for (auto& [name, t] : map) {
const auto& static_labels = t->get_static_labels();
for (const auto& pair : static_labels) {
if (pair.first == label.first && pair.second == label.second) {
vec.push_back(t);
}
}
}
return vec;
}
// labels: {{"method", "POST"}, {"code", "200"}}
static std::vector<std::shared_ptr<metric_t>> get_metric_by_labels_dynamic(
const std::map<std::string, std::string>& labels) {
std::vector<std::shared_ptr<metric_t>> vec;
auto map = metric_map_dynamic();
for (auto& [name, t] : map) {
auto val_map = t->value_map();
auto labels_name = t->labels_name();
for (auto& [k, v] : labels) {
if (auto it = std::find(labels_name.begin(), labels_name.end(), k);
it != labels_name.end()) {
if (auto it = std::find_if(val_map.begin(), val_map.end(),
[label_val = v](auto& pair) {
auto& key = pair.first;
return std::find(key.begin(), key.end(),
label_val) != key.end();
});
it != val_map.end()) {
vec.push_back(t);
}
}
}
}
return vec;
}
template <typename T>
static std::shared_ptr<T> get_metric_static(const std::string& name) {
auto m = get_metric_impl<false>(name);
if (m == nullptr) {
return nullptr;
}
return std::dynamic_pointer_cast<T>(m);
}
template <typename T>
static std::shared_ptr<T> get_metric_dynamic(const std::string& name) {
auto m = get_metric_impl<true>(name);
if (m == nullptr) {
return nullptr;
}
return std::dynamic_pointer_cast<T>(m);
}
static std::string serialize(
const std::vector<std::shared_ptr<metric_t>>& metrics) {
std::string str;
for (auto& m : metrics) {
if (m->metric_type() == MetricType::Summary) {
async_simple::coro::syncAwait(m->serialize_async(str));
}
else {
m->serialize(str);
}
}
return str;
}
static std::string serialize_static() { return serialize(collect<false>()); }
static std::string serialize_dynamic() { return serialize(collect<true>()); }
#ifdef CINATRA_ENABLE_METRIC_JSON
static std::string serialize_to_json_static() {
auto metrics = collect<false>();
return serialize_to_json(metrics);
}
static std::string serialize_to_json_dynamic() {
auto metrics = collect<true>();
return serialize_to_json(metrics);
}
static std::string serialize_to_json(
const std::vector<std::shared_ptr<metric_t>>& metrics) {
if (metrics.empty()) {
return "";
}
std::string str;
str.append("[");
for (auto& m : metrics) {
size_t start = str.size();
if (m->metric_type() == MetricType::Summary) {
async_simple::coro::syncAwait(m->serialize_to_json_async(str));
}
else {
m->serialize_to_json(str);
}
if (str.size() > start)
str.append(",");
}
str.back() = ']';
return str;
}
#endif
static std::vector<std::shared_ptr<metric_t>> filter_metrics_static(
const metric_filter_options& options) {
return filter_metrics<false>(options);
}
static std::vector<std::shared_ptr<metric_t>> filter_metrics_dynamic(
const metric_filter_options& options) {
return filter_metrics<true>(options);
}
private:
template <bool need_lock>
static void check_lock() {
if (need_lock_ != need_lock) {
std::string str = "need lock ";
std::string s = need_lock_ ? "true" : "false";
std::string r = need_lock ? "true" : "false";
str.append(s).append(" but set as ").append(r);
throw std::invalid_argument(str);
}
}
template <bool need_lock = true>
static auto get_lock() {
check_lock<need_lock>();
if constexpr (need_lock) {
return std::scoped_lock(mtx_);
}
else {
return std::scoped_lock(null_mtx_);
}
}
template <bool need_lock>
static bool register_metric_impl(std::shared_ptr<metric_t> metric) {
// the first time regiter_metric will set metric_manager_t lock or not lock.
// visit metric_manager_t with different lock strategy will cause throw
// exception.
std::call_once(flag_, [] {
need_lock_ = need_lock;
});
std::string name(metric->name());
auto lock = get_lock<need_lock>();
bool r = metric_map_.emplace(name, std::move(metric)).second;
if (!r) {
CINATRA_LOG_ERROR << "duplicate registered metric name: " << name;
}
return r;
}
template <bool need_lock>
static size_t remove_metric_impl(const std::string& name) {
auto lock = get_lock<need_lock>();
return metric_map_.erase(name);
}
template <bool need_lock>
static auto metric_map_impl() {
auto lock = get_lock<need_lock>();
return metric_map_;
}
template <bool need_lock>
static size_t metric_count_impl() {
auto lock = get_lock<need_lock>();
return metric_map_.size();
}
template <bool need_lock>
static std::vector<std::string> metric_keys_impl() {
std::vector<std::string> keys;
{
auto lock = get_lock<need_lock>();
for (auto& pair : metric_map_) {
keys.push_back(pair.first);
}
}
return keys;
}
template <bool need_lock>
static std::shared_ptr<metric_t> get_metric_impl(const std::string& name) {
auto lock = get_lock<need_lock>();
auto it = metric_map_.find(name);
if (it == metric_map_.end()) {
return nullptr;
}
return it->second;
}
template <bool need_lock>
static auto collect() {
std::vector<std::shared_ptr<metric_t>> metrics;
{
auto lock = get_lock<need_lock>();
for (auto& pair : metric_map_) {
metrics.push_back(pair.second);
}
}
return metrics;
}
static void filter_by_label_name(
std::vector<std::shared_ptr<metric_t>>& filtered_metrics,
std::shared_ptr<metric_t> m, const metric_filter_options& options,
std::vector<size_t>& indexs, size_t index) {
const auto& labels_name = m->labels_name();
for (auto& label_name : labels_name) {
if (std::regex_match(label_name, *options.label_regex)) {
if (options.is_white) {
filtered_metrics.push_back(m);
}
else {
indexs.push_back(index);
}
}
}
}
template <bool need_lock>
static std::vector<std::shared_ptr<metric_t>> filter_metrics(
const metric_filter_options& options) {
auto metrics = collect<need_lock>();
if (!options.name_regex && !options.label_regex) {
return metrics;
}
std::vector<std::shared_ptr<metric_t>> filtered_metrics;
std::vector<size_t> indexs;
size_t index = 0;
for (auto& m : metrics) {
if (options.name_regex && !options.label_regex) {
if (std::regex_match(std::string(m->name()), *options.name_regex)) {
if (options.is_white) {
filtered_metrics.push_back(m);
}
else {
indexs.push_back(index);
}
}
}
else if (options.label_regex && !options.name_regex) {
filter_by_label_name(filtered_metrics, m, options, indexs, index);
}
else {
if (std::regex_match(std::string(m->name()), *options.name_regex)) {
filter_by_label_name(filtered_metrics, m, options, indexs, index);
}
}
index++;
}
if (!options.is_white) {
for (size_t i : indexs) {
metrics.erase(std::next(metrics.begin(), i));
}
return metrics;
}
return filtered_metrics;
}
static inline std::mutex mtx_;
static inline std::map<std::string, std::shared_ptr<metric_t>> metric_map_;
static inline null_mutex_t null_mtx_;
static inline std::atomic_bool need_lock_ = true;
static inline std::once_flag flag_;
};
using default_metric_manager = metric_manager_t<0>;
} // namespace ylt::metric

View File

@ -0,0 +1,459 @@
#pragma once
#include <atomic>
#include "detail/time_window_quantiles.hpp"
#include "metric.hpp"
#if __has_include("ylt/util/concurrentqueue.h")
#include "ylt/util/concurrentqueue.h"
#else
#include "cinatra/ylt/util/concurrentqueue.h"
#endif
namespace ylt::metric {
#ifdef CINATRA_ENABLE_METRIC_JSON
struct json_summary_metric_t {
std::map<std::string, std::string> labels;
std::map<double, double> quantiles;
int64_t count;
double sum;
};
REFLECTION(json_summary_metric_t, labels, quantiles, count, sum);
struct json_summary_t {
std::string name;
std::string help;
std::string type;
std::vector<json_summary_metric_t> metrics;
};
REFLECTION(json_summary_t, name, help, type, metrics);
#endif
struct summary_label_sample {
std::vector<std::string> labels_value;
double value;
};
class summary_t : public metric_t {
public:
using Quantiles = std::vector<CKMSQuantiles::Quantile>;
summary_t(std::string name, std::string help, Quantiles quantiles,
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5)
: quantiles_{std::move(quantiles)},
metric_t(MetricType::Summary, std::move(name), std::move(help)),
max_age_(max_age),
age_buckets_(age_buckets) {
init_block(block_);
block_->quantile_values_ =
std::make_shared<TimeWindowQuantiles>(quantiles_, max_age, age_buckets);
use_atomic_ = true;
}
summary_t(std::string name, std::string help, Quantiles quantiles,
std::vector<std::string> labels_name,
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5)
: quantiles_{std::move(quantiles)},
metric_t(MetricType::Summary, std::move(name), std::move(help),
std::move(labels_name)),
max_age_(max_age),
age_buckets_(age_buckets) {
init_block(labels_block_);
}
summary_t(std::string name, std::string help, Quantiles quantiles,
std::map<std::string, std::string> static_labels,
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5)
: quantiles_{std::move(quantiles)},
metric_t(MetricType::Summary, std::move(name), std::move(help),
std::move(static_labels)),
max_age_(max_age),
age_buckets_(age_buckets) {
init_block(labels_block_);
labels_block_->label_quantile_values_[labels_value_] =
std::make_shared<TimeWindowQuantiles>(quantiles_, max_age, age_buckets);
labels_block_->label_count_.emplace(labels_value_, 0);
labels_block_->label_sum_.emplace(labels_value_, 0);
use_atomic_ = true;
}
~summary_t() {
if (block_) {
block_->stop_ = true;
}
if (labels_block_) {
labels_block_->stop_ = true;
}
}
struct block_t {
std::atomic<bool> stop_ = false;
moodycamel::ConcurrentQueue<double> sample_queue_;
std::shared_ptr<TimeWindowQuantiles> quantile_values_;
std::uint64_t count_;
double sum_;
};
struct labels_block_t {
std::atomic<bool> stop_ = false;
moodycamel::ConcurrentQueue<summary_label_sample> sample_queue_;
metric_hash_map<std::shared_ptr<TimeWindowQuantiles>>
label_quantile_values_;
metric_hash_map<uint64_t> label_count_;
metric_hash_map<double> label_sum_;
};
void observe(double value) {
if (!labels_name_.empty()) {
throw std::invalid_argument("not a default label metric");
}
if (block_->sample_queue_.size_approx() >= 20000000) {
// TODO: record failed count.
return;
}
block_->sample_queue_.enqueue(value);
bool expected = false;
if (is_coro_started_.compare_exchange_strong(expected, true)) {
start(block_).via(excutor_->get_executor()).start([](auto &&) {
});
}
}
void observe(std::vector<std::string> labels_value, double value) {
if (labels_value.empty()) {
throw std::invalid_argument("not a label metric");
}
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument("not equal with static label");
}
}
if (labels_block_->sample_queue_.size_approx() >= 20000000) {
// TODO: record failed count.
return;
}
labels_block_->sample_queue_.enqueue({std::move(labels_value), value});
bool expected = false;
if (is_coro_started_.compare_exchange_strong(expected, true)) {
start(labels_block_).via(excutor_->get_executor()).start([](auto &&) {
});
}
}
async_simple::coro::Lazy<std::vector<double>> get_rates(double &sum,
uint64_t &count) {
std::vector<double> vec;
if (quantiles_.empty()) {
co_return std::vector<double>{};
}
co_await coro_io::post(
[this, &vec, &sum, &count] {
sum = block_->sum_;
count = block_->count_;
for (const auto &quantile : quantiles_) {
vec.push_back(block_->quantile_values_->get(quantile.quantile));
}
},
excutor_->get_executor());
co_return vec;
}
async_simple::coro::Lazy<std::vector<double>> get_rates(
const std::vector<std::string> &labels_value, double &sum,
uint64_t &count) {
std::vector<double> vec;
if (quantiles_.empty()) {
co_return std::vector<double>{};
}
if (use_atomic_) {
if (labels_value != labels_value_) {
throw std::invalid_argument("not equal with static label");
}
}
co_await coro_io::post(
[this, &vec, &sum, &count, &labels_value] {
auto it = labels_block_->label_quantile_values_.find(labels_value);
if (it == labels_block_->label_quantile_values_.end()) {
return;
}
sum = labels_block_->label_sum_[labels_value];
count = labels_block_->label_count_[labels_value];
for (const auto &quantile : quantiles_) {
vec.push_back(it->second->get(quantile.quantile));
}
},
excutor_->get_executor());
co_return vec;
}
metric_hash_map<double> value_map() override {
auto ret = async_simple::coro::syncAwait(coro_io::post(
[this] {
return labels_block_->label_sum_;
},
excutor_->get_executor()));
return ret.value();
}
async_simple::coro::Lazy<double> get_sum() {
auto ret = co_await coro_io::post(
[this] {
return block_->sum_;
},
excutor_->get_executor());
co_return ret.value();
}
async_simple::coro::Lazy<uint64_t> get_count() {
auto ret = co_await coro_io::post(
[this] {
return block_->count_;
},
excutor_->get_executor());
co_return ret.value();
}
size_t size_approx() { return block_->sample_queue_.size_approx(); }
async_simple::coro::Lazy<void> serialize_async(std::string &str) override {
if (block_ == nullptr) {
co_await serialize_async_with_label(str);
co_return;
}
if (quantiles_.empty()) {
co_return;
}
serialize_head(str);
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(sum, count);
for (size_t i = 0; i < quantiles_.size(); i++) {
str.append(name_);
str.append("{quantile=\"");
str.append(std::to_string(quantiles_[i].quantile)).append("\"} ");
str.append(std::to_string(rates[i])).append("\n");
}
str.append(name_).append("_sum ").append(std::to_string(sum)).append("\n");
str.append(name_)
.append("_count ")
.append(std::to_string((uint64_t)count))
.append("\n");
}
#ifdef CINATRA_ENABLE_METRIC_JSON
async_simple::coro::Lazy<void> serialize_to_json_async(
std::string &str) override {
if (block_ == nullptr) {
co_await serialize_to_json_with_label_async(str);
co_return;
}
if (quantiles_.empty()) {
co_return;
}
json_summary_t summary{name_, help_, std::string(metric_name())};
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(sum, count);
json_summary_metric_t metric;
for (size_t i = 0; i < quantiles_.size(); i++) {
metric.quantiles.emplace(quantiles_[i].quantile, rates[i]);
}
metric.sum = sum;
metric.count = count;
summary.metrics.push_back(std::move(metric));
iguana::to_json(summary, str);
}
#endif
private:
template <typename T>
void init_block(std::shared_ptr<T> &block) {
block = std::make_shared<T>();
start(block).via(excutor_->get_executor()).start([](auto &&) {
});
}
async_simple::coro::Lazy<void> start(std::shared_ptr<block_t> block) {
double sample;
size_t count = 1000000;
while (!block->stop_) {
size_t index = 0;
while (block->sample_queue_.try_dequeue(sample)) {
block->quantile_values_->insert(sample);
block->count_ += 1;
block->sum_ += sample;
index++;
if (index == count) {
break;
}
}
if (block->sample_queue_.size_approx() == 0) {
is_coro_started_ = false;
if (block->sample_queue_.size_approx() == 0) {
break;
}
bool expected = false;
if (!is_coro_started_.compare_exchange_strong(expected, true)) {
break;
}
continue;
}
co_await async_simple::coro::Yield{};
}
co_return;
}
async_simple::coro::Lazy<void> start(
std::shared_ptr<labels_block_t> label_block) {
summary_label_sample sample;
size_t count = 1000000;
while (!label_block->stop_) {
size_t index = 0;
while (label_block->sample_queue_.try_dequeue(sample)) {
auto &ptr = label_block->label_quantile_values_[sample.labels_value];
if (ptr == nullptr) {
ptr = std::make_shared<TimeWindowQuantiles>(quantiles_, max_age_,
age_buckets_);
}
ptr->insert(sample.value);
label_block->label_count_[sample.labels_value] += 1;
label_block->label_sum_[sample.labels_value] += sample.value;
index++;
if (index == count) {
break;
}
}
co_await async_simple::coro::Yield{};
if (label_block->sample_queue_.size_approx() == 0) {
is_coro_started_ = false;
if (label_block->sample_queue_.size_approx() == 0) {
break;
}
bool expected = false;
if (!is_coro_started_.compare_exchange_strong(expected, true)) {
break;
}
continue;
}
co_await async_simple::coro::Yield{};
}
co_return;
}
async_simple::coro::Lazy<void> serialize_async_with_label(std::string &str) {
if (quantiles_.empty()) {
co_return;
}
serialize_head(str);
auto sum_map = co_await coro_io::post(
[this] {
return labels_block_->label_sum_;
},
excutor_->get_executor());
for (auto &[labels_value, sum_val] : sum_map.value()) {
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(labels_value, sum, count);
for (size_t i = 0; i < quantiles_.size(); i++) {
str.append(name_);
str.append("{");
build_label_string(str, labels_name_, labels_value);
str.append(",");
str.append("quantile=\"");
str.append(std::to_string(quantiles_[i].quantile)).append("\"} ");
str.append(std::to_string(rates[i])).append("\n");
}
str.append(name_).append("_sum ");
str.append("{");
build_label_string(str, labels_name_, labels_value);
str.append("} ");
str.append(std::to_string(sum)).append("\n");
str.append(name_).append("_count ");
str.append("{");
build_label_string(str, labels_name_, labels_value);
str.append("} ");
str.append(std::to_string((uint64_t)count)).append("\n");
}
}
#ifdef CINATRA_ENABLE_METRIC_JSON
async_simple::coro::Lazy<void> serialize_to_json_with_label_async(
std::string &str) {
if (quantiles_.empty()) {
co_return;
}
auto sum_map = co_await coro_io::post(
[this] {
return labels_block_->label_sum_;
},
excutor_->get_executor());
json_summary_t summary{name_, help_, std::string(metric_name())};
for (auto &[labels_value, sum_val] : sum_map.value()) {
json_summary_metric_t metric;
double sum = 0;
uint64_t count = 0;
auto rates = co_await get_rates(labels_value, sum, count);
metric.count = count;
metric.sum = sum;
for (size_t i = 0; i < quantiles_.size(); i++) {
for (size_t i = 0; i < labels_value.size(); i++) {
metric.labels[labels_name_[i]] = labels_value[i];
}
metric.quantiles.emplace(quantiles_[i].quantile, rates[i]);
}
summary.metrics.push_back(std::move(metric));
}
iguana::to_json(summary, str);
}
#endif
Quantiles quantiles_; // readonly
std::shared_ptr<block_t> block_;
std::shared_ptr<labels_block_t> labels_block_;
static inline std::shared_ptr<coro_io::io_context_pool> excutor_ =
coro_io::create_io_context_pool(1);
std::chrono::milliseconds max_age_;
int age_buckets_;
std::atomic<bool> is_coro_started_ = false;
};
} // namespace ylt::metric

View File

@ -31,16 +31,16 @@ constexpr inline cinatra::null_logger_t NULL_LOGGER;
#ifdef CINATRA_LOG_ERROR #ifdef CINATRA_LOG_ERROR
#else #else
#define CINATRA_LOG_ERROR \ #define CINATRA_LOG_ERROR \
cerr_logger_t {} cinatra::cerr_logger_t {}
#endif #endif
#ifdef CINATRA_LOG_WARNING #ifdef CINATRA_LOG_WARNING
#else #else
#ifndef NDEBUG #ifndef NDEBUG
#define CINATRA_LOG_WARNING \ #define CINATRA_LOG_WARNING \
cerr_logger_t {} cinatra::cerr_logger_t {}
#else #else
#define CINATRA_LOG_WARNING NULL_LOGGER #define CINATRA_LOG_WARNING cinatra::NULL_LOGGER
#endif #endif
#endif #endif
@ -48,9 +48,9 @@ constexpr inline cinatra::null_logger_t NULL_LOGGER;
#else #else
#ifndef NDEBUG #ifndef NDEBUG
#define CINATRA_LOG_INFO \ #define CINATRA_LOG_INFO \
cout_logger_t {} cinatra::cout_logger_t {}
#else #else
#define CINATRA_LOG_INFO NULL_LOGGER #define CINATRA_LOG_INFO cinatra::NULL_LOGGER
#endif #endif
#endif #endif
@ -58,9 +58,9 @@ constexpr inline cinatra::null_logger_t NULL_LOGGER;
#else #else
#ifndef NDEBUG #ifndef NDEBUG
#define CINATRA_LOG_DEBUG \ #define CINATRA_LOG_DEBUG \
cout_logger_t {} cinatra::cout_logger_t {}
#else #else
#define CINATRA_LOG_DEBUG NULL_LOGGER #define CINATRA_LOG_DEBUG cinatra::NULL_LOGGER
#endif #endif
#endif #endif
@ -68,8 +68,8 @@ constexpr inline cinatra::null_logger_t NULL_LOGGER;
#else #else
#ifndef NDEBUG #ifndef NDEBUG
#define CINATRA_LOG_TRACE \ #define CINATRA_LOG_TRACE \
cout_logger_t {} cinatra::cout_logger_t {}
#else #else
#define CINATRA_LOG_TRACE NULL_LOGGER #define CINATRA_LOG_TRACE cinatra::NULL_LOGGER
#endif #endif
#endif #endif

View File

@ -21,6 +21,9 @@
#include "async_simple/Unit.h" #include "async_simple/Unit.h"
#include "async_simple/coro/FutureAwaiter.h" #include "async_simple/coro/FutureAwaiter.h"
#include "async_simple/coro/Lazy.h" #include "async_simple/coro/Lazy.h"
#ifdef CINATRA_ENABLE_GZIP
#include "gzip.hpp"
#endif
#include "cinatra_log_wrapper.hpp" #include "cinatra_log_wrapper.hpp"
#include "http_parser.hpp" #include "http_parser.hpp"
#include "multipart.hpp" #include "multipart.hpp"
@ -197,6 +200,8 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
}); });
} }
coro_io::ExecutorWrapper<> &get_executor() { return executor_wrapper_; }
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
bool init_ssl(int verify_mode, const std::string &base_path, bool init_ssl(int verify_mode, const std::string &base_path,
const std::string &cert_file, const std::string &sni_hostname) { const std::string &cert_file, const std::string &sni_hostname) {
@ -246,7 +251,7 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
return true; return true;
} }
[[nodiscard]] bool init_ssl(int verify_mode = asio::ssl::verify_peer, [[nodiscard]] bool init_ssl(int verify_mode = asio::ssl::verify_none,
std::string full_path = "", std::string full_path = "",
const std::string &sni_hostname = "") { const std::string &sni_hostname = "") {
std::string base_path; std::string base_path;
@ -271,12 +276,29 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
return std::move(body_); return std::move(body_);
} }
#ifdef CINATRA_ENABLE_GZIP
void set_ws_deflate(bool enable_ws_deflate) {
enable_ws_deflate_ = enable_ws_deflate;
}
#endif
// only make socket connet(or handshake) to the host // only make socket connet(or handshake) to the host
async_simple::coro::Lazy<resp_data> connect(std::string uri) { async_simple::coro::Lazy<resp_data> connect(std::string uri) {
if (should_reset_) {
reset();
}
else {
should_reset_ = true;
}
resp_data data{}; resp_data data{};
bool no_schema = !has_schema(uri); bool no_schema = !has_schema(uri);
std::string append_uri; std::string append_uri;
if (no_schema) { if (no_schema) {
#ifdef CINATRA_ENABLE_SSL
if (is_ssl_schema_)
append_uri.append("https://").append(uri);
else
#endif
append_uri.append("http://").append(uri); append_uri.append("http://").append(uri);
} }
@ -284,12 +306,48 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
if (!ok) { if (!ok) {
co_return resp_data{std::make_error_code(std::errc::protocol_error), 404}; co_return resp_data{std::make_error_code(std::errc::protocol_error), 404};
} }
{
auto time_out_guard =
timer_guard(this, conn_timeout_duration_, "connect timer");
if (u.is_websocket()) {
// build websocket http header
add_header("Upgrade", "websocket");
add_header("Connection", "Upgrade");
if (ws_sec_key_.empty()) {
ws_sec_key_ = "s//GYHa/XO7Hd2F2eOGfyA=="; // provide a random string.
}
add_header("Sec-WebSocket-Key", ws_sec_key_);
add_header("Sec-WebSocket-Version", "13");
#ifdef CINATRA_ENABLE_GZIP
if (enable_ws_deflate_)
add_header("Sec-WebSocket-Extensions",
"permessage-deflate; client_max_window_bits");
#endif
req_context<> ctx{};
data = co_await async_request(std::move(uri), http_method::GET,
std::move(ctx));
auto future = start_timer(conn_timeout_duration_, "connect timer"); #ifdef CINATRA_ENABLE_GZIP
if (enable_ws_deflate_) {
for (auto c : data.resp_headers) {
if (c.name == "Sec-WebSocket-Extensions") {
if (c.value.find("permessage-deflate;") != std::string::npos) {
is_server_support_ws_deflate_ = true;
}
else {
is_server_support_ws_deflate_ = false;
}
break;
}
}
}
#endif
co_return data;
}
data = co_await connect(u); data = co_await connect(u);
if (auto ec = co_await wait_future(std::move(future)); ec) { }
co_return resp_data{ec, 404}; if (socket_->is_timeout_) {
co_return resp_data{std::make_error_code(std::errc::timed_out), 404};
} }
if (!data.net_err) { if (!data.net_err) {
data.status = 200; data.status = 200;
@ -316,50 +374,41 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
void set_ws_sec_key(std::string sec_key) { ws_sec_key_ = std::move(sec_key); } void set_ws_sec_key(std::string sec_key) { ws_sec_key_ = std::move(sec_key); }
async_simple::coro::Lazy<bool> async_ws_connect(std::string uri) { async_simple::coro::Lazy<resp_data> read_websocket() {
resp_data data{}; co_return co_await async_read_ws();
auto [r, u] = handle_uri(data, uri);
if (!r) {
CINATRA_LOG_WARNING << "url error:";
co_return false;
} }
req_context<> ctx{}; async_simple::coro::Lazy<resp_data> write_websocket(
if (u.is_websocket()) { const char *data, opcode op = opcode::text) {
// build websocket http header
add_header("Upgrade", "websocket");
add_header("Connection", "Upgrade");
if (ws_sec_key_.empty()) {
ws_sec_key_ = "s//GYHa/XO7Hd2F2eOGfyA=="; // provide a random string.
}
add_header("Sec-WebSocket-Key", ws_sec_key_);
add_header("Sec-WebSocket-Version", "13");
}
data = co_await async_request(std::move(uri), http_method::GET,
std::move(ctx));
async_read_ws().start([](auto &&) {
});
co_return !data.net_err;
}
async_simple::coro::Lazy<resp_data> async_send_ws(const char *data,
bool need_mask = true,
opcode op = opcode::text) {
std::string str(data); std::string str(data);
co_return co_await async_send_ws(std::span<char>(str), need_mask, op); co_return co_await write_websocket(str, op);
} }
async_simple::coro::Lazy<resp_data> async_send_ws(std::string data, async_simple::coro::Lazy<resp_data> write_websocket(
bool need_mask = true, const char *data, size_t size, opcode op = opcode::text) {
opcode op = opcode::text) { std::string str(data, size);
co_return co_await async_send_ws(std::span<char>(data), need_mask, op); co_return co_await write_websocket(str, op);
}
async_simple::coro::Lazy<resp_data> write_websocket(
std::string_view data, opcode op = opcode::text) {
std::string str(data);
co_return co_await write_websocket(str, op);
}
async_simple::coro::Lazy<resp_data> write_websocket(
std::string &data, opcode op = opcode::text) {
co_return co_await write_websocket(std::span<char>(data), op);
}
async_simple::coro::Lazy<resp_data> write_websocket(
std::string &&data, opcode op = opcode::text) {
co_return co_await write_websocket(std::span<char>(data), op);
} }
template <typename Source> template <typename Source>
async_simple::coro::Lazy<resp_data> async_send_ws(Source source, async_simple::coro::Lazy<resp_data> write_websocket(
bool need_mask = true, Source source, opcode op = opcode::text) {
opcode op = opcode::text) {
resp_data data{}; resp_data data{};
websocket ws{}; websocket ws{};
@ -373,7 +422,32 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
if constexpr (is_span_v<Source>) { if constexpr (is_span_v<Source>) {
std::string encode_header = ws.encode_frame(source, op, need_mask); #ifdef CINATRA_ENABLE_GZIP
if (enable_ws_deflate_ && is_server_support_ws_deflate_) {
std::string dest_buf;
if (cinatra::gzip_codec::deflate({source.data(), source.size()},
dest_buf)) {
std::span<char> msg(dest_buf.data(), dest_buf.size());
auto header = ws.encode_frame(msg, op, true, true);
std::vector<asio::const_buffer> buffers{asio::buffer(header),
asio::buffer(dest_buf)};
auto [ec, sz] = co_await async_write(buffers);
if (ec) {
data.net_err = ec;
data.status = 404;
}
}
else {
CINATRA_LOG_ERROR << "compuress data error, data: "
<< std::string(source.begin(), source.end());
data.net_err = std::make_error_code(std::errc::protocol_error);
data.status = 404;
}
}
else {
#endif
auto encode_header = ws.encode_frame(source, op, true);
std::vector<asio::const_buffer> buffers{ std::vector<asio::const_buffer> buffers{
asio::buffer(encode_header.data(), encode_header.size()), asio::buffer(encode_header.data(), encode_header.size()),
asio::buffer(source.data(), source.size())}; asio::buffer(source.data(), source.size())};
@ -383,14 +457,39 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
data.net_err = ec; data.net_err = ec;
data.status = 404; data.status = 404;
} }
#ifdef CINATRA_ENABLE_GZIP
}
#endif
} }
else { else {
while (true) { while (true) {
auto result = co_await source(); auto result = co_await source();
#ifdef CINATRA_ENABLE_GZIP
if (enable_ws_deflate_ && is_server_support_ws_deflate_) {
std::string dest_buf;
if (cinatra::gzip_codec::deflate(
{result.buf.data(), result.buf.size()}, dest_buf)) {
std::span<char> msg(dest_buf.data(), dest_buf.size());
auto header = ws.encode_frame(msg, op, result.eof, true);
std::vector<asio::const_buffer> buffers{asio::buffer(header),
asio::buffer(dest_buf)};
auto [ec, sz] = co_await async_write(buffers);
if (ec) {
data.net_err = ec;
data.status = 404;
}
}
else {
CINATRA_LOG_ERROR << "compuress data error, data: "
<< std::string(result.buf.data());
data.net_err = std::make_error_code(std::errc::protocol_error);
data.status = 404;
}
}
else {
#endif
std::span<char> msg(result.buf.data(), result.buf.size()); std::span<char> msg(result.buf.data(), result.buf.size());
std::string encode_header = auto encode_header = ws.encode_frame(msg, op, result.eof);
ws.encode_frame(msg, op, need_mask, result.eof);
std::vector<asio::const_buffer> buffers{ std::vector<asio::const_buffer> buffers{
asio::buffer(encode_header.data(), encode_header.size()), asio::buffer(encode_header.data(), encode_header.size()),
asio::buffer(msg.data(), msg.size())}; asio::buffer(msg.data(), msg.size())};
@ -405,27 +504,22 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
if (result.eof) { if (result.eof) {
break; break;
} }
#ifdef CINATRA_ENABLE_GZIP
}
#endif
} }
} }
co_return data; co_return data;
} }
async_simple::coro::Lazy<resp_data> async_send_ws_close( async_simple::coro::Lazy<resp_data> write_websocket_close(
std::string msg = "") { std::string msg = "") {
co_return co_await async_send_ws(std::move(msg), false, opcode::close); co_return co_await write_websocket(std::move(msg), opcode::close);
}
void on_ws_msg(std::function<void(resp_data)> on_ws_msg) {
on_ws_msg_ = std::move(on_ws_msg);
}
void on_ws_close(std::function<void(std::string_view)> on_ws_close) {
on_ws_close_ = std::move(on_ws_close);
} }
#ifdef BENCHMARK_TEST #ifdef BENCHMARK_TEST
void set_bench_stop() { stop_bench_ = true; } void set_bench_stop() { stop_bench_ = true; }
void set_read_fix() { read_fix_ = 1; }
#endif #endif
async_simple::coro::Lazy<resp_data> async_patch( async_simple::coro::Lazy<resp_data> async_patch(
@ -468,73 +562,6 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
std::string uri, std::string uri,
std::unordered_map<std::string, std::string> headers = {}) { std::unordered_map<std::string, std::string> headers = {}) {
resp_data data{}; resp_data data{};
#ifdef BENCHMARK_TEST
if (!req_str_.empty()) {
if (has_closed()) {
data.net_err = std::make_error_code(std::errc::not_connected);
data.status = 404;
co_return data;
}
std::error_code ec{};
size_t size = 0;
if (std::tie(ec, size) = co_await async_write(asio::buffer(req_str_));
ec) {
data.net_err = ec;
data.status = 404;
close_socket(*socket_);
co_return data;
}
if (read_fix_ == 0) {
req_context<> ctx{};
bool is_keep_alive = true;
data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx),
http_method::GET);
handle_result(data, ec, is_keep_alive);
if (ec) {
if (!stop_bench_)
CINATRA_LOG_ERROR << "do_bench_read error:" << ec.message();
data.net_err = ec;
data.status = 404;
}
else {
data.status = 200;
data.total = total_len_;
}
co_return data;
}
std::tie(ec, size) = co_await async_read(head_buf_, total_len_);
if (ec) {
if (!stop_bench_)
CINATRA_LOG_ERROR << "do_bench_read error:" << ec.message();
data.net_err = ec;
data.status = 404;
close_socket(*socket_);
co_return data;
}
else {
const char *data_ptr =
asio::buffer_cast<const char *>(head_buf_.data());
head_buf_.consume(total_len_);
// check status
if (data_ptr[9] > '3') {
data.status = 404;
co_return data;
}
}
head_buf_.consume(total_len_);
data.status = 200;
data.total = total_len_;
co_return data;
}
#endif
req_context<> ctx{}; req_context<> ctx{};
data = co_await async_request(std::move(uri), http_method::GET, data = co_await async_request(std::move(uri), http_method::GET,
std::move(ctx), std::move(headers)); std::move(ctx), std::move(headers));
@ -621,38 +648,27 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
void set_max_single_part_size(size_t size) { max_single_part_size_ = size; } void set_max_single_part_size(size_t size) { max_single_part_size_ = size; }
async_simple::Future<async_simple::Unit> start_timer( struct timer_guard {
std::chrono::steady_clock::duration duration, std::string msg) { timer_guard(coro_http_client *self,
is_timeout_ = false; std::chrono::steady_clock::duration duration, std::string msg)
: self(self) {
self->socket_->is_timeout_ = false;
async_simple::Promise<async_simple::Unit> promise; if (self->enable_timeout_) {
auto fut = promise.getFuture(); self->timeout(self->timer_, duration, std::move(msg))
.start([](auto &&) {
if (enable_timeout_) { });
timeout(timer_, std::move(promise), duration, std::move(msg))
.via(&executor_wrapper_)
.detach();
} }
else { return;
promise.setValue(async_simple::Unit{});
} }
return fut; ~timer_guard() {
if (self->enable_timeout_ && self->socket_->is_timeout_ == false) {
std::error_code ignore_ec;
self->timer_.cancel(ignore_ec);
} }
async_simple::coro::Lazy<std::error_code> wait_future(
async_simple::Future<async_simple::Unit> &&future) {
if (!enable_timeout_) {
co_return std::error_code{};
}
std::error_code err_code;
timer_.cancel(err_code);
co_await std::move(future);
if (is_timeout_) {
co_return std::make_error_code(std::errc::timed_out);
}
co_return std::error_code{};
} }
coro_http_client *self;
};
async_simple::coro::Lazy<resp_data> async_upload_multipart(std::string uri) { async_simple::coro::Lazy<resp_data> async_upload_multipart(std::string uri) {
std::shared_ptr<int> guard(nullptr, [this](auto) { std::shared_ptr<int> guard(nullptr, [this](auto) {
@ -682,18 +698,21 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
size_t size = 0; size_t size = 0;
if (socket_->has_closed_) { if (socket_->has_closed_) {
auto future = start_timer(conn_timeout_duration_, "connect timer"); {
auto time_out_guard =
timer_guard(this, conn_timeout_duration_, "connect timer");
data = co_await connect(u); data = co_await connect(u);
if (ec = co_await wait_future(std::move(future)); ec) { }
co_return resp_data{ec, 404}; if (socket_->is_timeout_) {
co_return resp_data{std::make_error_code(std::errc::timed_out), 404};
} }
if (data.net_err) { if (data.net_err) {
co_return data; co_return data;
} }
} }
auto future = start_timer(req_timeout_duration_, "upload timer"); auto time_out_guard =
timer_guard(this, req_timeout_duration_, "request timer");
std::tie(ec, size) = co_await async_write(asio::buffer(header_str)); std::tie(ec, size) = co_await async_write(asio::buffer(header_str));
#ifdef INJECT_FOR_HTTP_CLIENT_TEST #ifdef INJECT_FOR_HTTP_CLIENT_TEST
if (inject_write_failed == ClientInjectAction::write_failed) { if (inject_write_failed == ClientInjectAction::write_failed) {
@ -712,7 +731,7 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
data = co_await send_single_part(key, part); data = co_await send_single_part(key, part);
if (data.net_err) { if (data.net_err) {
if (data.net_err == asio::error::operation_aborted) { if (socket_->is_timeout_) {
data.net_err = std::make_error_code(std::errc::timed_out); data.net_err = std::make_error_code(std::errc::timed_out);
} }
co_return data; co_return data;
@ -723,16 +742,18 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
last_part.append("--").append(BOUNDARY).append("--").append(CRCF); last_part.append("--").append(BOUNDARY).append("--").append(CRCF);
if (std::tie(ec, size) = co_await async_write(asio::buffer(last_part)); if (std::tie(ec, size) = co_await async_write(asio::buffer(last_part));
ec) { ec) {
if (socket_->is_timeout_) {
ec = std::make_error_code(std::errc::timed_out);
}
co_return resp_data{ec, 404}; co_return resp_data{ec, 404};
} }
bool is_keep_alive = true; bool is_keep_alive = true;
data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx), data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx),
http_method::POST); http_method::POST);
if (auto errc = co_await wait_future(std::move(future)); errc) { if (socket_->is_timeout_) {
ec = errc; ec = std::make_error_code(std::errc::timed_out);
} }
handle_result(data, ec, is_keep_alive); handle_result(data, ec, is_keep_alive);
co_return data; co_return data;
} }
@ -808,14 +829,13 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
#endif #endif
#ifdef BENCHMARK_TEST #ifdef BENCHMARK_TEST
req_str_.clear();
total_len_ = 0; total_len_ = 0;
#endif #endif
}
async_simple::coro::Lazy<resp_data> reconnect(std::string uri) { // clear
reset(); head_buf_.consume(head_buf_.size());
co_return co_await connect(std::move(uri)); chunked_buf_.consume(chunked_buf_.size());
resp_chunk_str_.clear();
} }
std::string_view get_host() { return host_; } std::string_view get_host() { return host_; }
@ -873,20 +893,24 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
size_t size = 0; size_t size = 0;
if (socket_->has_closed_) { if (socket_->has_closed_) {
auto future = start_timer(conn_timeout_duration_, "connect timer"); {
auto guard = timer_guard(this, conn_timeout_duration_, "connect timer");
data = co_await connect(u); data = co_await connect(u);
if (ec = co_await wait_future(std::move(future)); ec) { }
co_return resp_data{ec, 404}; if (socket_->is_timeout_) {
co_return resp_data{std::make_error_code(std::errc::timed_out), 404};
} }
if (data.net_err) { if (data.net_err) {
co_return data; co_return data;
} }
} }
auto future = start_timer(req_timeout_duration_, "upload timer"); auto time_guard = timer_guard(this, req_timeout_duration_, "request timer");
std::tie(ec, size) = co_await async_write(asio::buffer(header_str)); std::tie(ec, size) = co_await async_write(asio::buffer(header_str));
if (ec) { if (ec) {
if (socket_->is_timeout_) {
ec = std::make_error_code(std::errc::timed_out);
}
co_return resp_data{ec, 404}; co_return resp_data{ec, 404};
} }
@ -898,7 +922,8 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
size_t rd_size = size_t rd_size =
source->read(file_data.data(), file_data.size()).gcount(); source->read(file_data.data(), file_data.size()).gcount();
std::vector<asio::const_buffer> bufs; std::vector<asio::const_buffer> bufs;
cinatra::to_chunked_buffers(bufs, {file_data.data(), rd_size}, std::string size_str;
cinatra::to_chunked_buffers(bufs, size_str, {file_data.data(), rd_size},
source->eof()); source->eof());
if (std::tie(ec, size) = co_await async_write(bufs); ec) { if (std::tie(ec, size) = co_await async_write(bufs); ec) {
break; break;
@ -917,7 +942,8 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
auto [rd_ec, rd_size] = auto [rd_ec, rd_size] =
co_await file.async_read(file_data.data(), file_data.size()); co_await file.async_read(file_data.data(), file_data.size());
std::vector<asio::const_buffer> bufs; std::vector<asio::const_buffer> bufs;
cinatra::to_chunked_buffers(bufs, {file_data.data(), rd_size}, std::string size_str;
cinatra::to_chunked_buffers(bufs, size_str, {file_data.data(), rd_size},
file.eof()); file.eof());
if (std::tie(ec, size) = co_await async_write(bufs); ec) { if (std::tie(ec, size) = co_await async_write(bufs); ec) {
break; break;
@ -928,8 +954,9 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
while (true) { while (true) {
auto result = co_await source(); auto result = co_await source();
std::vector<asio::const_buffer> bufs; std::vector<asio::const_buffer> bufs;
std::string size_str;
cinatra::to_chunked_buffers( cinatra::to_chunked_buffers(
bufs, {result.buf.data(), result.buf.size()}, result.eof); bufs, size_str, {result.buf.data(), result.buf.size()}, result.eof);
if (std::tie(ec, size) = co_await async_write(bufs); ec) { if (std::tie(ec, size) = co_await async_write(bufs); ec) {
break; break;
} }
@ -938,19 +965,19 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
} }
} }
if (ec) {
if (ec && ec == asio::error::operation_aborted) { if (socket_->is_timeout_) {
ec = std::make_error_code(std::errc::timed_out); ec = std::make_error_code(std::errc::timed_out);
}
co_return resp_data{ec, 404}; co_return resp_data{ec, 404};
} }
bool is_keep_alive = true; bool is_keep_alive = true;
data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx), data = co_await handle_read(ec, size, is_keep_alive, std::move(ctx),
http_method::POST); http_method::POST);
if (auto errc = co_await wait_future(std::move(future)); errc) { if (ec && socket_->is_timeout_) {
ec = errc; ec = std::make_error_code(std::errc::timed_out);
} }
handle_result(data, ec, is_keep_alive); handle_result(data, ec, is_keep_alive);
co_return data; co_return data;
} }
@ -1013,15 +1040,20 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
u.path = uri; u.path = uri;
} }
if (socket_->has_closed_) { if (socket_->has_closed_) {
auto conn_future = start_timer(conn_timeout_duration_, "connect timer");
host_ = proxy_host_.empty() ? u.get_host() : proxy_host_; host_ = proxy_host_.empty() ? u.get_host() : proxy_host_;
port_ = proxy_port_.empty() ? u.get_port() : proxy_port_; port_ = proxy_port_.empty() ? u.get_port() : proxy_port_;
auto guard = timer_guard(this, conn_timeout_duration_, "connect timer");
if (ec = co_await coro_io::async_connect(&executor_wrapper_, if (ec = co_await coro_io::async_connect(&executor_wrapper_,
socket_->impl_, host_, port_); socket_->impl_, host_, port_);
ec) { ec) {
break; break;
} }
if (socket_->is_timeout_) {
data.net_err = std::make_error_code(std::errc::timed_out);
co_return data;
}
if (enable_tcp_no_delay_) { if (enable_tcp_no_delay_) {
socket_->impl_.set_option(asio::ip::tcp::no_delay(true), ec); socket_->impl_.set_option(asio::ip::tcp::no_delay(true), ec);
if (ec) { if (ec) {
@ -1040,7 +1072,7 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
else { else {
host = std::string{u.host}; host = std::string{u.host};
} }
bool r = init_ssl(asio::ssl::verify_peer, "", host); bool r = init_ssl(asio::ssl::verify_none, "", host);
if (!r) { if (!r) {
data.net_err = std::make_error_code(std::errc::invalid_argument); data.net_err = std::make_error_code(std::errc::invalid_argument);
co_return data; co_return data;
@ -1052,9 +1084,6 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
} }
socket_->has_closed_ = false; socket_->has_closed_ = false;
if (ec = co_await wait_future(std::move(conn_future)); ec) {
break;
}
} }
std::vector<asio::const_buffer> vec; std::vector<asio::const_buffer> vec;
@ -1067,13 +1096,10 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
vec.push_back(asio::buffer(ctx.content.data(), ctx.content.size())); vec.push_back(asio::buffer(ctx.content.data(), ctx.content.size()));
} }
#ifdef BENCHMARK_TEST
req_str_ = req_head_str;
#endif
#ifdef CORO_HTTP_PRINT_REQ_HEAD #ifdef CORO_HTTP_PRINT_REQ_HEAD
CINATRA_LOG_DEBUG << req_head_str; CINATRA_LOG_DEBUG << req_head_str;
#endif #endif
auto future = start_timer(req_timeout_duration_, "request timer"); auto guard = timer_guard(this, req_timeout_duration_, "request timer");
if (has_body) { if (has_body) {
std::tie(ec, size) = co_await async_write(vec); std::tie(ec, size) = co_await async_write(vec);
} }
@ -1083,35 +1109,35 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
if (ec) { if (ec) {
break; break;
} }
data = data =
co_await handle_read(ec, size, is_keep_alive, std::move(ctx), method); co_await handle_read(ec, size, is_keep_alive, std::move(ctx), method);
if (auto errc = co_await wait_future(std::move(future)); errc) {
ec = errc;
}
} while (0); } while (0);
if (ec && socket_->is_timeout_) {
ec = std::make_error_code(std::errc::timed_out);
}
handle_result(data, ec, is_keep_alive); handle_result(data, ec, is_keep_alive);
co_return data; co_return data;
} }
async_simple::coro::Lazy<std::error_code> handle_shake() { async_simple::coro::Lazy<std::error_code> handle_shake() {
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
if (has_init_ssl_) { if (!has_init_ssl_) {
bool r = init_ssl(asio::ssl::verify_none, "", host_);
if (!r) {
co_return std::make_error_code(std::errc::invalid_argument);
}
}
if (socket_->ssl_stream_ == nullptr) { if (socket_->ssl_stream_ == nullptr) {
co_return std::make_error_code(std::errc::not_a_stream); co_return std::make_error_code(std::errc::not_a_stream);
} }
auto ec = co_await coro_io::async_handshake( auto ec = co_await coro_io::async_handshake(socket_->ssl_stream_,
socket_->ssl_stream_, asio::ssl::stream_base::client); asio::ssl::stream_base::client);
if (ec) { if (ec) {
CINATRA_LOG_ERROR << "handle failed " << ec.message(); CINATRA_LOG_ERROR << "handle failed " << ec.message();
} }
co_return ec; co_return ec;
}
else {
co_return std::error_code{};
}
#else #else
// please open CINATRA_ENABLE_SSL before request https! // please open CINATRA_ENABLE_SSL before request https!
co_return std::make_error_code(std::errc::protocol_error); co_return std::make_error_code(std::errc::protocol_error);
@ -1170,6 +1196,7 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
struct socket_t { struct socket_t {
asio::ip::tcp::socket impl_; asio::ip::tcp::socket impl_;
std::atomic<bool> has_closed_ = true; std::atomic<bool> has_closed_ = true;
bool is_timeout_ = false;
asio::streambuf head_buf_; asio::streambuf head_buf_;
asio::streambuf chunked_buf_; asio::streambuf chunked_buf_;
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
@ -1546,7 +1573,7 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
std::string boundary = std::string{parser_.get_boundary()}; std::string boundary = std::string{parser_.get_boundary()};
multipart_reader_t multipart(this); multipart_reader_t multipart(this);
while (true) { while (true) {
auto part_head = co_await multipart.read_part_head(); auto part_head = co_await multipart.read_part_head(boundary);
if (part_head.ec) { if (part_head.ec) {
co_return part_head.ec; co_return part_head.ec;
} }
@ -1615,14 +1642,6 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
break; break;
} }
if (chunk_size == 0) {
// all finished, no more data
chunked_buf_.consume(CRCF.size());
data.status = 200;
data.eof = true;
break;
}
if (additional_size < size_t(chunk_size + 2)) { if (additional_size < size_t(chunk_size + 2)) {
// not a complete chunk, read left chunk data. // not a complete chunk, read left chunk data.
size_t size_to_read = chunk_size + 2 - additional_size; size_t size_to_read = chunk_size + 2 - additional_size;
@ -1633,6 +1652,13 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
} }
if (chunk_size == 0) {
// all finished, no more data
chunked_buf_.consume(chunked_buf_.size());
data.eof = true;
break;
}
data_ptr = asio::buffer_cast<const char *>(chunked_buf_.data()); data_ptr = asio::buffer_cast<const char *>(chunked_buf_.data());
if (ctx.stream) { if (ctx.stream) {
ec = co_await ctx.stream->async_write(data_ptr, chunk_size); ec = co_await ctx.stream->async_write(data_ptr, chunk_size);
@ -1656,6 +1682,11 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
co_return resp_data{ec, 404}; co_return resp_data{ec, 404};
} }
if (socket_->is_timeout_) {
auto ec = std::make_error_code(std::errc::timed_out);
co_return resp_data{ec, 404};
}
if (enable_tcp_no_delay_) { if (enable_tcp_no_delay_) {
std::error_code ec; std::error_code ec;
socket_->impl_.set_option(asio::ip::tcp::no_delay(true), ec); socket_->impl_.set_option(asio::ip::tcp::no_delay(true), ec);
@ -1767,15 +1798,12 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
co_return resp_data{{}, 200}; co_return resp_data{{}, 200};
} }
// this function must be called before async_ws_connect. async_simple::coro::Lazy<resp_data> async_read_ws() {
async_simple::coro::Lazy<void> async_read_ws() {
resp_data data{}; resp_data data{};
head_buf_.consume(head_buf_.size()); head_buf_.consume(head_buf_.size());
size_t header_size = 2; size_t header_size = 2;
std::shared_ptr sock = socket_; std::shared_ptr sock = socket_;
auto on_ws_msg = std::move(on_ws_msg_);
auto on_ws_close = std::move(on_ws_close_);
asio::streambuf &read_buf = sock->head_buf_; asio::streambuf &read_buf = sock->head_buf_;
bool has_init_ssl = false; bool has_init_ssl = false;
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
@ -1790,14 +1818,11 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
data.status = 404; data.status = 404;
if (sock->has_closed_) { if (sock->has_closed_) {
co_return; co_return data;
} }
close_socket(*sock); close_socket(*sock);
co_return data;
if (on_ws_msg)
on_ws_msg(data);
co_return;
} }
const char *data_ptr = asio::buffer_cast<const char *>(read_buf.data()); const char *data_ptr = asio::buffer_cast<const char *>(read_buf.data());
@ -1820,9 +1845,7 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
data.net_err = ec; data.net_err = ec;
data.status = 404; data.status = 404;
close_socket(*sock); close_socket(*sock);
if (on_ws_msg) co_return data;
on_ws_msg(data);
co_return;
} }
} }
@ -1834,21 +1857,37 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
} }
#ifdef CINATRA_ENABLE_GZIP
if (!is_close_frame && is_server_support_ws_deflate_ &&
enable_ws_deflate_) {
inflate_str_.clear();
if (!cinatra::gzip_codec::inflate({data_ptr, payload_len},
inflate_str_)) {
CINATRA_LOG_ERROR << "uncompuress data error";
data.status = 404;
data.net_err = std::make_error_code(std::errc::protocol_error);
co_return data;
}
data.status = 200;
data.resp_body = {inflate_str_.data(), inflate_str_.size()};
}
else {
#endif
data.status = 200; data.status = 200;
data.resp_body = {data_ptr, payload_len}; data.resp_body = {data_ptr, payload_len};
#ifdef CINATRA_ENABLE_GZIP
}
#endif
read_buf.consume(read_buf.size()); read_buf.consume(read_buf.size());
header_size = 2; header_size = 2;
if (is_close_frame) { if (is_close_frame) {
if (on_ws_close)
on_ws_close(data.resp_body);
std::string reason = "close"; std::string reason = "close";
auto close_str = ws.format_close_payload(close_code::normal, auto close_str = ws.format_close_payload(close_code::normal,
reason.data(), reason.size()); reason.data(), reason.size());
auto span = std::span<char>(close_str); auto span = std::span<char>(close_str);
std::string encode_header = ws.encode_frame(span, opcode::close, false); auto encode_header = ws.encode_frame(span, opcode::close, true);
std::vector<asio::const_buffer> buffers{asio::buffer(encode_header), std::vector<asio::const_buffer> buffers{asio::buffer(encode_header),
asio::buffer(reason)}; asio::buffer(reason)};
@ -1858,12 +1897,9 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
data.net_err = asio::error::eof; data.net_err = asio::error::eof;
data.status = 404; data.status = 404;
if (on_ws_msg) co_return data;
on_ws_msg(data);
co_return;
} }
if (on_ws_msg) co_return data;
on_ws_msg(data);
} }
} }
@ -1951,17 +1987,19 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
} }
async_simple::coro::Lazy<bool> timeout( async_simple::coro::Lazy<bool> timeout(
auto &timer, auto promise, std::chrono::steady_clock::duration duration, auto &timer, std::chrono::steady_clock::duration duration,
std::string msg) { std::string msg) {
auto watcher = std::weak_ptr(socket_);
timer.expires_after(duration); timer.expires_after(duration);
is_timeout_ = co_await timer.async_await(); auto is_timeout = co_await timer.async_await();
if (!is_timeout_) { if (!is_timeout) {
promise.setValue(async_simple::Unit());
co_return false; co_return false;
} }
if (auto socket = watcher.lock(); socket) {
socket_->is_timeout_ = true;
CINATRA_LOG_WARNING << msg << " timeout"; CINATRA_LOG_WARNING << msg << " timeout";
close_socket(*socket_); close_socket(*socket_);
promise.setValue(async_simple::Unit()); }
co_return true; co_return true;
} }
@ -2002,8 +2040,6 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
std::map<std::string, multipart_t> form_data_; std::map<std::string, multipart_t> form_data_;
size_t max_single_part_size_ = 1024 * 1024; size_t max_single_part_size_ = 1024 * 1024;
std::function<void(resp_data)> on_ws_msg_;
std::function<void(std::string_view)> on_ws_close_;
std::string ws_sec_key_; std::string ws_sec_key_;
std::string host_; std::string host_;
std::string port_; std::string port_;
@ -2016,22 +2052,25 @@ class coro_http_client : public std::enable_shared_from_this<coro_http_client> {
#endif #endif
std::string redirect_uri_; std::string redirect_uri_;
bool enable_follow_redirect_ = false; bool enable_follow_redirect_ = false;
bool is_timeout_ = false;
bool enable_timeout_ = false; bool enable_timeout_ = false;
std::chrono::steady_clock::duration conn_timeout_duration_ = std::chrono::steady_clock::duration conn_timeout_duration_ =
std::chrono::seconds(8); std::chrono::seconds(8);
std::chrono::steady_clock::duration req_timeout_duration_ = std::chrono::steady_clock::duration req_timeout_duration_ =
std::chrono::seconds(60); std::chrono::seconds(60);
bool enable_tcp_no_delay_ = false; bool enable_tcp_no_delay_ = true;
std::string resp_chunk_str_; std::string resp_chunk_str_;
std::span<char> out_buf_; std::span<char> out_buf_;
bool should_reset_ = false;
#ifdef CINATRA_ENABLE_GZIP
bool enable_ws_deflate_ = false;
bool is_server_support_ws_deflate_ = false;
std::string inflate_str_;
#endif
#ifdef BENCHMARK_TEST #ifdef BENCHMARK_TEST
std::string req_str_;
bool stop_bench_ = false; bool stop_bench_ = false;
size_t total_len_ = 0; size_t total_len_ = 0;
int read_fix_ = 0;
#endif #endif
}; };
} // namespace cinatra } // namespace cinatra

View File

@ -21,6 +21,14 @@
#include "sha1.hpp" #include "sha1.hpp"
#include "string_resize.hpp" #include "string_resize.hpp"
#include "websocket.hpp" #include "websocket.hpp"
#include "ylt/metric/counter.hpp"
#include "ylt/metric/gauge.hpp"
#include "ylt/metric/histogram.hpp"
#include "ylt/metric/metric.hpp"
#ifdef CINATRA_ENABLE_GZIP
#include "gzip.hpp"
#endif
#include "metric_conf.hpp"
#include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_file.hpp"
#include "ylt/coro_io/coro_io.hpp" #include "ylt/coro_io/coro_io.hpp"
@ -44,9 +52,14 @@ class coro_http_connection
request_(parser_, this), request_(parser_, this),
response_(this) { response_(this) {
buffers_.reserve(3); buffers_.reserve(3);
cinatra_metric_conf::server_total_fd_inc();
} }
~coro_http_connection() { close(); } ~coro_http_connection() {
cinatra_metric_conf::server_total_fd_dec();
close();
}
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
bool init_ssl(const std::string &cert_file, const std::string &key_file, bool init_ssl(const std::string &cert_file, const std::string &key_file,
@ -91,6 +104,8 @@ class coro_http_connection
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
bool has_shake = false; bool has_shake = false;
#endif #endif
std::chrono::system_clock::time_point start{};
std::chrono::system_clock::time_point mid{};
while (true) { while (true) {
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
if (use_ssl_ && !has_shake) { if (use_ssl_ && !has_shake) {
@ -110,13 +125,21 @@ class coro_http_connection
if (ec != asio::error::eof) { if (ec != asio::error::eof) {
CINATRA_LOG_WARNING << "read http header error: " << ec.message(); CINATRA_LOG_WARNING << "read http header error: " << ec.message();
} }
cinatra_metric_conf::server_failed_req_inc();
close(); close();
break; break;
} }
if (cinatra_metric_conf::enable_metric) {
start = std::chrono::system_clock::now();
cinatra_metric_conf::server_total_req_inc();
}
const char *data_ptr = asio::buffer_cast<const char *>(head_buf_.data()); const char *data_ptr = asio::buffer_cast<const char *>(head_buf_.data());
int head_len = parser_.parse_request(data_ptr, size, 0); int head_len = parser_.parse_request(data_ptr, size, 0);
if (head_len <= 0) { if (head_len <= 0) {
cinatra_metric_conf::server_failed_req_inc();
CINATRA_LOG_ERROR << "parse http header error"; CINATRA_LOG_ERROR << "parse http header error";
close(); close();
break; break;
@ -130,8 +153,19 @@ class coro_http_connection
if (type != content_type::chunked && type != content_type::multipart) { if (type != content_type::chunked && type != content_type::multipart) {
size_t body_len = parser_.body_len(); size_t body_len = parser_.body_len();
if (body_len == 0) { if (body_len == 0) {
if (cinatra_metric_conf::enable_metric) {
cinatra_metric_conf::server_total_recv_bytes_inc(head_len);
}
if (parser_.method() == "GET"sv) { if (parser_.method() == "GET"sv) {
if (request_.is_upgrade()) { if (request_.is_upgrade()) {
#ifdef CINATRA_ENABLE_GZIP
if (request_.is_support_compressed()) {
is_client_ws_compressed_ = true;
}
else {
is_client_ws_compressed_ = false;
}
#endif
// websocket // websocket
build_ws_handshake_head(); build_ws_handshake_head();
bool ok = co_await reply(true); // response ws handshake bool ok = co_await reply(true); // response ws handshake
@ -141,6 +175,16 @@ class coro_http_connection
} }
response_.set_delay(true); response_.set_delay(true);
} }
else {
if (cinatra_metric_conf::enable_metric) {
mid = std::chrono::system_clock::now();
double count =
std::chrono::duration_cast<std::chrono::microseconds>(mid -
start)
.count();
cinatra_metric_conf::server_read_latency_observe(count);
}
}
} }
} }
else if (body_len <= head_buf_.size()) { else if (body_len <= head_buf_.size()) {
@ -150,6 +194,7 @@ class coro_http_connection
memcpy(body_.data(), data_ptr, body_len); memcpy(body_.data(), data_ptr, body_len);
head_buf_.consume(head_buf_.size()); head_buf_.consume(head_buf_.size());
} }
cinatra_metric_conf::server_total_recv_bytes_inc(head_len + body_len);
} }
else { else {
size_t part_size = head_buf_.size(); size_t part_size = head_buf_.size();
@ -164,9 +209,22 @@ class coro_http_connection
size_to_read); size_to_read);
if (ec) { if (ec) {
CINATRA_LOG_ERROR << "async_read error: " << ec.message(); CINATRA_LOG_ERROR << "async_read error: " << ec.message();
cinatra_metric_conf::server_failed_req_inc();
close(); close();
break; break;
} }
else {
if (cinatra_metric_conf::enable_metric) {
cinatra_metric_conf::server_total_recv_bytes_inc(head_len +
body_len);
mid = std::chrono::system_clock::now();
double count =
std::chrono::duration_cast<std::chrono::microseconds>(mid -
start)
.count();
cinatra_metric_conf::server_read_latency_observe(count);
}
}
} }
} }
@ -191,6 +249,10 @@ class coro_http_connection
if (auto coro_handler = router_.get_coro_handler(key); coro_handler) { if (auto coro_handler = router_.get_coro_handler(key); coro_handler) {
co_await router_.route_coro(coro_handler, request_, response_, key); co_await router_.route_coro(coro_handler, request_, response_, key);
} }
else {
if (default_handler_) {
co_await default_handler_(request_, response_);
}
else { else {
bool is_exist = false; bool is_exist = false;
std::function<void(coro_http_request & req, std::function<void(coro_http_request & req,
@ -216,7 +278,8 @@ class coro_http_connection
coro_handler; coro_handler;
std::tie(is_coro_exist, coro_handler, request_.params_) = std::tie(is_coro_exist, coro_handler, request_.params_) =
router_.get_coro_router_tree()->get_coro(url_path, method_str); router_.get_coro_router_tree()->get_coro(url_path,
method_str);
if (is_coro_exist) { if (is_coro_exist) {
if (coro_handler) { if (coro_handler) {
@ -268,11 +331,23 @@ class coro_http_connection
} }
} }
} }
}
if (!response_.get_delay()) { if (!response_.get_delay()) {
if (head_buf_.size()) { if (head_buf_.size()) {
if (type == content_type::multipart) {
response_.set_status_and_content(
status_type::not_implemented,
"mutipart handler not implemented or incorrect implemented");
co_await reply();
close();
CINATRA_LOG_ERROR
<< "mutipart handler not implemented or incorrect implemented"
<< ec.message();
break;
}
else if (parser_.method()[0] != 'G' && parser_.method()[0] != 'H') {
// handle pipeling, only support GET and HEAD method now. // handle pipeling, only support GET and HEAD method now.
if (parser_.method()[0] != 'G' && parser_.method()[0] != 'H') {
response_.set_status_and_content(status_type::method_not_allowed, response_.set_status_and_content(status_type::method_not_allowed,
"method not allowed"); "method not allowed");
co_await reply(); co_await reply();
@ -326,6 +401,7 @@ class coro_http_connection
co_return; co_return;
} }
} }
head_buf_.consume(head_buf_.size());
} }
else { else {
handle_session_for_response(); handle_session_for_response();
@ -333,10 +409,20 @@ class coro_http_connection
} }
} }
if (cinatra_metric_conf::enable_metric) {
mid = std::chrono::system_clock::now();
double count =
std::chrono::duration_cast<std::chrono::microseconds>(mid - start)
.count();
cinatra_metric_conf::server_req_latency_observe(count);
}
response_.clear(); response_.clear();
request_.clear();
buffers_.clear(); buffers_.clear();
body_.clear(); body_.clear();
resp_str_.clear(); resp_str_.clear();
multi_buf_ = true;
if (need_shrink_every_time_) { if (need_shrink_every_time_) {
body_.shrink_to_fit(); body_.shrink_to_fit();
} }
@ -344,11 +430,35 @@ class coro_http_connection
} }
async_simple::coro::Lazy<bool> reply(bool need_to_bufffer = true) { async_simple::coro::Lazy<bool> reply(bool need_to_bufffer = true) {
// avoid duplicate reply if (response_.status() >= status_type::bad_request) {
if (need_to_bufffer) { if (cinatra_metric_conf::enable_metric)
response_.to_buffers(buffers_); cinatra_metric_conf::server_failed_req_inc();
} }
auto [ec, _] = co_await async_write(buffers_); std::error_code ec;
size_t size;
if (multi_buf_) {
if (need_to_bufffer) {
response_.to_buffers(buffers_, chunk_size_str_);
}
int64_t send_size = 0;
for (auto &buf : buffers_) {
send_size += buf.size();
}
if (cinatra_metric_conf::enable_metric) {
cinatra_metric_conf::server_total_send_bytes_inc(send_size);
}
std::tie(ec, size) = co_await async_write(buffers_);
}
else {
if (need_to_bufffer) {
response_.build_resp_str(resp_str_);
}
if (cinatra_metric_conf::enable_metric) {
cinatra_metric_conf::server_total_send_bytes_inc(resp_str_.size());
}
std::tie(ec, size) = co_await async_write(asio::buffer(resp_str_));
}
if (ec) { if (ec) {
CINATRA_LOG_ERROR << "async_write error: " << ec.message(); CINATRA_LOG_ERROR << "async_write error: " << ec.message();
close(); close();
@ -364,33 +474,25 @@ class coro_http_connection
} }
std::string local_address() { std::string local_address() {
if (has_closed_) { std::string addr;
return ""; set_address_impl(addr, false);
} return addr;
std::stringstream ss;
std::error_code ec;
ss << socket_.local_endpoint(ec);
if (ec) {
return "";
}
return ss.str();
} }
std::string remote_address() { std::string remote_address() {
static std::string remote_addr; if (!remote_addr_.empty()) {
if (has_closed_) { return remote_addr_;
return remote_addr; }
set_address_impl(remote_addr_);
return remote_addr_;
} }
std::stringstream ss; void set_multi_buf(bool r) { multi_buf_ = r; }
std::error_code ec;
ss << socket_.remote_endpoint(ec); void set_default_handler(
if (ec) { std::function<async_simple::coro::Lazy<void>(
return remote_addr; coro_http_request &, coro_http_response &)> &handler) {
} default_handler_ = handler;
remote_addr = ss.str();
return ss.str();
} }
async_simple::coro::Lazy<bool> write_data(std::string_view message) { async_simple::coro::Lazy<bool> write_data(std::string_view message) {
@ -423,7 +525,7 @@ class coro_http_connection
bool eof = false) { bool eof = false) {
response_.set_delay(true); response_.set_delay(true);
buffers_.clear(); buffers_.clear();
to_chunked_buffers(buffers_, chunked_data, eof); to_chunked_buffers(buffers_, chunk_size_str_, chunked_data, eof);
co_return co_await reply(false); co_return co_await reply(false);
} }
@ -510,13 +612,6 @@ class coro_http_connection
chunked_buf_.consume(size); chunked_buf_.consume(size);
if (chunk_size == 0) {
// all finished, no more data
chunked_buf_.consume(CRCF.size());
result.eof = true;
co_return result;
}
if (additional_size < size_t(chunk_size + 2)) { if (additional_size < size_t(chunk_size + 2)) {
// not a complete chunk, read left chunk data. // not a complete chunk, read left chunk data.
size_t size_to_read = chunk_size + 2 - additional_size; size_t size_to_read = chunk_size + 2 - additional_size;
@ -528,6 +623,13 @@ class coro_http_connection
} }
} }
if (chunk_size == 0) {
// all finished, no more data
chunked_buf_.consume(chunked_buf_.size());
result.eof = true;
co_return result;
}
data_ptr = asio::buffer_cast<const char *>(chunked_buf_.data()); data_ptr = asio::buffer_cast<const char *>(chunked_buf_.data());
result.data = std::string_view{data_ptr, (size_t)chunk_size}; result.data = std::string_view{data_ptr, (size_t)chunk_size};
chunked_buf_.consume(chunk_size + CRCF.size()); chunked_buf_.consume(chunk_size + CRCF.size());
@ -536,12 +638,29 @@ class coro_http_connection
} }
async_simple::coro::Lazy<std::error_code> write_websocket( async_simple::coro::Lazy<std::error_code> write_websocket(
std::string_view msg, opcode op = opcode::text) { std::string_view msg, opcode op = opcode::text, bool eof = true) {
auto header = ws_.format_header(msg.length(), op);
std::vector<asio::const_buffer> buffers; std::vector<asio::const_buffer> buffers;
std::string_view header;
#ifdef CINATRA_ENABLE_GZIP
std::string dest_buf;
if (is_client_ws_compressed_ && msg.size() > 0) {
if (!cinatra::gzip_codec::deflate(msg, dest_buf)) {
CINATRA_LOG_ERROR << "compuress data error, data: " << msg;
co_return std::make_error_code(std::errc::protocol_error);
}
header = ws_.encode_ws_header(dest_buf.length(), op, eof, true, false);
buffers.push_back(asio::buffer(header));
buffers.push_back(asio::buffer(dest_buf));
}
else {
#endif
header = ws_.encode_ws_header(msg.length(), op, eof, false, false);
buffers.push_back(asio::buffer(header)); buffers.push_back(asio::buffer(header));
buffers.push_back(asio::buffer(msg)); buffers.push_back(asio::buffer(msg));
#ifdef CINATRA_ENABLE_GZIP
}
#endif
auto [ec, sz] = co_await async_write(buffers); auto [ec, sz] = co_await async_write(buffers);
co_return ec; co_return ec;
} }
@ -598,8 +717,27 @@ class coro_http_connection
break; break;
case cinatra::ws_frame_type::WS_TEXT_FRAME: case cinatra::ws_frame_type::WS_TEXT_FRAME:
case cinatra::ws_frame_type::WS_BINARY_FRAME: { case cinatra::ws_frame_type::WS_BINARY_FRAME: {
#ifdef CINATRA_ENABLE_GZIP
if (is_client_ws_compressed_) {
inflate_str_.clear();
if (!cinatra::gzip_codec::inflate(
{payload.data(), payload.size()}, inflate_str_)) {
CINATRA_LOG_ERROR << "uncompuress data error";
result.ec = std::make_error_code(std::errc::protocol_error);
break;
}
result.eof = true;
result.data = {inflate_str_.data(), inflate_str_.size()};
break;
}
else {
#endif
result.eof = true; result.eof = true;
result.data = {payload.data(), payload.size()}; result.data = {payload.data(), payload.size()};
break;
#ifdef CINATRA_ENABLE_GZIP
}
#endif
} break; } break;
case cinatra::ws_frame_type::WS_CLOSE_FRAME: { case cinatra::ws_frame_type::WS_CLOSE_FRAME: {
close_frame close_frame = close_frame close_frame =
@ -609,7 +747,6 @@ class coro_http_connection
std::string close_msg = ws_.format_close_payload( std::string close_msg = ws_.format_close_payload(
close_code::normal, close_frame.message, close_frame.length); close_code::normal, close_frame.message, close_frame.length);
auto header = ws_.format_header(close_msg.length(), opcode::close);
co_await write_websocket(close_msg, opcode::close); co_await write_websocket(close_msg, opcode::close);
close(); close();
@ -726,7 +863,7 @@ class coro_http_connection
return last_rwtime_; return last_rwtime_;
} }
auto &get_executor() { return *executor_; } auto get_executor() { return executor_; }
void close(bool need_cb = true) { void close(bool need_cb = true) {
if (has_closed_) { if (has_closed_) {
@ -760,13 +897,10 @@ class coro_http_connection
private: private:
bool check_keep_alive() { bool check_keep_alive() {
bool keep_alive = true; if (parser_.has_close()) {
auto val = request_.get_header_value("connection"); return false;
if (!val.empty() && iequal0(val, "close")) {
keep_alive = false;
} }
return true;
return keep_alive;
} }
void build_ws_handshake_head() { void build_ws_handshake_head() {
@ -789,14 +923,37 @@ class coro_http_connection
response_.add_header("Connection", "Upgrade"); response_.add_header("Connection", "Upgrade");
response_.add_header("Sec-WebSocket-Accept", std::string(accept_key, 28)); response_.add_header("Sec-WebSocket-Accept", std::string(accept_key, 28));
auto protocal_str = request_.get_header_value("sec-websocket-protocol"); auto protocal_str = request_.get_header_value("sec-websocket-protocol");
#ifdef CINATRA_ENABLE_GZIP
if (is_client_ws_compressed_) {
response_.add_header("Sec-WebSocket-Extensions",
"permessage-deflate; client_no_context_takeover");
}
#endif
if (!protocal_str.empty()) { if (!protocal_str.empty()) {
response_.add_header("Sec-WebSocket-Protocol", std::string(protocal_str)); response_.add_header("Sec-WebSocket-Protocol", std::string(protocal_str));
} }
} }
void set_address_impl(std::string &address, bool remote = true) {
if (has_closed_) {
return;
}
std::error_code ec;
auto pt = remote ? socket_.remote_endpoint(ec) : socket_.local_endpoint(ec);
if (ec) {
return;
}
address = pt.address().to_string(ec);
if (ec) {
return;
}
address.append(":").append(std::to_string(pt.port()));
}
private: private:
friend class multipart_reader_t<coro_http_connection>; friend class multipart_reader_t<coro_http_connection>;
async_simple::Executor *executor_; coro_io::ExecutorWrapper<> *executor_;
asio::ip::tcp::socket socket_; asio::ip::tcp::socket socket_;
coro_http_router &router_; coro_http_router &router_;
asio::streambuf head_buf_; asio::streambuf head_buf_;
@ -815,6 +972,11 @@ class coro_http_connection
uint64_t max_part_size_ = 8 * 1024 * 1024; uint64_t max_part_size_ = 8 * 1024 * 1024;
std::string resp_str_; std::string resp_str_;
#ifdef CINATRA_ENABLE_GZIP
bool is_client_ws_compressed_ = false;
std::string inflate_str_;
#endif
websocket ws_; websocket ws_;
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
std::unique_ptr<asio::ssl::context> ssl_ctx_ = nullptr; std::unique_ptr<asio::ssl::context> ssl_ctx_ = nullptr;
@ -822,5 +984,11 @@ class coro_http_connection
bool use_ssl_ = false; bool use_ssl_ = false;
#endif #endif
bool need_shrink_every_time_ = false; bool need_shrink_every_time_ = false;
bool multi_buf_ = true;
std::function<async_simple::coro::Lazy<void>(coro_http_request &,
coro_http_response &)>
default_handler_ = nullptr;
std::string chunk_size_str_;
std::string remote_addr_;
}; };
} // namespace cinatra } // namespace cinatra

View File

@ -2,8 +2,10 @@
#include <any> #include <any>
#include <charconv> #include <charconv>
#include <initializer_list>
#include <optional> #include <optional>
#include <regex> #include <regex>
#include <string>
#include "async_simple/coro/Lazy.h" #include "async_simple/coro/Lazy.h"
#include "define.h" #include "define.h"
@ -125,6 +127,8 @@ class coro_http_request {
const auto &get_queries() const { return parser_.queries(); } const auto &get_queries() const { return parser_.queries(); }
std::string_view full_url() { return parser_.full_url(); }
void set_body(std::string &body) { void set_body(std::string &body) {
body_ = body; body_ = body;
auto type = get_content_type(); auto type = get_content_type();
@ -180,23 +184,25 @@ class coro_http_request {
if (content_type.empty()) { if (content_type.empty()) {
return {}; return {};
} }
return content_type.substr(content_type.rfind("=") + 1);
auto pos = content_type.rfind("=");
if (pos == std::string_view::npos) {
return "";
}
return content_type.substr(pos + 1);
} }
coro_http_connection *get_conn() { return conn_; } coro_http_connection *get_conn() { return conn_; }
bool is_upgrade() { bool is_upgrade() {
auto h = get_header_value("Connection"); if (!parser_.has_upgrade())
if (h.empty())
return false; return false;
auto u = get_header_value("Upgrade"); auto u = get_header_value("Upgrade");
if (u.empty()) if (u.empty())
return false; return false;
if (h != UPGRADE)
return false;
if (u != WEBSOCKET) if (u != WEBSOCKET)
return false; return false;
@ -208,24 +214,29 @@ class coro_http_request {
return true; return true;
} }
void set_aspect_data(const std::string &&key, const std::any &data) { bool is_support_compressed() {
aspect_data_[key] = data; auto extension_str = get_header_value("Sec-WebSocket-Extensions");
if (extension_str.find("permessage-deflate") != std::string::npos) {
return true;
}
return false;
} }
template <typename T> void set_aspect_data(std::string data) {
std::optional<T> get_aspect_data(const std::string &&key) { aspect_data_.push_back(std::move(data));
auto it = aspect_data_.find(key);
if (it == aspect_data_.end()) {
return std::optional<T>{};
} }
try { void set_aspect_data(std::vector<std::string> data) {
return std::any_cast<T>(it->second); // throws aspect_data_ = std::move(data);
} catch (const std::bad_any_cast &e) {
return std::optional<T>{};
} }
template <typename... Args>
void set_aspect_data(Args... args) {
(aspect_data_.push_back(std::move(args)), ...);
} }
std::vector<std::string> &get_aspect_data() { return aspect_data_; }
std::unordered_map<std::string_view, std::string_view> get_cookies( std::unordered_map<std::string_view, std::string_view> get_cookies(
std::string_view cookie_str) const { std::string_view cookie_str) const {
auto cookies = get_cookies_map(cookie_str); auto cookies = get_cookies_map(cookie_str);
@ -259,6 +270,12 @@ class coro_http_request {
} }
bool has_session() { return !cached_session_id_.empty(); } bool has_session() { return !cached_session_id_.empty(); }
void clear() {
body_ = {};
if (!aspect_data_.empty()) {
aspect_data_.clear();
}
}
std::unordered_map<std::string, std::string> params_; std::unordered_map<std::string, std::string> params_;
std::smatch matches_; std::smatch matches_;
@ -267,8 +284,8 @@ class coro_http_request {
http_parser &parser_; http_parser &parser_;
std::string_view body_; std::string_view body_;
coro_http_connection *conn_; coro_http_connection *conn_;
bool is_websocket_; bool is_websocket_ = false;
std::unordered_map<std::string, std::any> aspect_data_; std::vector<std::string> aspect_data_;
std::string cached_session_id_; std::string cached_session_id_;
}; };
} // namespace cinatra } // namespace cinatra

View File

@ -12,6 +12,10 @@
#include "async_simple/coro/SyncAwait.h" #include "async_simple/coro/SyncAwait.h"
#include "cookie.hpp" #include "cookie.hpp"
#include "define.h" #include "define.h"
#ifdef CINATRA_ENABLE_GZIP
#include "gzip.hpp"
#endif
#include "picohttpparser.h"
#include "response_cv.hpp" #include "response_cv.hpp"
#include "time_util.hpp" #include "time_util.hpp"
#include "utils.hpp" #include "utils.hpp"
@ -46,9 +50,40 @@ class coro_http_response {
content_ = std::move(content); content_ = std::move(content);
has_set_content_ = true; has_set_content_ = true;
} }
void set_status_and_content(status_type status, std::string content = "") { void set_status_and_content(
status_type status, std::string content = "",
content_encoding encoding = content_encoding::none) {
set_status_and_content_view(status, std::move(content), encoding, false);
}
template <typename String>
void set_status_and_content_view(
status_type status, String content = "",
content_encoding encoding = content_encoding::none, bool is_view = true) {
status_ = status; status_ = status;
#ifdef CINATRA_ENABLE_GZIP
if (encoding == content_encoding::gzip) {
std::string encode_str;
bool r = gzip_codec::compress(content, encode_str, true);
if (!r) {
set_status_and_content(status_type::internal_server_error,
"gzip compress error");
}
else {
add_header("Content-Encoding", "gzip");
set_content(std::move(encode_str));
}
}
else
#endif
{
if (is_view) {
content_view_ = content;
}
else {
content_ = std::move(content); content_ = std::move(content);
}
}
has_set_content_ = true; has_set_content_ = true;
} }
void set_delay(bool r) { delay_ = r; } void set_delay(bool r) { delay_ = r; }
@ -61,11 +96,16 @@ class coro_http_response {
status_type status() { return status_; } status_type status() { return status_; }
std::string_view content() { return content_; } std::string_view content() { return content_; }
size_t content_size() { return content_.size(); }
void add_header(auto k, auto v) { void add_header(auto k, auto v) {
resp_headers_.emplace_back(resp_header{std::move(k), std::move(v)}); resp_headers_.emplace_back(resp_header{std::move(k), std::move(v)});
} }
void add_header_span(std::span<http_header> resp_headers) {
resp_header_span_ = resp_headers;
}
void set_keepalive(bool r) { keepalive_ = r; } void set_keepalive(bool r) { keepalive_ = r; }
void need_date_head(bool r) { need_date_ = r; } void need_date_head(bool r) { need_date_ = r; }
@ -75,16 +115,15 @@ class coro_http_response {
std::string_view get_boundary() { return boundary_; } std::string_view get_boundary() { return boundary_; }
void to_buffers(std::vector<asio::const_buffer> &buffers) { void to_buffers(std::vector<asio::const_buffer> &buffers,
std::string &size_str) {
buffers.push_back(asio::buffer(to_http_status_string(status_))); buffers.push_back(asio::buffer(to_http_status_string(status_)));
build_resp_head(buffers); build_resp_head(buffers);
if (!content_.empty()) { if (!content_.empty()) {
if (fmt_type_ == format_type::chunked) { handle_content(buffers, size_str, content_);
to_chunked_buffers(buffers, content_, true);
}
else {
buffers.push_back(asio::buffer(content_));
} }
else if (!content_view_.empty()) {
handle_content(buffers, size_str, content_view_);
} }
} }
@ -92,13 +131,9 @@ class coro_http_response {
resp_str.append(to_http_status_string(status_)); resp_str.append(to_http_status_string(status_));
bool has_len = false; bool has_len = false;
bool has_host = false; bool has_host = false;
for (auto &[k, v] : resp_headers_) { check_header(resp_headers_, has_len, has_host);
if (k == "Host") { if (!resp_header_span_.empty()) {
has_host = true; check_header(resp_header_span_, has_len, has_host);
}
if (k == "Content-Length") {
has_len = true;
}
} }
if (!has_host) { if (!has_host) {
@ -138,31 +173,50 @@ class coro_http_response {
: resp_str.append(CONN_CLOSE_SV); : resp_str.append(CONN_CLOSE_SV);
} }
if (!content_type_.empty()) { append_header_str(resp_str, resp_headers_);
resp_str.append(content_type_);
if (!resp_header_span_.empty()) {
append_header_str(resp_str, resp_header_span_);
} }
for (auto &[k, v] : resp_headers_) { resp_str.append(CRCF);
if (content_view_.empty()) {
resp_str.append(content_);
}
else {
resp_str.append(content_view_);
}
}
void append_header_str(auto &resp_str, auto &resp_headers) {
for (auto &[k, v] : resp_headers) {
resp_str.append(k); resp_str.append(k);
resp_str.append(COLON_SV); resp_str.append(COLON_SV);
resp_str.append(v); resp_str.append(v);
resp_str.append(CRCF); resp_str.append(CRCF);
} }
}
resp_str.append(CRCF); void check_header(auto &resp_headers, bool &has_len, bool &has_host) {
resp_str.append(content_); for (auto &[k, v] : resp_headers) {
if (k == "Server") {
has_host = true;
}
else if (k == "Content-Length") {
has_len = true;
}
else if (k == "Date") {
need_date_ = false;
}
}
} }
void build_resp_head(std::vector<asio::const_buffer> &buffers) { void build_resp_head(std::vector<asio::const_buffer> &buffers) {
bool has_len = false; bool has_len = false;
bool has_host = false; bool has_host = false;
for (auto &[k, v] : resp_headers_) { check_header(resp_headers_, has_len, has_host);
if (k == "Host") { if (!resp_header_span_.empty()) {
has_host = true; check_header(resp_header_span_, has_len, has_host);
}
if (k == "Content-Length") {
has_len = true;
}
} }
if (!has_host) { if (!has_host) {
@ -186,11 +240,12 @@ class coro_http_response {
} }
if (!content_.empty()) { if (!content_.empty()) {
auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content_.size()); if (!has_len)
buffers.emplace_back(asio::buffer(CONTENT_LENGTH_SV)); handle_content_len(buffers, content_);
buffers.emplace_back( }
asio::buffer(std::string_view(buf_, std::distance(buf_, ptr)))); else if (!content_view_.empty()) {
buffers.emplace_back(asio::buffer(CRCF)); if (!has_len)
handle_content_len(buffers, content_view_);
} }
else { else {
if (!has_len && boundary_.empty()) if (!has_len && boundary_.empty())
@ -214,14 +269,22 @@ class coro_http_response {
buffers.emplace_back(asio::buffer(content_type_)); buffers.emplace_back(asio::buffer(content_type_));
} }
for (auto &[k, v] : resp_headers_) { append_header(buffers, resp_headers_);
if (!resp_header_span_.empty()) {
append_header(buffers, resp_header_span_);
}
buffers.emplace_back(asio::buffer(CRCF));
}
void append_header(auto &buffers, auto &resp_headers) {
for (auto &[k, v] : resp_headers) {
buffers.emplace_back(asio::buffer(k)); buffers.emplace_back(asio::buffer(k));
buffers.emplace_back(asio::buffer(COLON_SV)); buffers.emplace_back(asio::buffer(COLON_SV));
buffers.emplace_back(asio::buffer(v)); buffers.emplace_back(asio::buffer(v));
buffers.emplace_back(asio::buffer(CRCF)); buffers.emplace_back(asio::buffer(CRCF));
} }
buffers.emplace_back(asio::buffer(CRCF));
} }
coro_http_connection *get_conn() { return conn_; } coro_http_connection *get_conn() { return conn_; }
@ -248,7 +311,33 @@ class coro_http_response {
cookies_[cookie.get_name()] = cookie; cookies_[cookie.get_name()] = cookie;
} }
void redirect(const std::string &url, bool is_forever = false) {
add_header("Location", url);
is_forever == false
? set_status_and_content(status_type::moved_temporarily)
: set_status_and_content(status_type::moved_permanently);
}
private: private:
void handle_content(std::vector<asio::const_buffer> &buffers,
std::string &size_str, std::string_view content) {
if (fmt_type_ == format_type::chunked) {
to_chunked_buffers(buffers, size_str, content, true);
}
else {
buffers.push_back(asio::buffer(content));
}
}
void handle_content_len(std::vector<asio::const_buffer> &buffers,
std::string_view content) {
auto [ptr, ec] = std::to_chars(buf_, buf_ + 32, content.size());
buffers.emplace_back(asio::buffer(CONTENT_LENGTH_SV));
buffers.emplace_back(
asio::buffer(std::string_view(buf_, std::distance(buf_, ptr))));
buffers.emplace_back(asio::buffer(CRCF));
}
status_type status_; status_type status_;
format_type fmt_type_; format_type fmt_type_;
std::string content_; std::string content_;
@ -256,6 +345,7 @@ class coro_http_response {
bool delay_; bool delay_;
char buf_[32]; char buf_[32];
std::vector<resp_header> resp_headers_; std::vector<resp_header> resp_headers_;
std::span<http_header> resp_header_span_;
coro_http_connection *conn_; coro_http_connection *conn_;
std::string boundary_; std::string boundary_;
bool has_set_content_ = false; bool has_set_content_ = false;
@ -263,5 +353,6 @@ class coro_http_response {
bool need_date_ = true; bool need_date_ = true;
std::unordered_map<std::string, cookie> cookies_; std::unordered_map<std::string, cookie> cookies_;
std::string_view content_type_; std::string_view content_type_;
std::string_view content_view_;
}; };
} // namespace cinatra } // namespace cinatra

View File

@ -1,6 +1,4 @@
#pragma once #pragma once
#include <async_simple/coro/Lazy.h>
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
#include <set> #include <set>
@ -17,34 +15,35 @@
#include "ylt/util/type_traits.h" #include "ylt/util/type_traits.h"
namespace cinatra { namespace cinatra {
template <template <typename...> class U, typename T> template <class, class = void>
struct is_template_instant_of : std::false_type {}; struct has_before : std::false_type {};
template <template <typename...> class U, typename... args> template <class T>
struct is_template_instant_of<U, U<args...>> : std::true_type {}; struct has_before<T, std::void_t<decltype(std::declval<T>().before(
std::declval<coro_http_request&>(),
std::declval<coro_http_response&>()))>>
: std::true_type {};
template <typename T> template <class, class = void>
constexpr inline bool is_lazy_v = struct has_after : std::false_type {};
is_template_instant_of<async_simple::coro::Lazy,
std::remove_cvref_t<T>>::value;
struct base_aspect { template <class T>
virtual bool before(coro_http_request& req, coro_http_response& resp) { struct has_after<T, std::void_t<decltype(std::declval<T>().after(
return true; std::declval<coro_http_request&>(),
} std::declval<coro_http_response&>()))>>
: std::true_type {};
virtual bool after(coro_http_request& req, coro_http_response& resp) { template <class T>
return true; constexpr bool has_before_v = has_before<T>::value;
}
}; template <class T>
constexpr bool has_after_v = has_after<T>::value;
class coro_http_router { class coro_http_router {
public: public:
// eg: "GET hello/" as a key // eg: "GET hello/" as a key
template <http_method method, typename Func> template <http_method method, typename Func, typename... Aspects>
void set_http_handler( void set_http_handler(std::string key, Func handler, Aspects&&... asps) {
std::string key, Func handler,
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
constexpr auto method_name = cinatra::method_name(method); constexpr auto method_name = cinatra::method_name(method);
std::string whole_str; std::string whole_str;
whole_str.append(method_name).append(" ").append(key); whole_str.append(method_name).append(" ").append(key);
@ -52,14 +51,32 @@ class coro_http_router {
// hold keys to make sure map_handles_ key is // hold keys to make sure map_handles_ key is
// std::string_view, avoid memcpy when route // std::string_view, avoid memcpy when route
using return_type = typename util::function_traits<Func>::return_type; using return_type = typename util::function_traits<Func>::return_type;
if constexpr (is_lazy_v<return_type>) { if constexpr (coro_io::is_lazy_v<return_type>) {
std::function<async_simple::coro::Lazy<void>(coro_http_request & req,
coro_http_response & resp)>
http_handler;
if constexpr (sizeof...(Aspects) > 0) {
http_handler = [this, handler = std::move(handler),
... asps = std::forward<Aspects>(asps)](
coro_http_request& req,
coro_http_response& resp) mutable
-> async_simple::coro::Lazy<void> {
bool ok = true;
(do_before(asps, req, resp, ok), ...);
if (ok) {
co_await handler(req, resp);
}
(do_after(asps, req, resp, ok), ...);
};
}
else {
http_handler = std::move(handler);
}
if (whole_str.find(":") != std::string::npos) { if (whole_str.find(":") != std::string::npos) {
std::vector<std::string> coro_method_names = {}; std::string method_str(method_name);
std::string coro_method_str; coro_router_tree_->coro_insert(whole_str, std::move(http_handler),
coro_method_str.append(method_name); method_str);
coro_method_names.push_back(coro_method_str);
coro_router_tree_->coro_insert(key, std::move(handler),
coro_method_names);
} }
else { else {
if (whole_str.find("{") != std::string::npos || if (whole_str.find("{") != std::string::npos ||
@ -71,7 +88,7 @@ class coro_http_router {
} }
coro_regex_handles_.emplace_back(std::regex(pattern), coro_regex_handles_.emplace_back(std::regex(pattern),
std::move(handler)); std::move(http_handler));
} }
else { else {
auto [it, ok] = coro_keys_.emplace(std::move(whole_str)); auto [it, ok] = coro_keys_.emplace(std::move(whole_str));
@ -79,21 +96,33 @@ class coro_http_router {
CINATRA_LOG_WARNING << key << " has already registered."; CINATRA_LOG_WARNING << key << " has already registered.";
return; return;
} }
coro_handles_.emplace(*it, std::move(handler)); coro_handles_.emplace(*it, std::move(http_handler));
if (!aspects.empty()) {
has_aspects_ = true;
aspects_.emplace(*it, std::move(aspects));
}
} }
} }
} }
else { else {
std::function<void(coro_http_request & req, coro_http_response & resp)>
http_handler;
if constexpr (sizeof...(Aspects) > 0) {
http_handler = [this, handler = std::move(handler),
... asps = std::forward<Aspects>(asps)](
coro_http_request& req,
coro_http_response& resp) mutable {
bool ok = true;
(do_before(asps, req, resp, ok), ...);
if (ok) {
handler(req, resp);
}
(do_after(asps, req, resp, ok), ...);
};
}
else {
http_handler = std::move(handler);
}
if (whole_str.find(':') != std::string::npos) { if (whole_str.find(':') != std::string::npos) {
std::vector<std::string> method_names = {}; std::string method_str(method_name);
std::string method_str; router_tree_->insert(whole_str, std::move(http_handler), method_str);
method_str.append(method_name);
method_names.push_back(method_str);
router_tree_->insert(whole_str, std::move(handler), method_names);
} }
else if (whole_str.find("{") != std::string::npos || else if (whole_str.find("{") != std::string::npos ||
whole_str.find(")") != std::string::npos) { whole_str.find(")") != std::string::npos) {
@ -103,7 +132,8 @@ class coro_http_router {
replace_all(pattern, "{}", "([^/]+)"); replace_all(pattern, "{}", "([^/]+)");
} }
regex_handles_.emplace_back(std::regex(pattern), std::move(handler)); regex_handles_.emplace_back(std::regex(pattern),
std::move(http_handler));
} }
else { else {
auto [it, ok] = keys_.emplace(std::move(whole_str)); auto [it, ok] = keys_.emplace(std::move(whole_str));
@ -111,13 +141,34 @@ class coro_http_router {
CINATRA_LOG_WARNING << key << " has already registered."; CINATRA_LOG_WARNING << key << " has already registered.";
return; return;
} }
map_handles_.emplace(*it, std::move(handler)); map_handles_.emplace(*it, std::move(http_handler));
if (!aspects.empty()) {
has_aspects_ = true;
aspects_.emplace(*it, std::move(aspects));
} }
} }
} }
template <typename T>
void do_before(T& aspect, coro_http_request& req, coro_http_response& resp,
bool& ok) {
if constexpr (has_before_v<T>) {
if (!ok) {
return;
}
ok = aspect.before(req, resp);
}
else {
ok = true;
}
}
template <typename T>
void do_after(T& aspect, coro_http_request& req, coro_http_response& resp,
bool& ok) {
if constexpr (has_after_v<T>) {
ok = aspect.after(req, resp);
}
else {
ok = true;
}
} }
std::function<void(coro_http_request& req, coro_http_response& resp)>* std::function<void(coro_http_request& req, coro_http_response& resp)>*
@ -139,19 +190,7 @@ class coro_http_router {
void route(auto handler, auto& req, auto& resp, std::string_view key) { void route(auto handler, auto& req, auto& resp, std::string_view key) {
try { try {
if (has_aspects_) {
auto [it, ok] = handle_aspects(req, resp, key, true);
if (!ok) {
return;
}
(*handler)(req, resp); (*handler)(req, resp);
if (it != aspects_.end()) {
handle_aspects(req, resp, it->second, false);
}
}
else {
(*handler)(req, resp);
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
CINATRA_LOG_WARNING << "exception in business function, reason: " CINATRA_LOG_WARNING << "exception in business function, reason: "
<< e.what(); << e.what();
@ -165,19 +204,7 @@ class coro_http_router {
async_simple::coro::Lazy<void> route_coro(auto handler, auto& req, auto& resp, async_simple::coro::Lazy<void> route_coro(auto handler, auto& req, auto& resp,
std::string_view key) { std::string_view key) {
try { try {
if (has_aspects_) {
auto [it, ok] = handle_aspects(req, resp, key, true);
if (!ok) {
co_return;
}
co_await (*handler)(req, resp); co_await (*handler)(req, resp);
if (it != aspects_.end()) {
handle_aspects(req, resp, it->second, false);
}
}
else {
co_await (*handler)(req, resp);
}
} catch (const std::exception& e) { } catch (const std::exception& e) {
CINATRA_LOG_WARNING << "exception in business function, reason: " CINATRA_LOG_WARNING << "exception in business function, reason: "
<< e.what(); << e.what();
@ -202,38 +229,6 @@ class coro_http_router {
const auto& get_regex_handlers() { return regex_handles_; } const auto& get_regex_handlers() { return regex_handles_; }
bool handle_aspects(auto& req, auto& resp, auto& aspects, bool before) {
bool r = true;
for (auto& aspect : aspects) {
if (before) {
r = aspect->before(req, resp);
}
else {
r = aspect->after(req, resp);
}
if (!r) {
break;
}
}
return r;
}
auto handle_aspects(auto& req, auto& resp, std::string_view key,
bool before) {
decltype(aspects_.begin()) it;
if (it = aspects_.find(key); it != aspects_.end()) {
auto& aspects = it->second;
bool r = handle_aspects(req, resp, aspects, before);
if (!r) {
return std::make_pair(aspects_.end(), false);
}
}
return std::make_pair(it, true);
}
void handle_after() {}
private: private:
std::set<std::string> keys_; std::set<std::string> keys_;
std::unordered_map< std::unordered_map<
@ -262,10 +257,5 @@ class coro_http_router {
std::regex, std::function<async_simple::coro::Lazy<void>( std::regex, std::function<async_simple::coro::Lazy<void>(
coro_http_request& req, coro_http_response& resp)>>> coro_http_request& req, coro_http_response& resp)>>>
coro_regex_handles_; coro_regex_handles_;
std::unordered_map<std::string_view,
std::vector<std::shared_ptr<base_aspect>>>
aspects_;
bool has_aspects_ = false;
}; };
} // namespace cinatra } // namespace cinatra

View File

@ -1,21 +1,17 @@
#pragma once #pragma once
#include <asio/dispatch.hpp> #include "cinatra/coro_http_client.hpp"
#include <cstdint>
#include <mutex>
#include <type_traits>
#include "asio/streambuf.hpp"
#include "async_simple/Promise.h"
#include "async_simple/coro/Lazy.h"
#include "cinatra/coro_http_response.hpp" #include "cinatra/coro_http_response.hpp"
#include "cinatra/coro_http_router.hpp" #include "cinatra/coro_http_router.hpp"
#include "cinatra/define.h"
#include "cinatra/mime_types.hpp" #include "cinatra/mime_types.hpp"
#include "cinatra_log_wrapper.hpp" #include "cinatra_log_wrapper.hpp"
#include "coro_http_connection.hpp" #include "coro_http_connection.hpp"
#include "ylt/coro_io/channel.hpp"
#include "ylt/coro_io/coro_file.hpp" #include "ylt/coro_io/coro_file.hpp"
#include "ylt/coro_io/coro_io.hpp" #include "ylt/coro_io/coro_io.hpp"
#include "ylt/coro_io/io_context_pool.hpp" #include "ylt/coro_io/io_context_pool.hpp"
#include "ylt/metric/metric.hpp"
namespace cinatra { namespace cinatra {
enum class file_resp_format_type { enum class file_resp_format_type {
@ -24,14 +20,37 @@ enum class file_resp_format_type {
}; };
class coro_http_server { class coro_http_server {
public: public:
coro_http_server(asio::io_context &ctx, unsigned short port) coro_http_server(asio::io_context &ctx, unsigned short port,
: out_ctx_(&ctx), port_(port), acceptor_(ctx), check_timer_(ctx) {} std::string address = "0.0.0.0")
: out_ctx_(&ctx), port_(port), acceptor_(ctx), check_timer_(ctx) {
init_address(std::move(address));
}
coro_http_server(size_t thread_num, unsigned short port) coro_http_server(asio::io_context &ctx,
: pool_(std::make_unique<coro_io::io_context_pool>(thread_num)), std::string address /* = "0.0.0.0:9001" */)
: out_ctx_(&ctx), acceptor_(ctx), check_timer_(ctx) {
init_address(std::move(address));
}
coro_http_server(size_t thread_num, unsigned short port,
std::string address = "0.0.0.0", bool cpu_affinity = false)
: pool_(std::make_unique<coro_io::io_context_pool>(thread_num,
cpu_affinity)),
port_(port), port_(port),
acceptor_(pool_->get_executor()->get_asio_executor()), acceptor_(pool_->get_executor()->get_asio_executor()),
check_timer_(pool_->get_executor()->get_asio_executor()) {} check_timer_(pool_->get_executor()->get_asio_executor()) {
init_address(std::move(address));
}
coro_http_server(size_t thread_num,
std::string address /* = "0.0.0.0:9001" */,
bool cpu_affinity = false)
: pool_(std::make_unique<coro_io::io_context_pool>(thread_num,
cpu_affinity)),
acceptor_(pool_->get_executor()->get_asio_executor()),
check_timer_(pool_->get_executor()->get_asio_executor()) {
init_address(std::move(address));
}
~coro_http_server() { ~coro_http_server() {
CINATRA_LOG_INFO << "coro_http_server will quit"; CINATRA_LOG_INFO << "coro_http_server will quit";
@ -51,29 +70,30 @@ class coro_http_server {
#endif #endif
// only call once, not thread safe. // only call once, not thread safe.
std::errc sync_start() noexcept { std::error_code sync_start() noexcept {
auto ret = async_start(); auto ret = async_start();
ret.wait(); ret.wait();
return ret.value(); return ret.value();
} }
// only call once, not thread safe. // only call once, not thread safe.
async_simple::Future<std::errc> async_start() { async_simple::Future<std::error_code> async_start() {
auto ec = listen(); errc_ = listen();
async_simple::Promise<std::errc> promise; async_simple::Promise<std::error_code> promise;
auto future = promise.getFuture(); auto future = promise.getFuture();
if (ec == std::errc{}) { if (!errc_) {
if (out_ctx_ == nullptr) { if (out_ctx_ == nullptr) {
thd_ = std::thread([this] { thd_ = std::thread([this] {
pool_->run(); pool_->run();
}); });
} }
accept().start([p = std::move(promise)](auto &&res) mutable { accept().start([p = std::move(promise), this](auto &&res) mutable {
if (res.hasError()) { if (res.hasError()) {
p.setValue(std::errc::io_error); errc_ = std::make_error_code(std::errc::io_error);
p.setValue(errc_);
} }
else { else {
p.setValue(res.value()); p.setValue(res.value());
@ -81,7 +101,7 @@ class coro_http_server {
}); });
} }
else { else {
promise.setValue(ec); promise.setValue(errc_);
} }
return future; return future;
@ -124,45 +144,159 @@ class coro_http_server {
// call it after server async_start or sync_start. // call it after server async_start or sync_start.
uint16_t port() const { return port_; } uint16_t port() const { return port_; }
template <http_method... method, typename Func> template <http_method... method, typename Func, typename... Aspects>
void set_http_handler( void set_http_handler(std::string key, Func handler, Aspects &&...asps) {
std::string key, Func handler,
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
static_assert(sizeof...(method) >= 1, "must set http_method"); static_assert(sizeof...(method) >= 1, "must set http_method");
if constexpr (sizeof...(method) == 1) { if constexpr (sizeof...(method) == 1) {
(router_.set_http_handler<method>(std::move(key), std::move(handler), (router_.set_http_handler<method>(std::move(key), std::move(handler),
std::move(aspects)), std::forward<Aspects>(asps)...),
...); ...);
} }
else { else {
(router_.set_http_handler<method>(key, handler, aspects), ...); (router_.set_http_handler<method>(key, handler,
std::forward<Aspects>(asps)...),
...);
} }
} }
template <http_method... method, typename Func, typename Owner> template <http_method... method, typename Func, typename... Aspects>
void set_http_handler( void set_http_handler(std::string key, Func handler,
std::string key, Func handler, Owner &&owner, util::class_type_t<Func> &owner, Aspects &&...asps) {
std::vector<std::shared_ptr<base_aspect>> aspects = {}) {
static_assert(std::is_member_function_pointer_v<Func>, static_assert(std::is_member_function_pointer_v<Func>,
"must be member function"); "must be member function");
using return_type = typename util::function_traits<Func>::return_type; using return_type = typename util::function_traits<Func>::return_type;
if constexpr (is_lazy_v<return_type>) { if constexpr (coro_io::is_lazy_v<return_type>) {
std::function<async_simple::coro::Lazy<void>(coro_http_request & req, std::function<async_simple::coro::Lazy<void>(coro_http_request & req,
coro_http_response & resp)> coro_http_response & resp)>
f = std::bind(handler, owner, std::placeholders::_1, f = std::bind(handler, &owner, std::placeholders::_1,
std::placeholders::_2); std::placeholders::_2);
set_http_handler<method...>(std::move(key), std::move(f), set_http_handler<method...>(std::move(key), std::move(f),
std::move(aspects)); std::forward<Aspects>(asps)...);
} }
else { else {
std::function<void(coro_http_request & req, coro_http_response & resp)> std::function<void(coro_http_request & req, coro_http_response & resp)>
f = std::bind(handler, owner, std::placeholders::_1, f = std::bind(handler, &owner, std::placeholders::_1,
std::placeholders::_2); std::placeholders::_2);
set_http_handler<method...>(std::move(key), std::move(f), set_http_handler<method...>(std::move(key), std::move(f),
std::move(aspects)); std::forward<Aspects>(asps)...);
} }
} }
void use_metrics(bool enable_json = false,
std::string url_path = "/metrics") {
init_metrics();
set_http_handler<http_method::GET>(
url_path,
[enable_json](coro_http_request &req, coro_http_response &res) {
std::string str;
#ifdef CINATRA_ENABLE_METRIC_JSON
if (enable_json) {
str =
ylt::metric::default_metric_manager::serialize_to_json_static();
res.set_content_type<resp_content_type::json>();
}
else
#endif
str = ylt::metric::default_metric_manager::serialize_static();
res.set_status_and_content(status_type::ok, std::move(str));
});
}
template <http_method... method, typename... Aspects>
void set_http_proxy_handler(std::string url_path,
std::vector<std::string_view> hosts,
coro_io::load_blance_algorithm type =
coro_io::load_blance_algorithm::random,
std::vector<int> weights = {},
Aspects &&...aspects) {
if (hosts.empty()) {
throw std::invalid_argument("not config hosts yet!");
}
auto channel = std::make_shared<coro_io::channel<coro_http_client>>(
coro_io::channel<coro_http_client>::create(hosts, {.lba = type},
weights));
auto handler =
[this, channel, type](
coro_http_request &req,
coro_http_response &response) -> async_simple::coro::Lazy<void> {
co_await channel->send_request(
[this, &req, &response](
coro_http_client &client,
std::string_view host) -> async_simple::coro::Lazy<void> {
co_await reply(client, host, req, response);
});
};
if constexpr (sizeof...(method) == 0) {
set_http_handler<http_method::GET, http_method::POST, http_method::DEL,
http_method::HEAD, http_method::PUT, http_method::PATCH,
http_method::CONNECT, http_method::TRACE,
http_method::OPTIONS>(url_path, std::move(handler),
std::forward<Aspects>(aspects)...);
}
else {
set_http_handler<method...>(url_path, std::move(handler),
std::forward<Aspects>(aspects)...);
}
}
template <http_method... method, typename... Aspects>
void set_websocket_proxy_handler(std::string url_path,
std::vector<std::string_view> hosts,
coro_io::load_blance_algorithm type =
coro_io::load_blance_algorithm::random,
std::vector<int> weights = {},
Aspects &&...aspects) {
if (hosts.empty()) {
throw std::invalid_argument("not config hosts yet!");
}
auto channel = std::make_shared<coro_io::channel<coro_http_client>>(
coro_io::channel<coro_http_client>::create(hosts, {.lba = type},
weights));
set_http_handler<cinatra::GET>(
url_path,
[channel](coro_http_request &req,
coro_http_response &resp) -> async_simple::coro::Lazy<void> {
websocket_result result{};
while (true) {
result = co_await req.get_conn()->read_websocket();
if (result.ec) {
break;
}
if (result.type == ws_frame_type::WS_CLOSE_FRAME) {
CINATRA_LOG_INFO << "close frame";
break;
}
co_await channel->send_request(
[&req, result](
coro_http_client &client,
std::string_view host) -> async_simple::coro::Lazy<void> {
auto r =
co_await client.write_websocket(std::string(result.data));
if (r.net_err) {
co_return;
}
auto data = co_await client.read_websocket();
if (data.net_err) {
co_return;
}
auto ec = co_await req.get_conn()->write_websocket(
std::string(result.data));
if (ec) {
co_return;
}
});
}
},
std::forward<Aspects>(aspects)...);
}
void set_max_size_of_cache_files(size_t max_size = 3 * 1024 * 1024) { void set_max_size_of_cache_files(size_t max_size = 3 * 1024 * 1024) {
std::error_code ec; std::error_code ec;
for (const auto &file : for (const auto &file :
@ -196,9 +330,9 @@ class coro_http_server {
void set_transfer_chunked_size(size_t size) { chunked_size_ = size; } void set_transfer_chunked_size(size_t size) { chunked_size_ = size; }
void set_static_res_dir( template <typename... Aspects>
std::string_view uri_suffix = "", std::string file_path = "www", void set_static_res_dir(std::string_view uri_suffix = "",
std::vector<std::shared_ptr<base_aspect>> aspects = {}) { std::string file_path = "www", Aspects &&...aspects) {
bool has_double_dot = (file_path.find("..") != std::string::npos) || bool has_double_dot = (file_path.find("..") != std::string::npos) ||
(uri_suffix.find("..") != std::string::npos); (uri_suffix.find("..") != std::string::npos);
if (std::filesystem::path(file_path).has_root_path() || if (std::filesystem::path(file_path).has_root_path() ||
@ -220,8 +354,12 @@ class coro_http_server {
} }
files_.clear(); files_.clear();
std::error_code ec;
for (const auto &file : for (const auto &file :
std::filesystem::recursive_directory_iterator(static_dir_)) { std::filesystem::recursive_directory_iterator(static_dir_, ec)) {
if (ec) {
continue;
}
if (!file.is_directory()) { if (!file.is_directory()) {
files_.push_back(file.path().string()); files_.push_back(file.path().string());
} }
@ -417,7 +555,7 @@ class coro_http_server {
} }
} }
}, },
aspects); std::forward<Aspects>(aspects)...);
} }
} }
@ -434,38 +572,73 @@ class coro_http_server {
void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; } void set_shrink_to_fit(bool r) { need_shrink_every_time_ = r; }
void set_default_handler(std::function<async_simple::coro::Lazy<void>(
coro_http_request &, coro_http_response &)>
handler) {
default_handler_ = std::move(handler);
}
size_t connection_count() { size_t connection_count() {
std::scoped_lock lock(conn_mtx_); std::scoped_lock lock(conn_mtx_);
return connections_.size(); return connections_.size();
} }
std::string_view address() { return address_; }
std::error_code get_errc() { return errc_; }
private: private:
std::errc listen() { std::error_code listen() {
CINATRA_LOG_INFO << "begin to listen"; CINATRA_LOG_INFO << "begin to listen";
using asio::ip::tcp; using asio::ip::tcp;
auto endpoint = tcp::endpoint(tcp::v4(), port_);
acceptor_.open(endpoint.protocol());
#ifdef __GNUC__
acceptor_.set_option(tcp::acceptor::reuse_address(true));
#endif
asio::error_code ec; asio::error_code ec;
asio::ip::tcp::resolver::query query(address_, std::to_string(port_));
asio::ip::tcp::resolver resolver(acceptor_.get_executor());
asio::ip::tcp::resolver::iterator it = resolver.resolve(query, ec);
asio::ip::tcp::resolver::iterator it_end;
if (ec || it == it_end) {
CINATRA_LOG_ERROR << "bad address: " << address_
<< " error: " << ec.message();
if (ec) {
return ec;
}
return std::make_error_code(std::errc::address_not_available);
}
auto endpoint = it->endpoint();
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
CINATRA_LOG_ERROR << "acceptor open failed"
<< " error: " << ec.message();
return ec;
}
#ifdef __GNUC__
acceptor_.set_option(tcp::acceptor::reuse_address(true), ec);
#endif
acceptor_.bind(endpoint, ec); acceptor_.bind(endpoint, ec);
if (ec) { if (ec) {
CINATRA_LOG_ERROR << "bind port: " << port_ << " error: " << ec.message(); CINATRA_LOG_ERROR << "bind port: " << port_ << " error: " << ec.message();
acceptor_.cancel(ec); std::error_code ignore_ec;
acceptor_.close(ec); acceptor_.cancel(ignore_ec);
return std::errc::address_in_use; acceptor_.close(ignore_ec);
return ec;
} }
#ifdef _MSC_VER #ifdef _MSC_VER
acceptor_.set_option(tcp::acceptor::reuse_address(true)); acceptor_.set_option(tcp::acceptor::reuse_address(true));
#endif #endif
acceptor_.listen(); acceptor_.listen(asio::socket_base::max_listen_connections, ec);
if (ec) {
CINATRA_LOG_ERROR << "get local endpoint port: " << port_
<< " listen error: " << ec.message();
return ec;
}
auto end_point = acceptor_.local_endpoint(ec); auto end_point = acceptor_.local_endpoint(ec);
if (ec) { if (ec) {
CINATRA_LOG_ERROR << "get local endpoint port: " << port_ CINATRA_LOG_ERROR << "get local endpoint port: " << port_
<< " error: " << ec.message(); << " error: " << ec.message();
return std::errc::address_in_use; return ec;
} }
port_ = end_point.port(); port_ = end_point.port();
@ -473,7 +646,7 @@ class coro_http_server {
return {}; return {};
} }
async_simple::coro::Lazy<std::errc> accept() { async_simple::coro::Lazy<std::error_code> accept() {
for (;;) { for (;;) {
coro_io::ExecutorWrapper<> *executor; coro_io::ExecutorWrapper<> *executor;
if (out_ctx_ == nullptr) { if (out_ctx_ == nullptr) {
@ -492,7 +665,7 @@ class coro_http_server {
if (error == asio::error::operation_aborted || if (error == asio::error::operation_aborted ||
error == asio::error::bad_descriptor) { error == asio::error::bad_descriptor) {
acceptor_close_waiter_.set_value(); acceptor_close_waiter_.set_value();
co_return std::errc::operation_canceled; co_return error;
} }
continue; continue;
} }
@ -510,6 +683,9 @@ class coro_http_server {
if (need_check_) { if (need_check_) {
conn->set_check_timeout(true); conn->set_check_timeout(true);
} }
if (default_handler_) {
conn->set_default_handler(default_handler_);
}
#ifdef CINATRA_ENABLE_SSL #ifdef CINATRA_ENABLE_SSL
if (use_ssl_) { if (use_ssl_) {
@ -530,7 +706,7 @@ class coro_http_server {
connections_.emplace(conn_id, conn); connections_.emplace(conn_id, conn);
} }
start_one(conn).via(&conn->get_executor()).detach(); start_one(conn).via(conn->get_executor()).detach();
} }
} }
@ -676,11 +852,93 @@ class coro_http_server {
co_return true; co_return true;
} }
async_simple::coro::Lazy<void> reply(coro_http_client &client,
std::string_view host,
coro_http_request &req,
coro_http_response &response) {
uri_t uri;
std::string proxy_host;
if (host.find("//") == std::string_view::npos) {
proxy_host.append("http://").append(host);
uri.parse_from(proxy_host.data());
}
else {
uri.parse_from(host.data());
}
std::unordered_map<std::string, std::string> req_headers;
for (auto &[k, v] : req.get_headers()) {
req_headers.emplace(k, v);
}
req_headers["Host"] = uri.host;
auto ctx = req_context<std::string_view>{.content = req.get_body()};
auto result = co_await client.async_request(
req.full_url(), method_type(req.get_method()), std::move(ctx),
std::move(req_headers));
response.add_header_span(result.resp_headers);
response.set_status_and_content_view(
static_cast<status_type>(result.status), result.resp_body);
co_await response.get_conn()->reply();
response.set_delay(true);
}
void init_address(std::string address) {
#if __has_include(<ylt/easylog.hpp>)
easylog::logger<>::instance(); // init easylog singleton to make sure
// server destruct before easylog.
#endif
if (size_t pos = address.find(':'); pos != std::string::npos) {
auto port_sv = std::string_view(address).substr(pos + 1);
uint16_t port;
auto [ptr, ec] = std::from_chars(
port_sv.data(), port_sv.data() + port_sv.size(), port, 10);
if (ec != std::errc{}) {
address_ = std::move(address);
return;
}
port_ = port;
address = address.substr(0, pos);
}
address_ = std::move(address);
}
private:
void init_metrics() {
using namespace ylt::metric;
cinatra_metric_conf::enable_metric = true;
default_metric_manager::create_metric_static<counter_t>(
cinatra_metric_conf::server_total_req, "");
default_metric_manager::create_metric_static<counter_t>(
cinatra_metric_conf::server_failed_req, "");
default_metric_manager::create_metric_static<counter_t>(
cinatra_metric_conf::server_total_recv_bytes, "");
default_metric_manager::create_metric_static<counter_t>(
cinatra_metric_conf::server_total_send_bytes, "");
default_metric_manager::create_metric_static<gauge_t>(
cinatra_metric_conf::server_total_fd, "");
default_metric_manager::create_metric_static<histogram_t>(
cinatra_metric_conf::server_req_latency, "",
std::vector<double>{30, 40, 50, 60, 70, 80, 90, 100, 150});
default_metric_manager::create_metric_static<histogram_t>(
cinatra_metric_conf::server_read_latency, "",
std::vector<double>{3, 5, 7, 9, 13, 18, 23, 35, 50});
}
private: private:
std::unique_ptr<coro_io::io_context_pool> pool_; std::unique_ptr<coro_io::io_context_pool> pool_;
asio::io_context *out_ctx_ = nullptr; asio::io_context *out_ctx_ = nullptr;
std::unique_ptr<coro_io::ExecutorWrapper<>> out_executor_ = nullptr; std::unique_ptr<coro_io::ExecutorWrapper<>> out_executor_ = nullptr;
uint16_t port_; uint16_t port_;
std::string address_;
std::error_code errc_ = {};
asio::ip::tcp::acceptor acceptor_; asio::ip::tcp::acceptor acceptor_;
std::thread thd_; std::thread thd_;
std::promise<void> acceptor_close_waiter_; std::promise<void> acceptor_close_waiter_;
@ -712,6 +970,9 @@ class coro_http_server {
#endif #endif
coro_http_router router_; coro_http_router router_;
bool need_shrink_every_time_ = false; bool need_shrink_every_time_ = false;
std::function<async_simple::coro::Lazy<void>(coro_http_request &,
coro_http_response &)>
default_handler_ = nullptr;
}; };
using http_server = coro_http_server; using http_server = coro_http_server;

View File

@ -42,8 +42,8 @@ struct coro_handler_t {
struct radix_tree_node { struct radix_tree_node {
std::string path; std::string path;
std::vector<handler_t> handlers; handler_t handler;
std::vector<coro_handler_t> coro_handlers; coro_handler_t coro_handler;
std::string indices; std::string indices;
std::vector<std::shared_ptr<radix_tree_node>> children; std::vector<std::shared_ptr<radix_tree_node>> children;
int max_params; int max_params;
@ -54,21 +54,18 @@ struct radix_tree_node {
std::function<void(coro_http_request &req, coro_http_response &resp)> std::function<void(coro_http_request &req, coro_http_response &resp)>
get_handler(const std::string &method) { get_handler(const std::string &method) {
for (auto &h : this->handlers) { if (handler.method == method) {
if (h.method == method) { return handler.handler;
return h.handler;
}
} }
return nullptr; return nullptr;
} }
std::function<async_simple::coro::Lazy<void>(coro_http_request &req, std::function<async_simple::coro::Lazy<void>(coro_http_request &req,
coro_http_response &resp)> coro_http_response &resp)>
get_coro_handler(const std::string &method) { get_coro_handler(const std::string &method) {
for (auto &h : this->coro_handlers) { if (coro_handler.method == method) {
if (h.method == method) { return coro_handler.coro_handler;
return h.coro_handler;
}
} }
return nullptr; return nullptr;
} }
@ -76,22 +73,17 @@ struct radix_tree_node {
int add_handler( int add_handler(
std::function<void(coro_http_request &req, coro_http_response &resp)> std::function<void(coro_http_request &req, coro_http_response &resp)>
handler, handler,
const std::vector<std::string> &methods) { const std::string &method) {
for (auto &m : methods) { this->handler = handler_t{method, handler};
auto old_handler = this->get_handler(m);
this->handlers.push_back(handler_t{m, handler});
}
return 0; return 0;
} }
int add_coro_handler(std::function<async_simple::coro::Lazy<void>( int add_coro_handler(std::function<async_simple::coro::Lazy<void>(
coro_http_request &req, coro_http_response &resp)> coro_http_request &req, coro_http_response &resp)>
coro_handler, coro_handler,
const std::vector<std::string> &methods) { const std::string &method) {
for (auto &m : methods) { this->coro_handler = coro_handler_t{method, coro_handler};
auto old_coro_handler = this->get_coro_handler(m);
this->coro_handlers.push_back(coro_handler_t{m, coro_handler});
}
return 0; return 0;
} }
@ -134,7 +126,7 @@ class radix_tree {
const std::string &path, const std::string &path,
std::function<void(coro_http_request &req, coro_http_response &resp)> std::function<void(coro_http_request &req, coro_http_response &resp)>
handler, handler,
const std::vector<std::string> &methods) { const std::string &method) {
auto root = this->root; auto root = this->root;
int i = 0, n = path.size(), param_count = 0, code = 0; int i = 0, n = path.size(), param_count = 0, code = 0;
while (i < n) { while (i < n) {
@ -166,7 +158,7 @@ class radix_tree {
++param_count; ++param_count;
} }
code = root->add_handler(handler, methods); code = root->add_handler(handler, method);
break; break;
} }
@ -181,7 +173,7 @@ class radix_tree {
++param_count; ++param_count;
if (i == n) { if (i == n) {
code = root->add_handler(handler, methods); code = root->add_handler(handler, method);
break; break;
} }
} }
@ -193,7 +185,7 @@ class radix_tree {
i += root->path.size() + 1; i += root->path.size() + 1;
if (i == n) { if (i == n) {
code = root->add_handler(handler, methods); code = root->add_handler(handler, method);
break; break;
} }
} }
@ -207,18 +199,18 @@ class radix_tree {
if (j < m) { if (j < m) {
std::shared_ptr<radix_tree_node> child( std::shared_ptr<radix_tree_node> child(
std::make_shared<radix_tree_node>(root->path.substr(j))); std::make_shared<radix_tree_node>(root->path.substr(j)));
child->handlers = root->handlers; child->handler = root->handler;
child->indices = root->indices; child->indices = root->indices;
child->children = root->children; child->children = root->children;
root->path = root->path.substr(0, j); root->path = root->path.substr(0, j);
root->handlers = {}; root->handler = {};
root->indices = child->path[0]; root->indices = child->path[0];
root->children = {child}; root->children = {child};
} }
if (i == n) { if (i == n) {
code = root->add_handler(handler, methods); code = root->add_handler(handler, method);
break; break;
} }
} }
@ -235,7 +227,7 @@ class radix_tree {
std::function<async_simple::coro::Lazy<void>( std::function<async_simple::coro::Lazy<void>(
coro_http_request &req, coro_http_response &resp)> coro_http_request &req, coro_http_response &resp)>
coro_handler, coro_handler,
const std::vector<std::string> &methods) { std::string &method) {
auto root = this->root; auto root = this->root;
int i = 0, n = path.size(), param_count = 0, code = 0; int i = 0, n = path.size(), param_count = 0, code = 0;
while (i < n) { while (i < n) {
@ -267,7 +259,7 @@ class radix_tree {
++param_count; ++param_count;
} }
code = root->add_coro_handler(coro_handler, methods); code = root->add_coro_handler(coro_handler, method);
break; break;
} }
@ -282,7 +274,7 @@ class radix_tree {
++param_count; ++param_count;
if (i == n) { if (i == n) {
code = root->add_coro_handler(coro_handler, methods); code = root->add_coro_handler(coro_handler, method);
break; break;
} }
} }
@ -294,7 +286,7 @@ class radix_tree {
i += root->path.size() + 1; i += root->path.size() + 1;
if (i == n) { if (i == n) {
code = root->add_coro_handler(coro_handler, methods); code = root->add_coro_handler(coro_handler, method);
break; break;
} }
} }
@ -308,18 +300,18 @@ class radix_tree {
if (j < m) { if (j < m) {
std::shared_ptr<radix_tree_node> child( std::shared_ptr<radix_tree_node> child(
std::make_shared<radix_tree_node>(root->path.substr(j))); std::make_shared<radix_tree_node>(root->path.substr(j)));
child->handlers = root->handlers; child->coro_handler = root->coro_handler;
child->indices = root->indices; child->indices = root->indices;
child->children = root->children; child->children = root->children;
root->path = root->path.substr(0, j); root->path = root->path.substr(0, j);
root->handlers = {}; root->coro_handler = {};
root->indices = child->path[0]; root->indices = child->path[0];
root->children = {child}; root->children = {child};
} }
if (i == n) { if (i == n) {
code = root->add_coro_handler(coro_handler, methods); code = root->add_coro_handler(coro_handler, method);
break; break;
} }
} }

View File

@ -8,16 +8,16 @@ using namespace std::string_view_literals;
namespace cinatra { namespace cinatra {
enum class http_method { enum class http_method {
UNKNOW, NIL = 0,
DEL,
GET, GET,
HEAD, HEAD,
POST, POST,
PUT, PUT,
TRACE,
PATCH, PATCH,
CONNECT, CONNECT,
OPTIONS, OPTIONS,
TRACE DEL,
}; };
constexpr inline auto GET = http_method::GET; constexpr inline auto GET = http_method::GET;
constexpr inline auto POST = http_method::POST; constexpr inline auto POST = http_method::POST;
@ -52,10 +52,18 @@ inline constexpr std::string_view method_name(http_method mthd) {
case cinatra::http_method::TRACE: case cinatra::http_method::TRACE:
return "TRACE"sv; return "TRACE"sv;
default: default:
return "UNKONWN"sv; return "NIL"sv;
} }
} }
inline constexpr std::array<int, 20> method_table = {
3, 1, 9, 0, 0, 0, 4, 5, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 6, 7};
inline constexpr http_method method_type(std::string_view mthd) {
int index = ((mthd[0] & ~0x20) ^ ((mthd[1] + 1) & ~0x20)) % 20;
return (http_method)method_table[index];
}
enum class transfer_type { CHUNKED, ACCEPT_RANGES }; enum class transfer_type { CHUNKED, ACCEPT_RANGES };
enum class content_type { enum class content_type {

View File

@ -0,0 +1,302 @@
#pragma once
#include <zlib.h>
#include <string_view>
namespace cinatra::gzip_codec {
// from https://github.com/chafey/GZipCodec
#define CHUNK 16384
#define windowBits 15
#define GZIP_ENCODING 16
// GZip Compression
// @param data - the data to compress (does not have to be string, can be binary
// data)
// @param compressed_data - the resulting gzip compressed data
// @param level - the gzip compress level -1 = default, 0 = no compression, 1=
// worst/fastest compression, 9 = best/slowest compression
// @return - true on success, false on failure
inline bool compress(std::string_view data, std::string &compressed_data,
int level = -1) {
unsigned char out[CHUNK];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
if (deflateInit2(&strm, level, Z_DEFLATED, windowBits | GZIP_ENCODING, 8,
Z_DEFAULT_STRATEGY) != Z_OK) {
return false;
}
strm.next_in = (unsigned char *)data.data();
strm.avail_in = (uInt)data.length();
do {
int have;
strm.avail_out = CHUNK;
strm.next_out = out;
if (deflate(&strm, Z_FINISH) == Z_STREAM_ERROR) {
return false;
}
have = CHUNK - strm.avail_out;
compressed_data.append((char *)out, have);
} while (strm.avail_out == 0);
if (deflateEnd(&strm) != Z_OK) {
return false;
}
return true;
}
// GZip Decompression
// @param compressed_data - the gzip compressed data
// @param data - the resulting uncompressed data (may contain binary data)
// @return - true on success, false on failure
inline bool uncompress(std::string_view compressed_data, std::string &data) {
int ret;
unsigned have;
z_stream strm;
unsigned char out[CHUNK];
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if (inflateInit2(&strm, 16 + MAX_WBITS) != Z_OK) {
return false;
}
strm.avail_in = (uInt)compressed_data.length();
strm.next_in = (unsigned char *)compressed_data.data();
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
switch (ret) {
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
inflateEnd(&strm);
return false;
}
have = CHUNK - strm.avail_out;
data.append((char *)out, have);
} while (strm.avail_out == 0);
if (inflateEnd(&strm) != Z_OK) {
return false;
}
return true;
}
inline int compress_file(const char *src_file, const char *out_file_name) {
char buf[BUFSIZ] = {0};
uInt bytes_read = 0;
gzFile out = gzopen(out_file_name, "wb");
if (!out) {
return -1;
}
std::ifstream in(src_file, std::ios::binary);
if (!in.is_open()) {
return -1;
}
while (true) {
in.read(buf, BUFSIZ);
bytes_read = (uInt)in.gcount();
if (bytes_read == 0)
break;
int bytes_written = gzwrite(out, buf, bytes_read);
if (bytes_written == 0) {
gzclose(out);
return -1;
}
if (bytes_read != BUFSIZ)
break;
}
gzclose(out);
return 0;
}
inline int uncompress_file(const char *src_file, const char *out_file_name) {
char buf[BUFSIZ] = {0};
std::ofstream out(out_file_name, std::ios::binary);
if (!out.is_open()) {
return -1;
}
gzFile fi = gzopen(src_file, "rb");
if (!fi) {
return -1;
}
gzrewind(fi);
while (!gzeof(fi)) {
int len = gzread(fi, buf, BUFSIZ);
out.write(buf, len);
}
gzclose(fi);
return 0;
}
inline bool inflate(std::string_view str_src, std::string &str_dest) {
int err = Z_DATA_ERROR;
// Create stream
z_stream zs = {0};
// Set output data streams, do this here to avoid overwriting on recursive
// calls
const int OUTPUT_BUF_SIZE = 8192;
Bytef bytes_out[OUTPUT_BUF_SIZE] = {0};
// Initialise the z_stream
err = ::inflateInit2(&zs, -15);
if (err != Z_OK) {
return false;
}
// Use whatever input is provided
zs.next_in = (Bytef *)(str_src.data());
zs.avail_in = str_src.length();
do {
try {
// Initialise stream values
// zs->zalloc = (alloc_func)0;
// zs->zfree = (free_func)0;
// zs->opaque = (voidpf)0;
zs.next_out = bytes_out;
zs.avail_out = OUTPUT_BUF_SIZE;
// Try to unzip the data
err = ::inflate(&zs, Z_SYNC_FLUSH);
// Is zip finished reading all currently available input and writing all
// generated output
if (err == Z_STREAM_END) {
// Finish up
int kerr = ::inflateEnd(&zs);
// Got a good result, set the size to the amount unzipped in this call
// (including all recursive calls)
str_dest.append((const char *)bytes_out,
OUTPUT_BUF_SIZE - zs.avail_out);
return true;
}
else if ((err == Z_OK) && (zs.avail_out == 0) && (zs.avail_in != 0)) {
// Output array was not big enough, call recursively until there is
// enough space
str_dest.append((const char *)bytes_out,
OUTPUT_BUF_SIZE - zs.avail_out);
continue;
}
else if ((err == Z_OK) && (zs.avail_in == 0)) {
// All available input has been processed, everything ok.
// Set the size to the amount unzipped in this call (including all
// recursive calls)
str_dest.append((const char *)bytes_out,
OUTPUT_BUF_SIZE - zs.avail_out);
int kerr = ::inflateEnd(&zs);
break;
}
else {
return false;
}
} catch (...) {
return false;
}
} while (true);
return err == Z_OK;
}
inline bool deflate(std::string_view str_src, std::string &str_dest) {
int err = Z_DATA_ERROR;
// Create stream
z_stream zs = {0};
// Set output data streams, do this here to avoid overwriting on recursive
// calls
const int OUTPUT_BUF_SIZE = 8192;
Bytef bytes_out[OUTPUT_BUF_SIZE] = {0};
// Initialise the z_stream
err = ::deflateInit2(&zs, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
if (err != Z_OK) {
return false;
}
// Use whatever input is provided
zs.next_in = (Bytef *)(str_src.data());
zs.avail_in = str_src.length();
do {
try {
// Initialise stream values
// zs->zalloc = (alloc_func)0;
// zs->zfree = (free_func)0;
// zs->opaque = (voidpf)0;
zs.next_out = bytes_out;
zs.avail_out = OUTPUT_BUF_SIZE;
// Try to unzip the data
err = ::deflate(&zs, Z_SYNC_FLUSH);
// Is zip finished reading all currently available input and writing all
// generated output
if (err == Z_STREAM_END) {
// Finish up
int kerr = ::deflateEnd(&zs);
// Got a good result, set the size to the amount unzipped in this call
// (including all recursive calls)
str_dest.append((const char *)bytes_out,
OUTPUT_BUF_SIZE - zs.avail_out);
return true;
}
else if ((err == Z_OK) && (zs.avail_out == 0) && (zs.avail_in != 0)) {
// Output array was not big enough, call recursively until there is
// enough space
str_dest.append((const char *)bytes_out,
OUTPUT_BUF_SIZE - zs.avail_out);
continue;
}
else if ((err == Z_OK) && (zs.avail_in == 0)) {
// All available input has been processed, everything ok.
// Set the size to the amount unzipped in this call (including all
// recursive calls)
str_dest.append((const char *)bytes_out,
OUTPUT_BUF_SIZE - zs.avail_out);
int kerr = ::deflateEnd(&zs);
break;
}
else {
return false;
}
} catch (...) {
return false;
}
} while (true);
if (err == Z_OK) {
// subtract 4 to remove the extra 00 00 ff ff added to the end of the deflat
// function
str_dest = str_dest.substr(0, str_dest.length() - 4);
return true;
}
return false;
}
} // namespace cinatra::gzip_codec

View File

@ -7,7 +7,9 @@
#include <string_view> #include <string_view>
#include <unordered_map> #include <unordered_map>
#include "cinatra/utils.hpp"
#include "cinatra_log_wrapper.hpp" #include "cinatra_log_wrapper.hpp"
#include "define.h"
#include "picohttpparser.h" #include "picohttpparser.h"
#include "url_encode_decode.hpp" #include "url_encode_decode.hpp"
@ -64,9 +66,12 @@ class http_parser {
size_t method_len; size_t method_len;
const char *url; const char *url;
size_t url_len; size_t url_len;
bool has_query{};
header_len_ = detail::phr_parse_request( header_len_ = detail::phr_parse_request(
data, size, &method, &method_len, &url, &url_len, &minor_version, data, size, &method, &method_len, &url, &url_len, &minor_version,
headers_.data(), &num_headers_, last_len); headers_.data(), &num_headers_, last_len, has_connection_, has_close_,
has_upgrade_, has_query);
if (header_len_ < 0) [[unlikely]] { if (header_len_ < 0) [[unlikely]] {
CINATRA_LOG_WARNING << "parse http head failed"; CINATRA_LOG_WARNING << "parse http head failed";
@ -76,11 +81,17 @@ class http_parser {
<< ", you can define macro " << ", you can define macro "
"CINATRA_MAX_HTTP_HEADER_FIELD_SIZE to expand it."; "CINATRA_MAX_HTTP_HEADER_FIELD_SIZE to expand it.";
} }
return header_len_;
} }
method_ = {method, method_len}; method_ = {method, method_len};
url_ = {url, url_len}; url_ = {url, url_len};
auto methd_type = method_type(method_);
if (methd_type == http_method::GET || methd_type == http_method::HEAD) {
body_len_ = 0;
}
else {
auto content_len = this->get_header_value("content-length"sv); auto content_len = this->get_header_value("content-length"sv);
if (content_len.empty()) { if (content_len.empty()) {
body_len_ = 0; body_len_ = 0;
@ -88,9 +99,14 @@ class http_parser {
else { else {
body_len_ = atoi(content_len.data()); body_len_ = atoi(content_len.data());
} }
}
full_url_ = url_;
if (!queries_.empty()) {
queries_.clear();
}
if (has_query) {
size_t pos = url_.find('?'); size_t pos = url_.find('?');
if (pos != std::string_view::npos) {
parse_query(url_.substr(pos + 1, url_len - pos - 1)); parse_query(url_.substr(pos + 1, url_len - pos - 1));
url_ = {url, pos}; url_ = {url, pos};
} }
@ -98,6 +114,12 @@ class http_parser {
return header_len_; return header_len_;
} }
bool has_connection() { return has_connection_; }
bool has_close() { return has_close_; }
bool has_upgrade() { return has_upgrade_; }
std::string_view get_header_value(std::string_view key) const { std::string_view get_header_value(std::string_view key) const {
for (size_t i = 0; i < num_headers_; i++) { for (size_t i = 0; i < num_headers_; i++) {
if (iequal0(headers_[i].name, key)) if (iequal0(headers_[i].name, key))
@ -108,6 +130,8 @@ class http_parser {
const auto &queries() const { return queries_; } const auto &queries() const { return queries_; }
std::string_view full_url() { return full_url_; }
std::string_view get_query_value(std::string_view key) { std::string_view get_query_value(std::string_view key) {
if (auto it = queries_.find(key); it != queries_.end()) { if (auto it = queries_.find(key); it != queries_.end()) {
return it->second; return it->second;
@ -202,35 +226,25 @@ class http_parser {
void parse_query(std::string_view str) { void parse_query(std::string_view str) {
std::string_view key; std::string_view key;
std::string_view val; std::string_view val;
size_t pos = 0;
size_t length = str.length(); auto vec = split_sv(str, "&");
for (size_t i = 0; i < length; i++) { for (auto s : vec) {
char c = str[i]; if (s.empty()) {
if (c == '=') { continue;
key = {&str[pos], i - pos}; }
key = trim(key); size_t pos = s.find('=');
pos = i + 1; if (s.find('=') != std::string_view::npos) {
key = s.substr(0, pos);
if (key.empty()) {
continue;
}
val = s.substr(pos + 1, s.length() - pos);
}
else {
key = s;
val = "";
} }
else if (c == '&') {
val = {&str[pos], i - pos};
val = trim(val);
queries_.emplace(key, val); queries_.emplace(key, val);
pos = i + 1;
}
}
if (pos == 0) {
return;
}
if ((length - pos) > 0) {
val = {&str[pos], length - pos};
val = trim(val);
queries_.emplace(key, val);
}
else if ((length - pos) == 0) {
queries_.emplace(key, "");
} }
} }
@ -247,9 +261,13 @@ class http_parser {
size_t num_headers_ = 0; size_t num_headers_ = 0;
int header_len_ = 0; int header_len_ = 0;
int body_len_ = 0; int body_len_ = 0;
bool has_connection_{};
bool has_close_{};
bool has_upgrade_{};
std::array<http_header, CINATRA_MAX_HTTP_HEADER_FIELD_SIZE> headers_; std::array<http_header, CINATRA_MAX_HTTP_HEADER_FIELD_SIZE> headers_;
std::string_view method_; std::string_view method_;
std::string_view url_; std::string_view url_;
std::string_view full_url_;
std::unordered_map<std::string_view, std::string_view> queries_; std::unordered_map<std::string_view, std::string_view> queries_;
}; };
} // namespace cinatra } // namespace cinatra

View File

@ -0,0 +1,120 @@
#pragma once
#include <atomic>
#include <string>
#include "ylt/metric/counter.hpp"
#include "ylt/metric/gauge.hpp"
#include "ylt/metric/histogram.hpp"
#include "ylt/metric/metric.hpp"
#include "ylt/metric/summary.hpp"
namespace cinatra {
struct cinatra_metric_conf {
inline static std::string server_total_req = "server_total_req";
inline static std::string server_failed_req = "server_failed_req";
inline static std::string server_total_fd = "server_total_fd";
inline static std::string server_total_recv_bytes = "server_total_recv_bytes";
inline static std::string server_total_send_bytes = "server_total_send_bytes";
inline static std::string server_req_latency = "server_req_latency";
inline static std::string server_read_latency = "server_read_latency";
inline static std::string server_total_thread_num = "server_total_thread_num";
inline static bool enable_metric = false;
inline static void server_total_req_inc() {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_total_req);
if (m == nullptr) {
return;
}
m->inc();
}
inline static void server_failed_req_inc() {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_failed_req);
if (m == nullptr) {
return;
}
m->inc();
}
inline static void server_total_fd_inc() {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::gauge_t>(server_total_fd);
if (m == nullptr) {
return;
}
m->inc();
}
inline static void server_total_fd_dec() {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::gauge_t>(server_total_fd);
if (m == nullptr) {
return;
}
m->dec();
}
inline static void server_total_recv_bytes_inc(double val) {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_total_recv_bytes);
if (m == nullptr) {
return;
}
m->inc(val);
}
inline static void server_total_send_bytes_inc(double val) {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::counter_t>(server_total_send_bytes);
if (m == nullptr) {
return;
}
m->inc(val);
}
inline static void server_req_latency_observe(double val) {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::histogram_t>(server_req_latency);
if (m == nullptr) {
return;
}
m->observe(val);
}
inline static void server_read_latency_observe(double val) {
if (!enable_metric) {
return;
}
static auto m = ylt::metric::default_metric_manager::get_metric_static<
ylt::metric::histogram_t>(server_read_latency);
if (m == nullptr) {
return;
}
m->observe(val);
}
};
} // namespace cinatra

View File

@ -11,7 +11,12 @@ class multipart_reader_t {
head_buf_(conn_->head_buf_), head_buf_(conn_->head_buf_),
chunked_buf_(conn_->chunked_buf_) {} chunked_buf_(conn_->chunked_buf_) {}
async_simple::coro::Lazy<part_head_t> read_part_head() { async_simple::coro::Lazy<part_head_t> read_part_head(
std::string_view boundary) {
if (boundary.empty()) {
co_return part_head_t{std::make_error_code(std::errc::protocol_error)};
}
if (head_buf_.size() > 0) { if (head_buf_.size() > 0) {
const char *data_ptr = asio::buffer_cast<const char *>(head_buf_.data()); const char *data_ptr = asio::buffer_cast<const char *>(head_buf_.data());
chunked_buf_.sputn(data_ptr, head_buf_.size()); chunked_buf_.sputn(data_ptr, head_buf_.size());

View File

@ -808,7 +808,9 @@ static const char *parse_headers(const char *buf, const char *buf_end,
static const char *parse_headers(const char *buf, const char *buf_end, static const char *parse_headers(const char *buf, const char *buf_end,
http_header *headers, size_t *num_headers, http_header *headers, size_t *num_headers,
size_t max_headers, int *ret) { size_t max_headers, int *ret,
bool &has_connection, bool &has_close,
bool &has_upgrade) {
for (;; ++*num_headers) { for (;; ++*num_headers) {
const char *name; const char *name;
size_t name_len; size_t name_len;
@ -877,6 +879,21 @@ static const char *parse_headers(const char *buf, const char *buf_end,
NULL) { NULL) {
return NULL; return NULL;
} }
if (name_len == 10) {
if (memcmp(name + 1, "onnection", name_len - 1) == 0) {
// has connection
has_connection = true;
char ch = *value;
if (ch == 'U' || ch == 'u') {
// has upgrade
has_upgrade = true;
}
else if (ch == 'c' || ch == 'C') {
// has_close
has_close = true;
}
}
}
headers[*num_headers] = {std::string_view{name, name_len}, headers[*num_headers] = {std::string_view{name, name_len},
std::string_view{value, value_len}}; std::string_view{value, value_len}};
} }
@ -885,12 +902,40 @@ static const char *parse_headers(const char *buf, const char *buf_end,
#endif #endif
static const char *parse_request(const char *buf, const char *buf_end, #define ADVANCE_PATH(tok, toklen, has_query) \
const char **method, size_t *method_len, do { \
const char **path, size_t *path_len, const char *tok_start = buf; \
int *minor_version, http_header *headers, static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \
size_t *num_headers, size_t max_headers, int found2; \
int *ret) { buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \
if (!found2) { \
CHECK_EOF(); \
} \
while (1) { \
if (*buf == ' ') { \
break; \
} \
else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
if ((unsigned char)*buf < '\040' || *buf == '\177') { \
*ret = -1; \
return NULL; \
} \
} \
else if (unlikely(*buf == '?')) { \
has_query = true; \
} \
++buf; \
CHECK_EOF(); \
} \
tok = tok_start; \
toklen = buf - tok_start; \
} while (0)
static const char *parse_request(
const char *buf, const char *buf_end, const char **method,
size_t *method_len, const char **path, size_t *path_len, int *minor_version,
http_header *headers, size_t *num_headers, size_t max_headers, int *ret,
bool &has_connection, bool &has_close, bool &has_upgrade, bool &has_query) {
/* skip first empty line (some clients add CRLF after POST content) */ /* skip first empty line (some clients add CRLF after POST content) */
CHECK_EOF(); CHECK_EOF();
if (*buf == '\015') { if (*buf == '\015') {
@ -904,7 +949,7 @@ static const char *parse_request(const char *buf, const char *buf_end,
/* parse request line */ /* parse request line */
ADVANCE_TOKEN(*method, *method_len); ADVANCE_TOKEN(*method, *method_len);
++buf; ++buf;
ADVANCE_TOKEN(*path, *path_len); ADVANCE_PATH(*path, *path_len, has_query);
++buf; ++buf;
if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) {
return NULL; return NULL;
@ -921,14 +966,17 @@ static const char *parse_request(const char *buf, const char *buf_end,
return NULL; return NULL;
} }
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret,
has_connection, has_close, has_upgrade);
} }
inline int phr_parse_request(const char *buf_start, size_t len, inline int phr_parse_request(const char *buf_start, size_t len,
const char **method, size_t *method_len, const char **method, size_t *method_len,
const char **path, size_t *path_len, const char **path, size_t *path_len,
int *minor_version, http_header *headers, int *minor_version, http_header *headers,
size_t *num_headers, size_t last_len) { size_t *num_headers, size_t last_len,
bool &has_connection, bool &has_close,
bool &has_upgrade, bool &has_query) {
const char *buf = buf_start, *buf_end = buf_start + len; const char *buf = buf_start, *buf_end = buf_start + len;
size_t max_headers = *num_headers; size_t max_headers = *num_headers;
int r; int r;
@ -948,7 +996,8 @@ inline int phr_parse_request(const char *buf_start, size_t len,
if ((buf = parse_request(buf + last_len, buf_end, method, method_len, path, if ((buf = parse_request(buf + last_len, buf_end, method, method_len, path,
path_len, minor_version, headers, num_headers, path_len, minor_version, headers, num_headers,
max_headers, &r)) == NULL) { max_headers, &r, has_connection, has_close,
has_upgrade, has_query)) == NULL) {
return r; return r;
} }
@ -987,7 +1036,10 @@ inline const char *parse_response(const char *buf, const char *buf_end,
return NULL; return NULL;
} }
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); bool has_connection, has_close, has_upgrade;
return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret,
has_connection, has_close, has_upgrade);
} }
inline int phr_parse_response(const char *buf_start, size_t len, inline int phr_parse_response(const char *buf_start, size_t len,
@ -1033,8 +1085,9 @@ inline int phr_parse_headers(const char *buf_start, size_t len,
return r; return r;
} }
if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, bool has_connection, has_close, has_upgrade;
&r)) == NULL) { if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r,
has_connection, has_close, has_upgrade)) == NULL) {
return r; return r;
} }

View File

@ -8,7 +8,7 @@ namespace cinatra::detail {
#if __cpp_lib_string_resize_and_overwrite >= 202110L #if __cpp_lib_string_resize_and_overwrite >= 202110L
template <typename ch> template <typename ch>
inline void resize(std::basic_string<ch> &str, std::size_t sz) { inline void resize(std::basic_string<ch> &str, std::size_t sz) {
str.resize_and_overwrite(sz, [](ch *, std::size_t sz) { str.resize_and_overwrite(sz, [sz](ch *, std::size_t) {
return sz; return sz;
}); });
} }

View File

@ -294,7 +294,7 @@ struct context {
std::string path; std::string path;
std::string query; std::string query;
std::string body; std::string body;
http_method method = http_method::UNKNOW; http_method method = http_method::NIL;
context() = default; context() = default;
context(const uri_t &u, http_method mthd) context(const uri_t &u, http_method mthd)

View File

@ -24,6 +24,7 @@
#include "define.h" #include "define.h"
#include "response_cv.hpp" #include "response_cv.hpp"
#include "string_resize.hpp"
namespace cinatra { namespace cinatra {
struct ci_less { struct ci_less {
@ -135,18 +136,17 @@ inline std::string_view trim_sv(std::string_view v) {
return v; return v;
} }
inline std::string_view to_hex_string(size_t val) {
static char buf[20];
auto [ptr, ec] = std::to_chars(std::begin(buf), std::end(buf), val, 16);
return std::string_view{buf, size_t(std::distance(buf, ptr))};
}
inline void to_chunked_buffers(std::vector<asio::const_buffer> &buffers, inline void to_chunked_buffers(std::vector<asio::const_buffer> &buffers,
std::string &size_str,
std::string_view chunk_data, bool eof) { std::string_view chunk_data, bool eof) {
size_t length = chunk_data.size(); size_t length = chunk_data.size();
if (length > 0) { if (length > 0) {
// convert bytes transferred count to a hex string. // convert bytes transferred count to a hex string.
auto chunk_size = to_hex_string(length); detail::resize(size_str, 20);
auto [ptr, ec] =
std::to_chars(size_str.data(), size_str.data() + 20, length, 16);
std::string_view chunk_size{size_str.data(),
size_t(std::distance(size_str.data(), ptr))};
// Construct chunk based on rfc2616 section 3.6.1 // Construct chunk based on rfc2616 section 3.6.1
buffers.push_back(asio::buffer(chunk_size)); buffers.push_back(asio::buffer(chunk_size));

View File

@ -0,0 +1,8 @@
#pragma once
// Note: Update the version when release a new version.
// CINATRA_VERSION % 100 is the sub-minor version
// CINATRA_VERSION / 100 % 1000 is the minor version
// CINATRA_VERSION / 100000 is the major version
#define CINATRA_VERSION 901 // 0.9.1

View File

@ -83,7 +83,7 @@ class websocket {
} }
if (msg_masked) { if (msg_masked) {
std::memcpy(mask_, inp + pos, 4); std::memcpy(mask_key_, inp + pos, 4);
} }
return left_header_len_ == 0 ? ws_header_status::complete return left_header_len_ == 0 ? ws_header_status::complete
@ -95,9 +95,9 @@ class websocket {
ws_frame_type parse_payload(std::span<char> buf) { ws_frame_type parse_payload(std::span<char> buf) {
// unmask data: // unmask data:
if (*(uint32_t *)mask_ != 0) { if (*(uint32_t *)mask_key_ != 0) {
for (size_t i = 0; i < payload_length_; i++) { for (size_t i = 0; i < payload_length_; i++) {
buf[i] = buf[i] ^ mask_[i % 4]; buf[i] = buf[i] ^ mask_key_[i % 4];
} }
} }
@ -121,73 +121,67 @@ class websocket {
return ws_frame_type::WS_BINARY_FRAME; return ws_frame_type::WS_BINARY_FRAME;
} }
std::string format_header(size_t length, opcode code) { std::string_view encode_ws_header(size_t size, opcode op, bool eof,
size_t header_length = encode_header(length, code); bool need_compression = false,
return {msg_header_, header_length}; bool is_client = true) {
}
std::string encode_frame(std::span<char> &data, opcode op, bool need_mask,
bool eof = true) {
std::string header;
/// Base header.
frame_header hdr{}; frame_header hdr{};
hdr.fin = eof; hdr.fin = eof;
hdr.rsv1 = 0; hdr.rsv1 = 0;
if (need_compression)
hdr.rsv2 = 1;
else
hdr.rsv2 = 0; hdr.rsv2 = 0;
hdr.rsv3 = 0; hdr.rsv3 = 0;
hdr.opcode = static_cast<uint8_t>(op); hdr.opcode = static_cast<uint8_t>(op);
hdr.mask = 1; hdr.mask = is_client;
if (data.empty()) { hdr.len = size < 126 ? size : (size < 65536 ? 126 : 127);
int mask = 0;
header.resize(sizeof(frame_header) + sizeof(mask));
std::memcpy(header.data(), &hdr, sizeof(hdr));
std::memcpy(header.data() + sizeof(hdr), &mask, sizeof(mask));
return header;
}
hdr.len = std::memcpy(msg_header_, (char *)&hdr, sizeof(hdr));
data.size() < 126 ? data.size() : (data.size() < 65536 ? 126 : 127);
uint8_t buffer[sizeof(frame_header)]; size_t len_bytes = 0;
std::memcpy(buffer, (uint8_t *)&hdr, sizeof(hdr)); if (size >= 126) {
std::string str_hdr_len = if (size >= 65536) {
std::string((const char *)buffer, sizeof(frame_header)); len_bytes = 8;
header.append(str_hdr_len); *((uint64_t *)(msg_header_ + 2)) = htobe64(size);
/// The payload length may be larger than 126 bytes.
std::string str_payload_len;
if (data.size() >= 126) {
if (data.size() >= 65536) {
uint64_t len = data.size();
str_payload_len.resize(sizeof(uint64_t));
*((uint64_t *)&str_payload_len[0]) = htobe64(len);
} }
else { else {
uint16_t len = data.size(); len_bytes = 2;
str_payload_len.resize(sizeof(uint16_t)); *((uint16_t *)(msg_header_ + 2)) = htons(static_cast<uint16_t>(size));
*((uint16_t *)&str_payload_len[0]) = htons(static_cast<uint16_t>(len));
} }
header.append(str_payload_len);
} }
/// The mask is a 32-bit value. size_t header_len = 6;
uint8_t mask[4] = {};
if (need_mask) { if (is_client) {
header[1] |= 0x80; if (size > 0) {
// generate mask key.
uint32_t random = (uint32_t)rand(); uint32_t random = (uint32_t)rand();
memcpy(mask, &random, 4); memcpy(mask_key_, &random, 4);
} }
size_t size = header.size(); std::memcpy(msg_header_ + 2 + len_bytes, mask_key_, 4);
header.resize(size + 4); }
std::memcpy(header.data() + size, mask, 4); else {
header_len = 2;
}
return {msg_header_, header_len + len_bytes};
}
void encode_ws_payload(std::span<char> &data) {
for (int i = 0; i < data.size(); ++i) { for (int i = 0; i < data.size(); ++i) {
data[i] ^= mask[i % 4]; data[i] ^= mask_key_[i % 4];
}
} }
return header; std::string_view encode_frame(std::span<char> &data, opcode op, bool eof,
bool need_compression = false) {
std::string_view ws_header =
encode_ws_header(data.size(), op, eof, need_compression);
encode_ws_payload(data);
return ws_header;
} }
close_frame parse_close_payload(char *src, size_t length) { close_frame parse_close_payload(char *src, size_t length) {
@ -207,14 +201,12 @@ class websocket {
std::string format_close_payload(uint16_t code, char *message, std::string format_close_payload(uint16_t code, char *message,
size_t length) { size_t length) {
if (length == 0) {
return "";
}
std::string close_payload; std::string close_payload;
if (code) {
close_payload.resize(length + 2); close_payload.resize(length + 2);
code = htons(code); code = htons(code);
std::memcpy(close_payload.data(), &code, 2); std::memcpy(close_payload.data(), &code, 2);
if (length > 0) {
std::memcpy(close_payload.data() + 2, message, length); std::memcpy(close_payload.data() + 2, message, length);
} }
return close_payload; return close_payload;
@ -227,7 +219,7 @@ class websocket {
opcode get_opcode() { return (opcode)msg_opcode_; } opcode get_opcode() { return (opcode)msg_opcode_; }
private: private:
size_t encode_header(size_t length, opcode code) { size_t encode_header(size_t length, opcode code, bool is_compressed = false) {
size_t header_length; size_t header_length;
if (length < 126) { if (length < 126) {
@ -251,6 +243,9 @@ class websocket {
msg_header_[0] |= code; msg_header_[0] |= code;
} }
if (is_compressed)
msg_header_[0] |= 0x40;
return header_length; return header_length;
} }
@ -259,11 +254,11 @@ class websocket {
size_t payload_length_ = 0; size_t payload_length_ = 0;
size_t left_header_len_ = 0; size_t left_header_len_ = 0;
uint8_t mask_[4] = {}; uint8_t mask_key_[4] = {};
unsigned char msg_opcode_ = 0; unsigned char msg_opcode_ = 0;
unsigned char msg_fin_ = 0; unsigned char msg_fin_ = 0;
char msg_header_[10]; char msg_header_[14];
ws_head_len len_bytes_ = SHORT_HEADER; ws_head_len len_bytes_ = SHORT_HEADER;
}; };

View File

@ -0,0 +1,83 @@
#pragma once
#include <charconv>
#include "dragonbox_to_chars.h"
#include "fast_float.h"
#include "iguana/define.h"
#include "itoa.hpp"
namespace iguana {
template <typename T>
struct is_char_type
: std::disjunction<std::is_same<T, char>, std::is_same<T, wchar_t>,
std::is_same<T, char16_t>, std::is_same<T, char32_t>> {};
inline void *to_chars_float(...) {
throw std::runtime_error("not allowed to invoke");
return {};
}
template <typename T, typename Ret = decltype(to_chars_float(
std::declval<T>(), std::declval<char *>()))>
using return_of_tochars = std::conditional_t<std::is_same_v<Ret, char *>,
std::true_type, std::false_type>;
// here std::true_type is used as a type , any other type is also ok.
using has_to_chars_float = iguana::return_of_tochars<std::true_type>;
namespace detail {
// check_number==true: check if the string [first, last) is a legal number
template <bool check_number = true, typename U>
std::pair<const char *, std::errc> from_chars(const char *first,
const char *last, U &value) {
using T = std::decay_t<U>;
if constexpr (std::is_floating_point_v<T>) {
auto [p, ec] = fast_float::from_chars(first, last, value);
if constexpr (check_number) {
if (p != last || ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
return {p, ec};
}
else {
auto [p, ec] = std::from_chars(first, last, value);
if constexpr (check_number) {
if (p != last || ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
}
return {p, ec};
}
}
// not support uint8 for now
template <typename T>
char *to_chars(char *buffer, T value) noexcept {
using U = std::decay_t<T>;
if constexpr (std::is_floating_point_v<U>) {
if constexpr (has_to_chars_float::value) {
return static_cast<char *>(to_chars_float(value, buffer));
}
else {
return jkj::dragonbox::to_chars(value, buffer);
}
}
else if constexpr (std::is_signed_v<U> && (sizeof(U) >= 8)) {
return xtoa(value, buffer, 10, 1); // int64_t
}
else if constexpr (std::is_unsigned_v<U> && (sizeof(U) >= 8)) {
return xtoa(value, buffer, 10, 0); // uint64_t
}
else if constexpr (std::is_integral_v<U> && (sizeof(U) > 1)) {
return itoa_fwd(value, buffer); // only support more than 2 bytes intergal
}
else if constexpr (!is_char_type<U>::value) {
return itoa_fwd(static_cast<int>(value),
buffer); // only support more than 2 bytes intergal
}
else {
static_assert(!sizeof(U), "only support arithmetic type except char type");
}
}
} // namespace detail
} // namespace iguana

View File

@ -0,0 +1,135 @@
#pragma once
namespace iguana {
struct sint32_t {
using value_type = int32_t;
int32_t val;
bool operator==(const sint32_t& other) const { return val == other.val; }
};
inline bool operator==(sint32_t value1, int32_t value2) {
return value1.val == value2;
}
// for key in std::map
inline bool operator<(const sint32_t& lhs, const sint32_t& rhs) {
return lhs.val < rhs.val;
}
struct sint64_t {
using value_type = int64_t;
int64_t val;
bool operator==(const sint64_t& other) const { return val == other.val; }
};
inline bool operator==(sint64_t value1, int64_t value2) {
return value1.val == value2;
}
inline bool operator<(const sint64_t& lhs, const sint64_t& rhs) {
return lhs.val < rhs.val;
}
struct fixed32_t {
using value_type = uint32_t;
uint32_t val;
bool operator==(const fixed32_t& other) const { return val == other.val; }
};
inline bool operator==(fixed32_t value1, uint32_t value2) {
return value1.val == value2;
}
inline bool operator<(const fixed32_t& lhs, const fixed32_t& rhs) {
return lhs.val < rhs.val;
}
struct fixed64_t {
using value_type = uint64_t;
uint64_t val;
bool operator==(const fixed64_t& other) const { return val == other.val; }
};
inline bool operator==(fixed64_t value1, uint64_t value2) {
return value1.val == value2;
}
inline bool operator<(const fixed64_t& lhs, const fixed64_t& rhs) {
return lhs.val < rhs.val;
}
struct sfixed32_t {
using value_type = int32_t;
int32_t val;
bool operator==(const sfixed32_t& other) const { return val == other.val; }
};
inline bool operator==(sfixed32_t value1, int32_t value2) {
return value1.val == value2;
}
inline bool operator<(const sfixed32_t& lhs, const sfixed32_t& rhs) {
return lhs.val < rhs.val;
}
struct sfixed64_t {
using value_type = int64_t;
int64_t val;
bool operator==(const sfixed64_t& other) const { return val == other.val; }
};
inline bool operator==(sfixed64_t value1, int64_t value2) {
return value1.val == value2;
}
inline bool operator<(const sfixed64_t& lhs, const sfixed64_t& rhs) {
return lhs.val < rhs.val;
}
} // namespace iguana
// for key in std::unordered_map
namespace std {
template <>
struct hash<iguana::sint32_t> {
size_t operator()(const iguana::sint32_t& x) const noexcept {
return std::hash<int32_t>()(x.val);
}
};
template <>
struct hash<iguana::sint64_t> {
size_t operator()(const iguana::sint64_t& x) const noexcept {
return std::hash<int64_t>()(x.val);
}
};
template <>
struct hash<iguana::fixed32_t> {
size_t operator()(const iguana::fixed32_t& x) const noexcept {
return std::hash<uint32_t>()(x.val);
}
};
template <>
struct hash<iguana::fixed64_t> {
size_t operator()(const iguana::fixed64_t& x) const noexcept {
return std::hash<uint64_t>()(x.val);
}
};
template <>
struct hash<iguana::sfixed32_t> {
size_t operator()(const iguana::sfixed32_t& x) const noexcept {
return std::hash<int32_t>()(x.val);
}
};
template <>
struct hash<iguana::sfixed64_t> {
size_t operator()(const iguana::sfixed64_t& x) const noexcept {
return std::hash<int64_t>()(x.val);
}
};
} // namespace std

View File

@ -0,0 +1,131 @@
#pragma once
#include <cstddef>
#include <string>
#include <utility>
#include <vector>
namespace iguana::detail {
#if __cpp_lib_string_resize_and_overwrite >= 202110L
template <typename ch>
inline void resize(std::basic_string<ch> &str, std::size_t sz) {
str.resize_and_overwrite(sz, [sz](ch *, std::size_t) {
return sz;
});
}
#elif (defined(_MSC_VER) && _MSC_VER <= 1920)
// old msvc don't support visit private, discard it.
#else
template <typename Function, Function func_ptr>
class string_thief {
public:
friend void string_set_length_hacker(std::string &self, std::size_t sz) {
#if defined(_MSVC_STL_VERSION)
(self.*func_ptr)._Myval2._Mysize = sz;
#else
#if defined(_LIBCPP_VERSION)
(self.*func_ptr)(sz);
#else
#if (_GLIBCXX_USE_CXX11_ABI == 0) && defined(__GLIBCXX__)
(self.*func_ptr)()->_M_set_length_and_sharable(sz);
#else
#if defined(__GLIBCXX__)
(self.*func_ptr)(sz);
#endif
#endif
#endif
#endif
}
};
#if defined(__GLIBCXX__) // libstdc++
#if (_GLIBCXX_USE_CXX11_ABI == 0)
template class string_thief<decltype(&std::string::_M_rep),
&std::string::_M_rep>;
#else
template class string_thief<decltype(&std::string::_M_set_length),
&std::string::_M_set_length>;
#endif
#elif defined(_LIBCPP_VERSION)
template class string_thief<decltype(&std::string::__set_size),
&std::string::__set_size>;
#elif defined(_MSVC_STL_VERSION)
template class string_thief<decltype(&std::string::_Mypair),
&std::string::_Mypair>;
#endif
void string_set_length_hacker(std::string &, std::size_t);
template <typename ch>
inline void resize(std::basic_string<ch> &raw_str, std::size_t sz) {
std::string &str = *reinterpret_cast<std::string *>(&raw_str);
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
defined(_MSVC_STL_VERSION)
if (sz > str.capacity()) {
str.reserve(sz);
}
string_set_length_hacker(str, sz);
str[sz] = '\0';
#else
raw_str.resize(sz);
#endif
}
#endif
#if (defined(_MSC_VER) && _MSC_VER <= 1920)
#else
void vector_set_length_hacker(std::vector<char> &self, std::size_t sz);
template <typename Function, Function func_ptr>
class vector_thief {
public:
friend void vector_set_length_hacker(std::vector<char> &self,
std::size_t sz) {
#if defined(_MSVC_STL_VERSION)
(self.*func_ptr)._Myval2._Mylast = self.data() + sz;
#else
#if defined(_LIBCPP_VERSION)
#if _LIBCPP_VERSION < 14000
((*(std::__vector_base<char, std::allocator<char> > *)(&self)).*func_ptr) =
self.data() + sz;
#else
(self.*func_ptr) = self.data() + sz;
#endif
#else
#if defined(__GLIBCXX__)
((*(std::_Vector_base<char, std::allocator<char> > *)(&self)).*func_ptr)
._M_finish = self.data() + sz;
#endif
#endif
#endif
}
};
#if defined(__GLIBCXX__) // libstdc++
template class vector_thief<decltype(&std::vector<char>::_M_impl),
&std::vector<char>::_M_impl>;
#elif defined(_LIBCPP_VERSION)
template class vector_thief<decltype(&std::vector<char>::__end_),
&std::vector<char>::__end_>;
#elif defined(_MSVC_STL_VERSION)
template class vector_thief<decltype(&std::vector<char>::_Mypair),
&std::vector<char>::_Mypair>;
#endif
template <typename ch>
inline void resize(std::vector<ch> &raw_vec, std::size_t sz) {
#if defined(__GLIBCXX__) || \
(defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_ASAN)) || \
defined(_MSVC_STL_VERSION)
std::vector<char> &vec = *reinterpret_cast<std::vector<char> *>(&raw_vec);
vec.reserve(sz);
vector_set_length_hacker(vec, sz);
#else
raw_vec.resize(sz);
#endif
}
#endif
}; // namespace iguana::detail

View File

@ -67,8 +67,51 @@ template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>> struct has_type<T, std::tuple<Us...>>
: std::disjunction<std::is_same<T, Us>...> {}; : std::disjunction<std::is_same<T, Us>...> {};
template <class T>
struct member_tratis {};
template <class T, class Owner>
struct member_tratis<T Owner::*> {
using owner_type = Owner;
using value_type = T;
};
template <typename T> template <typename T>
inline constexpr bool is_int64_v = inline constexpr bool is_int64_v =
std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>; std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t>;
template <typename T>
struct is_variant : std::false_type {};
template <typename... T>
struct is_variant<std::variant<T...>> : std::true_type {};
template <class T>
struct member_traits {
using value_type = T;
};
template <class T, class Owner>
struct member_traits<T Owner::*> {
using owner_type = Owner;
using value_type = T;
};
template <class T>
using member_value_type_t = typename member_traits<T>::value_type;
template <typename T, std::size_t I, typename = void>
struct variant_type_at {
using type = T;
};
template <typename T, std::size_t I>
struct variant_type_at<T, I, std::enable_if_t<is_variant<T>::value>> {
using type = std::variant_alternative_t<I, T>;
};
template <typename T, std::size_t I>
using variant_type_at_t =
typename variant_type_at<typename member_traits<T>::value_type, I>::type;
} // namespace iguana } // namespace iguana
#endif // SERIALIZE_TRAITS_HPP #endif // SERIALIZE_TRAITS_HPP

View File

@ -11,13 +11,13 @@ namespace detail {
template <typename U, typename It, template <typename U, typename It,
std::enable_if_t<sequence_container_v<U>, int> = 0> std::enable_if_t<sequence_container_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end); IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end);
template <typename U, typename It, std::enable_if_t<smart_ptr_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<smart_ptr_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end); IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end);
template <typename U, typename It, std::enable_if_t<refletable_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<refletable_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
from_json(value, it, end); from_json(value, it, end);
} }
@ -61,15 +61,15 @@ IGUANA_INLINE void parse_escape(U &value, It &&it, It &&end) {
} }
template <typename U, typename It, std::enable_if_t<num_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<num_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
if constexpr (contiguous_iterator<std::decay_t<It>>) { if constexpr (contiguous_iterator<std::decay_t<It>>) {
const auto size = std::distance(it, end); const auto size = std::distance(it, end);
if (size == 0) if (size == 0)
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
const auto start = &*it; const auto start = &*it;
auto [p, ec] = detail::from_chars(start, start + size, value); auto [p, ec] = detail::from_chars<false>(start, start + size, value);
if (ec != std::errc{}) if (ec != std::errc{} || !can_follow_number(*p))
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
it += (p - &*it); it += (p - &*it);
} }
@ -82,14 +82,12 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
buffer[i] = *it++; buffer[i] = *it++;
++i; ++i;
} }
auto [p, ec] = detail::from_chars(buffer, buffer + i, value); detail::from_chars(buffer, buffer + i, value);
if (ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
} }
} }
template <typename U, typename It, std::enable_if_t<numeric_str_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<numeric_str_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
auto start = it; auto start = it;
while (it != end && is_numeric(*it)) { while (it != end && is_numeric(*it)) {
@ -101,7 +99,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
template <bool skip = false, typename U, typename It, template <bool skip = false, typename U, typename It,
std::enable_if_t<char_v<U>, int> = 0> std::enable_if_t<char_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
if constexpr (!skip) { if constexpr (!skip) {
skip_ws(it, end); skip_ws(it, end);
match<'"'>(it, end); match<'"'>(it, end);
@ -140,7 +138,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
} }
template <typename U, typename It, std::enable_if_t<bool_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<bool_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &&value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &&value, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
if (it < end) if (it < end)
@ -166,7 +164,7 @@ IGUANA_INLINE void parse_item(U &&value, It &&it, It &&end) {
template <bool skip = false, typename U, typename It, template <bool skip = false, typename U, typename It,
std::enable_if_t<string_v<U>, int> = 0> std::enable_if_t<string_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
if constexpr (!skip) { if constexpr (!skip) {
skip_ws(it, end); skip_ws(it, end);
match<'"'>(it, end); match<'"'>(it, end);
@ -208,7 +206,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
template <bool skip = false, typename U, typename It, template <bool skip = false, typename U, typename It,
std::enable_if_t<string_view_v<U>, int> = 0> std::enable_if_t<string_view_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
static_assert(contiguous_iterator<std::decay_t<It>>, "must be contiguous"); static_assert(contiguous_iterator<std::decay_t<It>>, "must be contiguous");
if constexpr (!skip) { if constexpr (!skip) {
skip_ws(it, end); skip_ws(it, end);
@ -229,16 +227,16 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
} }
template <typename U, typename It, std::enable_if_t<enum_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<enum_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
static constexpr auto str_to_enum = get_enum_map<true, std::decay_t<U>>(); static constexpr auto str_to_enum = get_enum_map<true, std::decay_t<U>>();
if constexpr (bool_v<decltype(str_to_enum)>) { if constexpr (bool_v<decltype(str_to_enum)>) {
// not defined a specialization template // not defined a specialization template
using T = std::underlying_type_t<std::decay_t<U>>; using T = std::underlying_type_t<std::decay_t<U>>;
parse_item(reinterpret_cast<T &>(value), it, end); from_json_impl(reinterpret_cast<T &>(value), it, end);
} }
else { else {
std::string_view enum_names; std::string_view enum_names;
parse_item(enum_names, it, end); from_json_impl(enum_names, it, end);
auto it = str_to_enum.find(enum_names); auto it = str_to_enum.find(enum_names);
if (it != str_to_enum.end()) if (it != str_to_enum.end())
IGUANA_LIKELY { value = it->second; } IGUANA_LIKELY { value = it->second; }
@ -250,7 +248,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
} }
template <typename U, typename It, std::enable_if_t<fixed_array_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<fixed_array_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
using T = std::remove_reference_t<U>; using T = std::remove_reference_t<U>;
constexpr auto n = sizeof(T) / sizeof(decltype(std::declval<T>()[0])); constexpr auto n = sizeof(T) / sizeof(decltype(std::declval<T>()[0]));
skip_ws(it, end); skip_ws(it, end);
@ -262,7 +260,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
auto value_it = std::begin(value); auto value_it = std::begin(value);
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
if (*it != '"') if (*it != '"')
IGUANA_LIKELY { parse_item<true>(*value_it++, it, end); } IGUANA_LIKELY { from_json_impl<true>(*value_it++, it, end); }
} }
match<'"'>(it, end); match<'"'>(it, end);
return; return;
@ -280,7 +278,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
} }
auto value_it = std::begin(value); auto value_it = std::begin(value);
for (size_t i = 0; i < n; ++i) { for (size_t i = 0; i < n; ++i) {
parse_item(*value_it++, it, end); from_json_impl(*value_it++, it, end);
skip_ws(it, end); skip_ws(it, end);
if (it == end) { if (it == end) {
throw std::runtime_error("Unexpected end"); throw std::runtime_error("Unexpected end");
@ -301,7 +299,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
template <typename U, typename It, template <typename U, typename It,
std::enable_if_t<sequence_container_v<U>, int>> std::enable_if_t<sequence_container_v<U>, int>>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
value.clear(); value.clear();
skip_ws(it, end); skip_ws(it, end);
@ -316,7 +314,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
if (i > 0) if (i > 0)
IGUANA_LIKELY { match<','>(it, end); } IGUANA_LIKELY { match<','>(it, end); }
parse_item(value.emplace_back(), it, end); from_json_impl(value.emplace_back(), it, end);
skip_ws(it, end); skip_ws(it, end);
} }
throw std::runtime_error("Expected ]"); throw std::runtime_error("Expected ]");
@ -337,7 +335,7 @@ IGUANA_INLINE auto get_key(It &&it, It &&end) {
// compile time versions of keys // compile time versions of keys
it = start; it = start;
static thread_local std::string static_key{}; static thread_local std::string static_key{};
detail::parse_item<true>(static_key, it, end); detail::from_json_impl<true>(static_key, it, end);
return std::string_view(static_key); return std::string_view(static_key);
} }
else else
@ -352,14 +350,14 @@ IGUANA_INLINE auto get_key(It &&it, It &&end) {
} }
else { else {
static thread_local std::string static_key{}; static thread_local std::string static_key{};
detail::parse_item(static_key, it, end); detail::from_json_impl(static_key, it, end);
return std::string_view(static_key); return std::string_view(static_key);
} }
} }
template <typename U, typename It, template <typename U, typename It,
std::enable_if_t<map_container_v<U>, int> = 0> std::enable_if_t<map_container_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
using T = std::remove_reference_t<U>; using T = std::remove_reference_t<U>;
using key_type = typename T::key_type; using key_type = typename T::key_type;
skip_ws(it, end); skip_ws(it, end);
@ -384,19 +382,19 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
match<':'>(it, end); match<':'>(it, end);
if constexpr (string_v<key_type> || string_view_v<key_type>) { if constexpr (string_v<key_type> || string_view_v<key_type>) {
parse_item(value[key_type(key)], it, end); from_json_impl(value[key_type(key)], it, end);
} }
else { else {
static thread_local key_type key_value{}; static thread_local key_type key_value{};
parse_item(key_value, key.begin(), key.end()); from_json_impl(key_value, key.begin(), key.end());
parse_item(value[key_value], it, end); from_json_impl(value[key_value], it, end);
} }
skip_ws(it, end); skip_ws(it, end);
} }
} }
template <typename U, typename It, std::enable_if_t<tuple_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<tuple_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
match<'['>(it, end); match<'['>(it, end);
skip_ws(it, end); skip_ws(it, end);
@ -410,7 +408,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
match<','>(it, end); match<','>(it, end);
skip_ws(it, end); skip_ws(it, end);
} }
parse_item(v, it, end); from_json_impl(v, it, end);
skip_ws(it, end); skip_ws(it, end);
}); });
@ -418,7 +416,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
} }
template <typename U, typename It, std::enable_if_t<optional_v<U>, int> = 0> template <typename U, typename It, std::enable_if_t<optional_v<U>, int> = 0>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
if (it < end && *it == '"') if (it < end && *it == '"')
IGUANA_LIKELY { ++it; } IGUANA_LIKELY { ++it; }
@ -439,17 +437,17 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
using value_type = typename T::value_type; using value_type = typename T::value_type;
value_type t; value_type t;
if constexpr (string_v<value_type> || string_view_v<value_type>) { if constexpr (string_v<value_type> || string_view_v<value_type>) {
parse_item<true>(t, it, end); from_json_impl<true>(t, it, end);
} }
else { else {
parse_item(t, it, end); from_json_impl(t, it, end);
} }
value = std::move(t); value = std::move(t);
} }
} }
template <typename U, typename It, std::enable_if_t<smart_ptr_v<U>, int>> template <typename U, typename It, std::enable_if_t<smart_ptr_v<U>, int>>
IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) { IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
if (it == end) if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Unexexpected eof"); } IGUANA_UNLIKELY { throw std::runtime_error("Unexexpected eof"); }
@ -465,7 +463,7 @@ IGUANA_INLINE void parse_item(U &value, It &&it, It &&end) {
else { else {
value = std::make_shared<value_type>(); value = std::make_shared<value_type>();
} }
parse_item(*value, it, end); from_json_impl(*value, it, end);
} }
} }
@ -499,6 +497,44 @@ IGUANA_INLINE void skip_object_value(It &&it, It &&end) {
} }
} }
template <typename value_type, typename U, typename It>
IGUANA_INLINE bool from_json_variant_impl(U &value, It it, It end, It &temp_it,
It &temp_end) {
try {
value_type val;
from_json_impl(val, it, end);
value = val;
temp_it = it;
temp_end = end;
return true;
} catch (std::exception &ex) {
return false;
}
}
template <typename U, typename It, size_t... Idx>
IGUANA_INLINE void from_json_variant(U &value, It &it, It &end,
std::index_sequence<Idx...>) {
static_assert(!has_duplicate_type_v<std::remove_reference_t<U>>,
"don't allow same type in std::variant");
bool r = false;
It temp_it = it;
It temp_end = end;
((void)(!r && (r = from_json_variant_impl<
variant_element_t<Idx, std::remove_reference_t<U>>>(
value, it, end, temp_it, temp_end),
true)),
...);
it = temp_it;
end = temp_end;
}
template <typename U, typename It, std::enable_if_t<variant_v<U>, int> = 0>
IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) {
from_json_variant(value, it, end,
std::make_index_sequence<
std::variant_size_v<std::remove_reference_t<U>>>{});
}
} // namespace detail } // namespace detail
template <typename T, typename It, std::enable_if_t<refletable_v<T>, int>> template <typename T, typename It, std::enable_if_t<refletable_v<T>, int>>
@ -522,7 +558,10 @@ IGUANA_INLINE void from_json(T &value, It &&it, It &&end) {
IGUANA_UNLIKELY { return; } IGUANA_UNLIKELY { return; }
skip_ws(it, end); skip_ws(it, end);
match<':'>(it, end); match<':'>(it, end);
detail::parse_item(value.*member_ptr, it, end); {
using namespace detail;
from_json_impl(value.*member_ptr, it, end);
}
skip_ws(it, end); skip_ws(it, end);
if (*it == '}') if (*it == '}')
@ -551,7 +590,8 @@ IGUANA_INLINE void from_json(T &value, It &&it, It &&end) {
[&](auto &&member_ptr) IGUANA__INLINE_LAMBDA { [&](auto &&member_ptr) IGUANA__INLINE_LAMBDA {
using V = std::decay_t<decltype(member_ptr)>; using V = std::decay_t<decltype(member_ptr)>;
if constexpr (std::is_member_pointer_v<V>) { if constexpr (std::is_member_pointer_v<V>) {
detail::parse_item(value.*member_ptr, it, end); using namespace detail;
from_json_impl(value.*member_ptr, it, end);
} }
else { else {
static_assert(!sizeof(V), "type not supported"); static_assert(!sizeof(V), "type not supported");
@ -583,7 +623,8 @@ IGUANA_INLINE void from_json(T &value, It &&it, It &&end) {
template <typename T, typename It, template <typename T, typename It,
std::enable_if_t<non_refletable_v<T>, int> = 0> std::enable_if_t<non_refletable_v<T>, int> = 0>
IGUANA_INLINE void from_json(T &value, It &&it, It &&end) { IGUANA_INLINE void from_json(T &value, It &&it, It &&end) {
detail::parse_item(value, it, end); using namespace detail;
from_json_impl(value, it, end);
} }
template <typename T, typename It> template <typename T, typename It>
@ -603,6 +644,31 @@ IGUANA_INLINE void from_json(T &value, const View &view) {
from_json(value, std::begin(view), std::end(view)); from_json(value, std::begin(view), std::end(view));
} }
template <
auto member,
typename Parant = typename member_tratis<decltype(member)>::owner_type,
typename T>
IGUANA_INLINE void from_json(T &value, std::string_view str) {
constexpr size_t duplicate_count =
iguana::duplicate_count<std::remove_reference_t<Parant>, member>();
static_assert(duplicate_count != 1, "the member is not belong to the object");
static_assert(duplicate_count == 2, "has duplicate field name");
constexpr auto name = name_of<member>();
constexpr size_t index = index_of<member>();
constexpr size_t member_count = member_count_of<member>();
str = str.substr(str.find(name) + name.size());
size_t pos = str.find(":") + 1;
if constexpr (index == member_count - 1) { // last field
str = str.substr(pos, str.find("}") - pos + 1);
}
else {
str = str.substr(pos, str.find(",") - pos);
}
detail::from_json_impl(value.*member, std::begin(str), std::end(str));
}
template <typename T, typename View, template <typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0> std::enable_if_t<json_view_v<View>, int> = 0>
IGUANA_INLINE void from_json(T &value, const View &view, IGUANA_INLINE void from_json(T &value, const View &view,
@ -635,10 +701,11 @@ IGUANA_INLINE void from_json(T &value, const Byte *data, size_t size,
} }
template <bool Is_view = false, typename It> template <bool Is_view = false, typename It>
void parse(jvalue &result, It &&it, It &&end); void parse(jvalue &result, It &&it, It &&end, bool parse_as_double = false);
template <bool Is_view = false, typename It> template <bool Is_view = false, typename It>
inline void parse(jarray &result, It &&it, It &&end) { inline void parse(jarray &result, It &&it, It &&end,
bool parse_as_double = false) {
skip_ws(it, end); skip_ws(it, end);
match<'['>(it, end); match<'['>(it, end);
if (*it == ']') if (*it == ']')
@ -652,7 +719,7 @@ inline void parse(jarray &result, It &&it, It &&end) {
} }
result.emplace_back(); result.emplace_back();
parse<Is_view>(result.back(), it, end); parse<Is_view>(result.back(), it, end, parse_as_double);
if (*it == ']') if (*it == ']')
IGUANA_UNLIKELY { IGUANA_UNLIKELY {
@ -666,7 +733,8 @@ inline void parse(jarray &result, It &&it, It &&end) {
} }
template <bool Is_view = false, typename It> template <bool Is_view = false, typename It>
inline void parse(jobject &result, It &&it, It &&end) { inline void parse(jobject &result, It &&it, It &&end,
bool parse_as_double = false) {
skip_ws(it, end); skip_ws(it, end);
match<'{'>(it, end); match<'{'>(it, end);
if (*it == '}') if (*it == '}')
@ -681,7 +749,8 @@ inline void parse(jobject &result, It &&it, It &&end) {
if (it == end) if (it == end)
IGUANA_UNLIKELY { throw std::runtime_error("Expected }"); } IGUANA_UNLIKELY { throw std::runtime_error("Expected }"); }
std::string key; std::string key;
detail::parse_item(key, it, end); using namespace detail;
from_json_impl(key, it, end);
auto emplaced = result.try_emplace(key); auto emplaced = result.try_emplace(key);
if (!emplaced.second) if (!emplaced.second)
@ -689,7 +758,7 @@ inline void parse(jobject &result, It &&it, It &&end) {
match<':'>(it, end); match<':'>(it, end);
parse<Is_view>(emplaced.first->second, it, end); parse<Is_view>(emplaced.first->second, it, end, parse_as_double);
if (*it == '}') if (*it == '}')
IGUANA_UNLIKELY { IGUANA_UNLIKELY {
@ -702,7 +771,8 @@ inline void parse(jobject &result, It &&it, It &&end) {
} }
template <bool Is_view, typename It> template <bool Is_view, typename It>
inline void parse(jvalue &result, It &&it, It &&end) { inline void parse(jvalue &result, It &&it, It &&end, bool parse_as_double) {
using namespace detail;
skip_ws(it, end); skip_ws(it, end);
switch (*it) { switch (*it) {
case 'n': case 'n':
@ -713,7 +783,7 @@ inline void parse(jvalue &result, It &&it, It &&end) {
case 'f': case 'f':
case 't': case 't':
detail::parse_item(result.template emplace<bool>(), it, end); from_json_impl(result.template emplace<bool>(), it, end);
break; break;
case '0': case '0':
case '1': case '1':
@ -727,8 +797,8 @@ inline void parse(jvalue &result, It &&it, It &&end) {
case '9': case '9':
case '-': { case '-': {
double d{}; double d{};
detail::parse_item(d, it, end); from_json_impl(d, it, end);
if (static_cast<int>(d) == d) if (!parse_as_double && (static_cast<int>(d) == d))
result.emplace<int>(d); result.emplace<int>(d);
else else
result.emplace<double>(d); result.emplace<double>(d);
@ -737,20 +807,20 @@ inline void parse(jvalue &result, It &&it, It &&end) {
case '"': case '"':
if constexpr (Is_view) { if constexpr (Is_view) {
result.template emplace<std::string_view>(); result.template emplace<std::string_view>();
detail::parse_item(std::get<std::string_view>(result), it, end); from_json_impl(std::get<std::string_view>(result), it, end);
} }
else { else {
result.template emplace<std::string>(); result.template emplace<std::string>();
detail::parse_item(std::get<std::string>(result), it, end); from_json_impl(std::get<std::string>(result), it, end);
} }
break; break;
case '[': case '[':
result.template emplace<jarray>(); result.template emplace<jarray>();
parse<Is_view>(std::get<jarray>(result), it, end); parse<Is_view>(std::get<jarray>(result), it, end, parse_as_double);
break; break;
case '{': { case '{': {
result.template emplace<jobject>(); result.template emplace<jobject>();
parse<Is_view>(std::get<jobject>(result), it, end); parse<Is_view>(std::get<jobject>(result), it, end, parse_as_double);
break; break;
} }
default: default:
@ -760,11 +830,13 @@ inline void parse(jvalue &result, It &&it, It &&end) {
skip_ws(it, end); skip_ws(it, end);
} }
// when Is_view is true, parse str as string_view // set Is_view == true, parse str as std::string_view
// set parse_as_double == true, parse the number as double in any case
template <bool Is_view = false, typename It> template <bool Is_view = false, typename It>
inline void parse(jvalue &result, It &&it, It &&end, std::error_code &ec) { inline void parse(jvalue &result, It &&it, It &&end, std::error_code &ec,
bool parse_as_double = false) {
try { try {
parse<Is_view>(result, it, end); parse<Is_view>(result, it, end, parse_as_double);
ec = {}; ec = {};
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
result.template emplace<std::nullptr_t>(); result.template emplace<std::nullptr_t>();
@ -774,15 +846,16 @@ inline void parse(jvalue &result, It &&it, It &&end, std::error_code &ec) {
template <bool Is_view = false, typename T, typename View, template <bool Is_view = false, typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0> std::enable_if_t<json_view_v<View>, int> = 0>
inline void parse(T &result, const View &view) { inline void parse(T &result, const View &view, bool parse_as_double = false) {
parse<Is_view>(result, std::begin(view), std::end(view)); parse<Is_view>(result, std::begin(view), std::end(view), parse_as_double);
} }
template <bool Is_view = false, typename T, typename View, template <bool Is_view = false, typename T, typename View,
std::enable_if_t<json_view_v<View>, int> = 0> std::enable_if_t<json_view_v<View>, int> = 0>
inline void parse(T &result, const View &view, std::error_code &ec) noexcept { inline void parse(T &result, const View &view, std::error_code &ec,
bool parse_as_double = false) noexcept {
try { try {
parse<Is_view>(result, view); parse<Is_view>(result, view, parse_as_double);
ec = {}; ec = {};
} catch (std::runtime_error &e) { } catch (std::runtime_error &e) {
ec = iguana::make_error_code(e.what()); ec = iguana::make_error_code(e.what());

View File

@ -18,10 +18,7 @@ class numeric_str {
if (val_.empty()) if (val_.empty())
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
T res; T res;
auto [_, ec] =
detail::from_chars(val_.data(), val_.data() + val_.size(), res); detail::from_chars(val_.data(), val_.data() + val_.size(), res);
if (ec != std::errc{})
IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); }
return res; return res;
} }
@ -214,4 +211,28 @@ IGUANA_INLINE bool is_numeric(char c) noexcept {
return static_cast<bool>(is_num[static_cast<unsigned int>(c)]); return static_cast<bool>(is_num[static_cast<unsigned int>(c)]);
} }
// '\t' '\r' '\n' '"' '}' ']' ',' ' ' '\0'
IGUANA_INLINE bool can_follow_number(char c) noexcept {
static constexpr int can_follow_num[256] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 2
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 5
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F
};
return static_cast<bool>(can_follow_num[static_cast<unsigned int>(c)]);
}
} // namespace iguana } // namespace iguana

View File

@ -11,33 +11,33 @@ namespace iguana {
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<refletable_v<T>, int> = 0> std::enable_if_t<refletable_v<T>, int> = 0>
IGUANA_INLINE void to_json(T &&t, Stream &s); IGUANA_INLINE void to_json(T &&t, Stream &s);
namespace detail {
template <bool Is_writing_escape = true, typename Stream, typename T> template <bool Is_writing_escape = true, typename Stream, typename T>
IGUANA_INLINE void render_json_value(Stream &ss, std::optional<T> &val); IGUANA_INLINE void to_json_impl(Stream &ss, std::optional<T> &val);
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<fixed_array_v<T>, int> = 0> std::enable_if_t<fixed_array_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, const T &t); IGUANA_INLINE void to_json_impl(Stream &ss, const T &t);
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<sequence_container_v<T>, int> = 0> std::enable_if_t<sequence_container_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, const T &v); IGUANA_INLINE void to_json_impl(Stream &ss, const T &v);
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<smart_ptr_v<T>, int> = 0> std::enable_if_t<smart_ptr_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, const T &v); IGUANA_INLINE void to_json_impl(Stream &ss, const T &v);
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<map_container_v<T>, int> = 0> std::enable_if_t<map_container_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, const T &o); IGUANA_INLINE void to_json_impl(Stream &ss, const T &o);
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<tuple_v<T>, int> = 0> std::enable_if_t<tuple_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &s, T &&t); IGUANA_INLINE void to_json_impl(Stream &s, T &&t);
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<variant_v<T>, int> = 0> std::enable_if_t<variant_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &s, T &&t); IGUANA_INLINE void to_json_impl(Stream &s, T &&t);
template <typename Stream, typename InputIt, typename T, typename F> template <typename Stream, typename InputIt, typename T, typename F>
IGUANA_INLINE void join(Stream &ss, InputIt first, InputIt last, const T &delim, IGUANA_INLINE void join(Stream &ss, InputIt first, InputIt last, const T &delim,
@ -52,17 +52,17 @@ IGUANA_INLINE void join(Stream &ss, InputIt first, InputIt last, const T &delim,
} }
template <bool Is_writing_escape, typename Stream> template <bool Is_writing_escape, typename Stream>
IGUANA_INLINE void render_json_value(Stream &ss, std::nullptr_t) { IGUANA_INLINE void to_json_impl(Stream &ss, std::nullptr_t) {
ss.append("null"); ss.append("null");
} }
template <bool Is_writing_escape, typename Stream> template <bool Is_writing_escape, typename Stream>
IGUANA_INLINE void render_json_value(Stream &ss, bool b) { IGUANA_INLINE void to_json_impl(Stream &ss, bool b) {
ss.append(b ? "true" : "false"); ss.append(b ? "true" : "false");
}; };
template <bool Is_writing_escape, typename Stream> template <bool Is_writing_escape, typename Stream>
IGUANA_INLINE void render_json_value(Stream &ss, char value) { IGUANA_INLINE void to_json_impl(Stream &ss, char value) {
ss.append("\""); ss.append("\"");
ss.push_back(value); ss.push_back(value);
ss.append("\""); ss.append("\"");
@ -70,7 +70,7 @@ IGUANA_INLINE void render_json_value(Stream &ss, char value) {
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<num_v<T>, int> = 0> std::enable_if_t<num_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, T value) { IGUANA_INLINE void to_json_impl(Stream &ss, T value) {
char temp[65]; char temp[65];
auto p = detail::to_chars(temp, value); auto p = detail::to_chars(temp, value);
ss.append(temp, p - temp); ss.append(temp, p - temp);
@ -78,13 +78,13 @@ IGUANA_INLINE void render_json_value(Stream &ss, T value) {
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<numeric_str_v<T>, int> = 0> std::enable_if_t<numeric_str_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, T v) { IGUANA_INLINE void to_json_impl(Stream &ss, T v) {
ss.append(v.value().data(), v.value().size()); ss.append(v.value().data(), v.value().size());
} }
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<string_container_v<T>, int> = 0> std::enable_if_t<string_container_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, T &&t) { IGUANA_INLINE void to_json_impl(Stream &ss, T &&t) {
ss.push_back('"'); ss.push_back('"');
if constexpr (Is_writing_escape) { if constexpr (Is_writing_escape) {
write_string_with_escape(t.data(), t.size(), ss); write_string_with_escape(t.data(), t.size(), ss);
@ -99,28 +99,28 @@ template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<num_v<T>, int> = 0> std::enable_if_t<num_v<T>, int> = 0>
IGUANA_INLINE void render_key(Stream &ss, T &t) { IGUANA_INLINE void render_key(Stream &ss, T &t) {
ss.push_back('"'); ss.push_back('"');
render_json_value<Is_writing_escape>(ss, t); to_json_impl<Is_writing_escape>(ss, t);
ss.push_back('"'); ss.push_back('"');
} }
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<string_container_v<T>, int> = 0> std::enable_if_t<string_container_v<T>, int> = 0>
IGUANA_INLINE void render_key(Stream &ss, T &&t) { IGUANA_INLINE void render_key(Stream &ss, T &&t) {
render_json_value<Is_writing_escape>(ss, std::forward<T>(t)); to_json_impl<Is_writing_escape>(ss, std::forward<T>(t));
} }
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<refletable_v<T>, int> = 0> std::enable_if_t<refletable_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, T &&t) { IGUANA_INLINE void to_json_impl(Stream &ss, T &&t) {
to_json(std::forward<T>(t), ss); to_json(std::forward<T>(t), ss);
} }
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<enum_v<T>, int> = 0> std::enable_if_t<enum_v<T>, int> = 0>
IGUANA_INLINE void render_json_value(Stream &ss, T val) { IGUANA_INLINE void to_json_impl(Stream &ss, T val) {
static constexpr auto enum_to_str = get_enum_map<false, std::decay_t<T>>(); static constexpr auto enum_to_str = get_enum_map<false, std::decay_t<T>>();
if constexpr (bool_v<decltype(enum_to_str)>) { if constexpr (bool_v<decltype(enum_to_str)>) {
render_json_value<Is_writing_escape>( to_json_impl<Is_writing_escape>(
ss, static_cast<std::underlying_type_t<T>>(val)); ss, static_cast<std::underlying_type_t<T>>(val));
} }
else { else {
@ -128,7 +128,7 @@ IGUANA_INLINE void render_json_value(Stream &ss, T val) {
if (it != enum_to_str.end()) if (it != enum_to_str.end())
IGUANA_LIKELY { IGUANA_LIKELY {
auto str = it->second; auto str = it->second;
render_json_value<Is_writing_escape>( to_json_impl<Is_writing_escape>(
ss, std::string_view(str.data(), str.size())); ss, std::string_view(str.data(), str.size()));
} }
else { else {
@ -140,12 +140,12 @@ IGUANA_INLINE void render_json_value(Stream &ss, T val) {
} }
template <bool Is_writing_escape, typename Stream, typename T> template <bool Is_writing_escape, typename Stream, typename T>
IGUANA_INLINE void render_json_value(Stream &ss, std::optional<T> &val) { IGUANA_INLINE void to_json_impl(Stream &ss, std::optional<T> &val) {
if (!val) { if (!val) {
ss.append("null"); ss.append("null");
} }
else { else {
render_json_value<Is_writing_escape>(ss, *val); to_json_impl<Is_writing_escape>(ss, *val);
} }
} }
@ -154,14 +154,14 @@ IGUANA_INLINE void render_array(Stream &ss, const T &v) {
ss.push_back('['); ss.push_back('[');
join(ss, std::begin(v), std::end(v), ',', join(ss, std::begin(v), std::end(v), ',',
[&ss](const auto &jsv) IGUANA__INLINE_LAMBDA { [&ss](const auto &jsv) IGUANA__INLINE_LAMBDA {
render_json_value<Is_writing_escape>(ss, jsv); to_json_impl<Is_writing_escape>(ss, jsv);
}); });
ss.push_back(']'); ss.push_back(']');
} }
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<fixed_array_v<T>, int>> std::enable_if_t<fixed_array_v<T>, int>>
IGUANA_INLINE void render_json_value(Stream &ss, const T &t) { IGUANA_INLINE void to_json_impl(Stream &ss, const T &t) {
if constexpr (std::is_same_v<char, std::remove_reference_t< if constexpr (std::is_same_v<char, std::remove_reference_t<
decltype(std::declval<T>()[0])>>) { decltype(std::declval<T>()[0])>>) {
constexpr size_t n = sizeof(T) / sizeof(decltype(std::declval<T>()[0])); constexpr size_t n = sizeof(T) / sizeof(decltype(std::declval<T>()[0]));
@ -184,24 +184,24 @@ IGUANA_INLINE void render_json_value(Stream &ss, const T &t) {
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<map_container_v<T>, int>> std::enable_if_t<map_container_v<T>, int>>
IGUANA_INLINE void render_json_value(Stream &ss, const T &o) { IGUANA_INLINE void to_json_impl(Stream &ss, const T &o) {
ss.push_back('{'); ss.push_back('{');
join(ss, o.cbegin(), o.cend(), ',', join(ss, o.cbegin(), o.cend(), ',',
[&ss](const auto &jsv) IGUANA__INLINE_LAMBDA { [&ss](const auto &jsv) IGUANA__INLINE_LAMBDA {
render_key<Is_writing_escape>(ss, jsv.first); render_key<Is_writing_escape>(ss, jsv.first);
ss.push_back(':'); ss.push_back(':');
render_json_value<Is_writing_escape>(ss, jsv.second); to_json_impl<Is_writing_escape>(ss, jsv.second);
}); });
ss.push_back('}'); ss.push_back('}');
} }
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<sequence_container_v<T>, int>> std::enable_if_t<sequence_container_v<T>, int>>
IGUANA_INLINE void render_json_value(Stream &ss, const T &v) { IGUANA_INLINE void to_json_impl(Stream &ss, const T &v) {
ss.push_back('['); ss.push_back('[');
join(ss, v.cbegin(), v.cend(), ',', join(ss, v.cbegin(), v.cend(), ',',
[&ss](const auto &jsv) IGUANA__INLINE_LAMBDA { [&ss](const auto &jsv) IGUANA__INLINE_LAMBDA {
render_json_value<Is_writing_escape>(ss, jsv); to_json_impl<Is_writing_escape>(ss, jsv);
}); });
ss.push_back(']'); ss.push_back(']');
} }
@ -217,9 +217,9 @@ constexpr auto write_json_key = [](auto &s, auto i,
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<smart_ptr_v<T>, int>> std::enable_if_t<smart_ptr_v<T>, int>>
IGUANA_INLINE void render_json_value(Stream &ss, const T &v) { IGUANA_INLINE void to_json_impl(Stream &ss, const T &v) {
if (v) { if (v) {
render_json_value<Is_writing_escape>(ss, *v); to_json_impl<Is_writing_escape>(ss, *v);
} }
else { else {
ss.append("null"); ss.append("null");
@ -228,13 +228,13 @@ IGUANA_INLINE void render_json_value(Stream &ss, const T &v) {
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<tuple_v<T>, int>> std::enable_if_t<tuple_v<T>, int>>
IGUANA_INLINE void render_json_value(Stream &s, T &&t) { IGUANA_INLINE void to_json_impl(Stream &s, T &&t) {
using U = typename std::decay_t<T>; using U = typename std::decay_t<T>;
s.push_back('['); s.push_back('[');
constexpr size_t size = std::tuple_size_v<U>; constexpr size_t size = std::tuple_size_v<U>;
for_each(std::forward<T>(t), for_each(std::forward<T>(t),
[&s, size](auto &v, auto i) IGUANA__INLINE_LAMBDA { [&s, size](auto &v, auto i) IGUANA__INLINE_LAMBDA {
render_json_value<Is_writing_escape>(s, v); to_json_impl<Is_writing_escape>(s, v);
if (i != size - 1) if (i != size - 1)
IGUANA_LIKELY { s.push_back(','); } IGUANA_LIKELY { s.push_back(','); }
@ -244,17 +244,20 @@ IGUANA_INLINE void render_json_value(Stream &s, T &&t) {
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<variant_v<T>, int>> std::enable_if_t<variant_v<T>, int>>
IGUANA_INLINE void render_json_value(Stream &s, T &&t) { IGUANA_INLINE void to_json_impl(Stream &s, T &&t) {
static_assert(!has_duplicate_type_v<std::remove_reference_t<T>>,
"don't allow same type in std::variant");
std::visit( std::visit(
[&s](auto value) { [&s](auto value) {
render_json_value<Is_writing_escape>(s, value); to_json_impl<Is_writing_escape>(s, value);
}, },
t); t);
} }
} // namespace detail
template <bool Is_writing_escape, typename Stream, typename T, template <bool Is_writing_escape, typename Stream, typename T,
std::enable_if_t<refletable_v<T>, int>> std::enable_if_t<refletable_v<T>, int>>
IGUANA_INLINE void to_json(T &&t, Stream &s) { IGUANA_INLINE void to_json(T &&t, Stream &s) {
using namespace detail;
s.push_back('{'); s.push_back('{');
for_each(std::forward<T>(t), for_each(std::forward<T>(t),
[&t, &s](const auto &v, auto i) IGUANA__INLINE_LAMBDA { [&t, &s](const auto &v, auto i) IGUANA__INLINE_LAMBDA {
@ -265,7 +268,7 @@ IGUANA_INLINE void to_json(T &&t, Stream &s) {
write_json_key(s, i, t); write_json_key(s, i, t);
s.push_back(':'); s.push_back(':');
render_json_value<Is_writing_escape>(s, t.*v); to_json_impl<Is_writing_escape>(s, t.*v);
if (Idx < Count - 1) if (Idx < Count - 1)
IGUANA_LIKELY { s.push_back(','); } IGUANA_LIKELY { s.push_back(','); }
}); });
@ -275,7 +278,8 @@ IGUANA_INLINE void to_json(T &&t, Stream &s) {
template <bool Is_writing_escape = true, typename Stream, typename T, template <bool Is_writing_escape = true, typename Stream, typename T,
std::enable_if_t<non_refletable_v<T>, int> = 0> std::enable_if_t<non_refletable_v<T>, int> = 0>
IGUANA_INLINE void to_json(T &&t, Stream &s) { IGUANA_INLINE void to_json(T &&t, Stream &s) {
render_json_value<Is_writing_escape>(s, t); using namespace detail;
to_json_impl<Is_writing_escape>(s, t);
} }
} // namespace iguana } // namespace iguana

View File

@ -0,0 +1,257 @@
#pragma once
#include "detail/string_resize.hpp"
#include "pb_util.hpp"
namespace iguana {
namespace detail {
template <typename T>
IGUANA_INLINE void from_pb_impl(T& val, std::string_view& pb_str,
uint32_t field_no = 0);
template <typename T>
IGUANA_INLINE void decode_pair_value(T& val, std::string_view& pb_str) {
size_t pos;
uint32_t key = detail::decode_varint(pb_str, pos);
pb_str = pb_str.substr(pos);
WireType wire_type = static_cast<WireType>(key & 0b0111);
if (wire_type != detail::get_wire_type<std::remove_reference_t<T>>()) {
return;
}
from_pb_impl(val, pb_str);
}
template <typename T>
IGUANA_INLINE void from_pb_impl(T& val, std::string_view& pb_str,
uint32_t field_no) {
size_t pos = 0;
if constexpr (is_reflection_v<T>) {
size_t pos;
uint32_t size = detail::decode_varint(pb_str, pos);
pb_str = pb_str.substr(pos);
if (pb_str.size() < size)
IGUANA_UNLIKELY {
throw std::invalid_argument("Invalid fixed int value: too few bytes.");
}
if (size == 0) {
return;
}
from_pb(val, pb_str.substr(0, size));
pb_str = pb_str.substr(size);
}
else if constexpr (is_sequence_container<T>::value) {
using item_type = typename T::value_type;
if constexpr (is_lenprefix_v<item_type>) {
// item_type non-packed
while (!pb_str.empty()) {
item_type item{};
from_pb_impl(item, pb_str);
val.push_back(std::move(item));
if (pb_str.empty()) {
break;
}
uint32_t key = detail::decode_varint(pb_str, pos);
uint32_t field_number = key >> 3;
if (field_number != field_no) {
break;
}
else {
pb_str = pb_str.substr(pos);
}
}
}
else {
// item_type packed
size_t pos;
uint32_t size = detail::decode_varint(pb_str, pos);
pb_str = pb_str.substr(pos);
if (pb_str.size() < size)
IGUANA_UNLIKELY {
throw std::invalid_argument(
"Invalid fixed int value: too few bytes.");
}
using item_type = typename T::value_type;
size_t start = pb_str.size();
while (!pb_str.empty()) {
item_type item;
from_pb_impl(item, pb_str);
val.push_back(std::move(item));
if (start - pb_str.size() == size) {
break;
}
}
}
}
else if constexpr (is_map_container<T>::value) {
using item_type = std::pair<typename T::key_type, typename T::mapped_type>;
while (!pb_str.empty()) {
size_t pos;
uint32_t size = detail::decode_varint(pb_str, pos);
pb_str = pb_str.substr(pos);
if (pb_str.size() < size)
IGUANA_UNLIKELY {
throw std::invalid_argument(
"Invalid fixed int value: too few bytes.");
}
item_type item = {};
decode_pair_value(item.first, pb_str);
decode_pair_value(item.second, pb_str);
val.emplace(std::move(item));
if (pb_str.empty()) {
break;
}
uint32_t key = detail::decode_varint(pb_str, pos);
uint32_t field_number = key >> 3;
if (field_number != field_no) {
break;
}
pb_str = pb_str.substr(pos);
}
}
else if constexpr (std::is_integral_v<T>) {
val = static_cast<T>(detail::decode_varint(pb_str, pos));
pb_str = pb_str.substr(pos);
}
else if constexpr (detail::is_signed_varint_v<T>) {
constexpr size_t len = sizeof(typename T::value_type);
uint64_t temp = detail::decode_varint(pb_str, pos);
if constexpr (len == 8) {
val.val = detail::decode_zigzag(temp);
}
else {
val.val = detail::decode_zigzag(static_cast<uint32_t>(temp));
}
pb_str = pb_str.substr(pos);
}
else if constexpr (detail::is_fixed_v<T>) {
constexpr size_t size = sizeof(typename T::value_type);
if (pb_str.size() < size)
IGUANA_UNLIKELY {
throw std::invalid_argument("Invalid fixed int value: too few bytes.");
}
memcpy(&(val.val), pb_str.data(), size);
pb_str = pb_str.substr(size);
}
else if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>) {
constexpr size_t size = sizeof(T);
if (pb_str.size() < size)
IGUANA_UNLIKELY {
throw std::invalid_argument("Invalid fixed int value: too few bytes.");
}
memcpy(&(val), pb_str.data(), size);
pb_str = pb_str.substr(size);
}
else if constexpr (std::is_same_v<T, std::string> ||
std::is_same_v<T, std::string_view>) {
size_t size = detail::decode_varint(pb_str, pos);
if (pb_str.size() < pos + size)
IGUANA_UNLIKELY {
throw std::invalid_argument("Invalid string value: too few bytes.");
}
if constexpr (std::is_same_v<T, std::string_view>) {
val = std::string_view(pb_str.data() + pos, size);
}
else {
detail::resize(val, size);
memcpy(val.data(), pb_str.data() + pos, size);
}
pb_str = pb_str.substr(size + pos);
}
else if constexpr (std::is_enum_v<T>) {
using U = std::underlying_type_t<T>;
U value{};
from_pb_impl(value, pb_str);
val = static_cast<T>(value);
}
else if constexpr (optional_v<T>) {
from_pb_impl(val.emplace(), pb_str);
}
else {
static_assert(!sizeof(T), "err");
}
}
template <typename T, typename Field>
IGUANA_INLINE void parse_oneof(T& t, const Field& f, std::string_view& pb_str) {
using item_type = typename std::decay_t<Field>::sub_type;
from_pb_impl(t.template emplace<item_type>(), pb_str, f.field_no);
}
} // namespace detail
template <typename T>
IGUANA_INLINE void from_pb(T& t, std::string_view pb_str) {
if (pb_str.empty())
IGUANA_UNLIKELY { return; }
size_t pos = 0;
uint32_t key = detail::decode_varint(pb_str, pos);
WireType wire_type = static_cast<WireType>(key & 0b0111);
uint32_t field_number = key >> 3;
#ifdef SEQUENTIAL_PARSE
constexpr static auto tp = get_members_tuple<T>();
constexpr size_t SIZE = std::tuple_size_v<std::decay_t<decltype(tp)>>;
bool parse_done = false;
detail::for_each_n(
[&](auto i) IGUANA__INLINE_LAMBDA {
constexpr auto val = std::get<decltype(i)::value>(tp);
using sub_type = typename std::decay_t<decltype(val)>::sub_type;
using value_type = typename std::decay_t<decltype(val)>::value_type;
// sub_type is the element type when value_type is the variant type;
// otherwise, they are the same.
if (parse_done || field_number != val.field_no) {
return;
}
pb_str = pb_str.substr(pos);
if (wire_type != detail::get_wire_type<sub_type>())
IGUANA_UNLIKELY { throw std::runtime_error("unmatched wire_type"); }
if constexpr (variant_v<value_type>) {
detail::parse_oneof(val.value(t), val, pb_str);
}
else {
detail::from_pb_impl(val.value(t), pb_str, val.field_no);
}
if (pb_str.empty()) {
parse_done = true;
return;
}
key = detail::decode_varint(pb_str, pos);
wire_type = static_cast<WireType>(key & 0b0111);
field_number = key >> 3;
},
std::make_index_sequence<SIZE>{});
if (parse_done)
IGUANA_LIKELY { return; }
#endif
while (true) {
pb_str = pb_str.substr(pos);
constexpr static auto map = get_members<T>();
auto& member = map.at(field_number);
std::visit(
[&t, &pb_str, wire_type](auto& val) {
using sub_type = typename std::decay_t<decltype(val)>::sub_type;
using value_type = typename std::decay_t<decltype(val)>::value_type;
if (wire_type != detail::get_wire_type<sub_type>()) {
throw std::runtime_error("unmatched wire_type");
}
if constexpr (variant_v<value_type>) {
detail::parse_oneof(val.value(t), val, pb_str);
}
else {
detail::from_pb_impl(val.value(t), pb_str, val.field_no);
}
},
member);
if (!pb_str.empty())
IGUANA_LIKELY {
key = detail::decode_varint(pb_str, pos);
wire_type = static_cast<WireType>(key & 0b0111);
field_number = key >> 3;
}
else {
return;
}
}
}
} // namespace iguana

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