mirror of
https://github.com/drogonframework/drogon.git
synced 2025-06-25 00:01:17 -04:00
Compare commits
16 Commits
master
...
v1.10.0-be
Author | SHA1 | Date | |
---|---|---|---|
|
e7eff323a6 | ||
|
88bf6f7fcb | ||
|
80ff0a4869 | ||
|
70f1f8f38f | ||
|
29c8540aa8 | ||
|
518bda2865 | ||
|
92b70f0d64 | ||
|
33344d09df | ||
|
62f83517f2 | ||
|
ed62f263da | ||
|
a742a8284c | ||
|
e45e468040 | ||
|
fb08813e5c | ||
|
e81c3deb0b | ||
|
605f3df734 | ||
|
50838a9fd3 |
12
.github/workflows/cmake.yml
vendored
12
.github/workflows/cmake.yml
vendored
@ -17,7 +17,7 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
windows:
|
windows:
|
||||||
name: windows/msvc - ${{ matrix.link }}
|
name: windows/msvc - ${{ matrix.link }}
|
||||||
runs-on: windows-2022
|
runs-on: windows-2019
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -44,6 +44,9 @@ jobs:
|
|||||||
- name: Create Build Environment & Configure Cmake
|
- name: Create Build Environment & Configure Cmake
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ./build
|
working-directory: ./build
|
||||||
|
# For unknown reasons, we fail to create file in windows ci environment.
|
||||||
|
# So examples, drogon_ctl and integration tests can not be built in windows ci.
|
||||||
|
# We should try to enable them again in the future.
|
||||||
run: |
|
run: |
|
||||||
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
||||||
cmake .. \
|
cmake .. \
|
||||||
@ -51,8 +54,8 @@ jobs:
|
|||||||
-DBUILD_TESTING=on \
|
-DBUILD_TESTING=on \
|
||||||
-DBUILD_SHARED_LIBS=$shared \
|
-DBUILD_SHARED_LIBS=$shared \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" \
|
||||||
-DBUILD_CTL=ON \
|
-DBUILD_CTL=OFF \
|
||||||
-DBUILD_EXAMPLES=ON \
|
-DBUILD_EXAMPLES=OFF \
|
||||||
-DUSE_SPDLOG=ON \
|
-DUSE_SPDLOG=ON \
|
||||||
-DCMAKE_INSTALL_PREFIX=../install \
|
-DCMAKE_INSTALL_PREFIX=../install \
|
||||||
-DCMAKE_POLICY_DEFAULT_CMP0091=NEW \
|
-DCMAKE_POLICY_DEFAULT_CMP0091=NEW \
|
||||||
@ -70,7 +73,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
osver: [13, 14, 15]
|
osver: [12, 13]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Drogon source code
|
- name: Checkout Drogon source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -99,6 +102,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare for testing
|
- name: Prepare for testing
|
||||||
run: |
|
run: |
|
||||||
|
brew tap homebrew/services
|
||||||
brew services restart postgresql@14
|
brew services restart postgresql@14
|
||||||
brew services start mariadb
|
brew services start mariadb
|
||||||
brew services start redis
|
brew services start redis
|
||||||
|
4
.github/workflows/codespell.yml
vendored
4
.github/workflows/codespell.yml
vendored
@ -11,5 +11,5 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: sudo apt-get install -y codespell
|
- run: pip install --user codespell[toml]
|
||||||
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows,NotIn,aNULL," --skip="*.csp"
|
- run: codespell --ignore-words-list="coo,folx,ot,statics,xwindows,NotIn," --skip="*.csp"
|
||||||
|
2
.github/workflows/cpp.yml
vendored
2
.github/workflows/cpp.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
|||||||
CLANG_FORMAT: clang-format-17
|
CLANG_FORMAT: clang-format-17
|
||||||
|
|
||||||
cpplint:
|
cpplint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
28
.github/workflows/docker-publish.yml
vendored
28
.github/workflows/docker-publish.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: Build and Push Docker Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [created] # 当新版本被创建时触发
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build Docker image
|
|
||||||
run: |
|
|
||||||
cd docker/ubuntu
|
|
||||||
docker build -t drogonframework/drogon:latest .
|
|
||||||
|
|
||||||
- name: Push Docker image
|
|
||||||
run: |
|
|
||||||
docker push drogonframework/drogon:latest
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,6 +35,7 @@ build/
|
|||||||
cmake-build-debug/
|
cmake-build-debug/
|
||||||
cmake-build-debug-visual-studio/
|
cmake-build-debug-visual-studio/
|
||||||
.idea/
|
.idea/
|
||||||
|
lib/inc/drogon/version.h
|
||||||
html/
|
html/
|
||||||
latex/
|
latex/
|
||||||
.vscode
|
.vscode
|
||||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -2,3 +2,6 @@
|
|||||||
path = trantor
|
path = trantor
|
||||||
url = https://github.com/an-tao/trantor.git
|
url = https://github.com/an-tao/trantor.git
|
||||||
branch = master
|
branch = master
|
||||||
|
[submodule "third_party/eric-hpack-core"]
|
||||||
|
path = third_party/eric-hpack-core
|
||||||
|
url = https://gitlab.com/joe1231231218/eric-hpack-core.git
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.5...3.31)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
project(drogon)
|
project(drogon)
|
||||||
|
|
||||||
@ -13,7 +13,6 @@ option(BUILD_DOC "Build Doxygen documentation" OFF)
|
|||||||
option(BUILD_BROTLI "Build Brotli" ON)
|
option(BUILD_BROTLI "Build Brotli" ON)
|
||||||
option(BUILD_YAML_CONFIG "Build yaml config" ON)
|
option(BUILD_YAML_CONFIG "Build yaml config" ON)
|
||||||
option(USE_SUBMODULE "Use trantor as a submodule" ON)
|
option(USE_SUBMODULE "Use trantor as a submodule" ON)
|
||||||
option(USE_STATIC_LIBS_ONLY "Use only static libraries as dependencies" OFF)
|
|
||||||
|
|
||||||
include(CMakeDependentOption)
|
include(CMakeDependentOption)
|
||||||
CMAKE_DEPENDENT_OPTION(BUILD_POSTGRESQL "Build with postgresql support" ON "BUILD_ORM" OFF)
|
CMAKE_DEPENDENT_OPTION(BUILD_POSTGRESQL "Build with postgresql support" ON "BUILD_ORM" OFF)
|
||||||
@ -25,7 +24,7 @@ CMAKE_DEPENDENT_OPTION(USE_SPDLOG "Allow using the spdlog logging library" OFF "
|
|||||||
|
|
||||||
set(DROGON_MAJOR_VERSION 1)
|
set(DROGON_MAJOR_VERSION 1)
|
||||||
set(DROGON_MINOR_VERSION 9)
|
set(DROGON_MINOR_VERSION 9)
|
||||||
set(DROGON_PATCH_VERSION 11)
|
set(DROGON_PATCH_VERSION 5)
|
||||||
set(DROGON_VERSION
|
set(DROGON_VERSION
|
||||||
${DROGON_MAJOR_VERSION}.${DROGON_MINOR_VERSION}.${DROGON_PATCH_VERSION})
|
${DROGON_MAJOR_VERSION}.${DROGON_MINOR_VERSION}.${DROGON_PATCH_VERSION})
|
||||||
set(DROGON_VERSION_STRING "${DROGON_VERSION}")
|
set(DROGON_VERSION_STRING "${DROGON_VERSION}")
|
||||||
@ -42,7 +41,7 @@ set(INSTALL_DROGON_CMAKE_DIR ${DEF_INSTALL_DROGON_CMAKE_DIR}
|
|||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||||
# Force MSVC to use UTF-8 because that's what we use. Otherwise it uses
|
# Force MSVC to use UTF-8 because that's what we use. Otherwise it uses
|
||||||
# the default of whatever Windows sets and causes encoding issues.
|
# the default of whatever Windows sets and causes encoding issues.
|
||||||
message(STATUS "You are using MSVC. Forcing to use UTF-8")
|
message(STATUS "You are using MSVC. Forceing to use UTF-8")
|
||||||
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
|
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
|
||||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||||
if (MSVC_VERSION GREATER_EQUAL 1914)
|
if (MSVC_VERSION GREATER_EQUAL 1914)
|
||||||
@ -78,10 +77,6 @@ if (BUILD_SHARED_LIBS)
|
|||||||
endif ()
|
endif ()
|
||||||
endif (BUILD_SHARED_LIBS)
|
endif (BUILD_SHARED_LIBS)
|
||||||
|
|
||||||
if(USE_STATIC_LIBS_ONLY)
|
|
||||||
set(CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
|
||||||
endif(USE_STATIC_LIBS_ONLY)
|
|
||||||
|
|
||||||
if(USE_SPDLOG)
|
if(USE_SPDLOG)
|
||||||
find_package(spdlog CONFIG)
|
find_package(spdlog CONFIG)
|
||||||
if(spdlog_FOUND)
|
if(spdlog_FOUND)
|
||||||
@ -121,7 +116,6 @@ endif()
|
|||||||
target_include_directories(
|
target_include_directories(
|
||||||
${PROJECT_NAME}
|
${PROJECT_NAME}
|
||||||
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc>
|
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc>
|
||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/lib/inc>
|
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/orm_lib/inc>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/orm_lib/inc>
|
||||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc>
|
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc>
|
||||||
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
|
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
|
||||||
@ -250,6 +244,10 @@ if (BUILD_BROTLI)
|
|||||||
endif (Brotli_FOUND)
|
endif (Brotli_FOUND)
|
||||||
endif (BUILD_BROTLI)
|
endif (BUILD_BROTLI)
|
||||||
|
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/third_party/eric-hpack-core>)
|
||||||
set(DROGON_SOURCES
|
set(DROGON_SOURCES
|
||||||
lib/src/AOPAdvice.cc
|
lib/src/AOPAdvice.cc
|
||||||
lib/src/AccessLogger.cc
|
lib/src/AccessLogger.cc
|
||||||
@ -266,6 +264,8 @@ set(DROGON_SOURCES
|
|||||||
lib/src/Hodor.cc
|
lib/src/Hodor.cc
|
||||||
lib/src/HttpAppFrameworkImpl.cc
|
lib/src/HttpAppFrameworkImpl.cc
|
||||||
lib/src/HttpBinder.cc
|
lib/src/HttpBinder.cc
|
||||||
|
lib/src/Http2Transport.cc
|
||||||
|
lib/src/Http1xTransport.cc
|
||||||
lib/src/HttpClientImpl.cc
|
lib/src/HttpClientImpl.cc
|
||||||
lib/src/HttpConnectionLimit.cc
|
lib/src/HttpConnectionLimit.cc
|
||||||
lib/src/HttpControllerBinder.cc
|
lib/src/HttpControllerBinder.cc
|
||||||
@ -274,7 +274,6 @@ set(DROGON_SOURCES
|
|||||||
lib/src/HttpFileUploadRequest.cc
|
lib/src/HttpFileUploadRequest.cc
|
||||||
lib/src/HttpRequestImpl.cc
|
lib/src/HttpRequestImpl.cc
|
||||||
lib/src/HttpRequestParser.cc
|
lib/src/HttpRequestParser.cc
|
||||||
lib/src/RequestStream.cc
|
|
||||||
lib/src/HttpResponseImpl.cc
|
lib/src/HttpResponseImpl.cc
|
||||||
lib/src/HttpResponseParser.cc
|
lib/src/HttpResponseParser.cc
|
||||||
lib/src/HttpServer.cc
|
lib/src/HttpServer.cc
|
||||||
@ -285,7 +284,6 @@ set(DROGON_SOURCES
|
|||||||
lib/src/ListenerManager.cc
|
lib/src/ListenerManager.cc
|
||||||
lib/src/LocalHostFilter.cc
|
lib/src/LocalHostFilter.cc
|
||||||
lib/src/MultiPart.cc
|
lib/src/MultiPart.cc
|
||||||
lib/src/MultipartStreamParser.cc
|
|
||||||
lib/src/NotFound.cc
|
lib/src/NotFound.cc
|
||||||
lib/src/PluginsManager.cc
|
lib/src/PluginsManager.cc
|
||||||
lib/src/PromExporter.cc
|
lib/src/PromExporter.cc
|
||||||
@ -304,7 +302,8 @@ set(DROGON_SOURCES
|
|||||||
lib/src/WebSocketClientImpl.cc
|
lib/src/WebSocketClientImpl.cc
|
||||||
lib/src/WebSocketConnectionImpl.cc
|
lib/src/WebSocketConnectionImpl.cc
|
||||||
lib/src/YamlConfigAdapter.cc
|
lib/src/YamlConfigAdapter.cc
|
||||||
lib/src/drogon_test.cc)
|
lib/src/drogon_test.cc
|
||||||
|
third_party/eric-hpack-core/hpack.cpp)
|
||||||
set(private_headers
|
set(private_headers
|
||||||
lib/src/AOPAdvice.h
|
lib/src/AOPAdvice.h
|
||||||
lib/src/CacheFile.h
|
lib/src/CacheFile.h
|
||||||
@ -313,6 +312,8 @@ set(private_headers
|
|||||||
lib/src/MiddlewaresFunction.h
|
lib/src/MiddlewaresFunction.h
|
||||||
lib/src/HttpAppFrameworkImpl.h
|
lib/src/HttpAppFrameworkImpl.h
|
||||||
lib/src/HttpClientImpl.h
|
lib/src/HttpClientImpl.h
|
||||||
|
lib/src/Http2Transport.h
|
||||||
|
lib/src/Http1xTransport.h
|
||||||
lib/src/HttpConnectionLimit.h
|
lib/src/HttpConnectionLimit.h
|
||||||
lib/src/HttpControllerBinder.h
|
lib/src/HttpControllerBinder.h
|
||||||
lib/src/HttpControllersRouter.h
|
lib/src/HttpControllersRouter.h
|
||||||
@ -341,23 +342,23 @@ set(private_headers
|
|||||||
lib/src/JsonConfigAdapter.h
|
lib/src/JsonConfigAdapter.h
|
||||||
lib/src/YamlConfigAdapter.h
|
lib/src/YamlConfigAdapter.h
|
||||||
lib/src/ConfigAdapter.h
|
lib/src/ConfigAdapter.h
|
||||||
lib/src/MultipartStreamParser.h)
|
third_party/eric-hpack-core/hpack.h)
|
||||||
|
|
||||||
if (NOT WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
if (NOT WIN32)
|
||||||
set(DROGON_SOURCES
|
set(DROGON_SOURCES
|
||||||
${DROGON_SOURCES}
|
${DROGON_SOURCES}
|
||||||
lib/src/SharedLibManager.cc)
|
lib/src/SharedLibManager.cc)
|
||||||
set(private_headers
|
set(private_headers
|
||||||
${private_headers}
|
${private_headers}
|
||||||
lib/src/SharedLibManager.h)
|
lib/src/SharedLibManager.h)
|
||||||
elseif(WIN32)
|
else (NOT WIN32)
|
||||||
set(DROGON_SOURCES
|
set(DROGON_SOURCES
|
||||||
${DROGON_SOURCES}
|
${DROGON_SOURCES}
|
||||||
third_party/mman-win32/mman.c)
|
third_party/mman-win32/mman.c)
|
||||||
set(private_headers
|
set(private_headers
|
||||||
${private_headers}
|
${private_headers}
|
||||||
third_party/mman-win32/mman.h)
|
third_party/mman-win32/mman.h)
|
||||||
endif()
|
endif (NOT WIN32)
|
||||||
|
|
||||||
if (BUILD_POSTGRESQL)
|
if (BUILD_POSTGRESQL)
|
||||||
# find postgres
|
# find postgres
|
||||||
@ -511,7 +512,7 @@ execute_process(COMMAND "git" rev-parse HEAD
|
|||||||
OUTPUT_VARIABLE GIT_SHA1
|
OUTPUT_VARIABLE GIT_SHA1
|
||||||
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in"
|
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in"
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/lib/inc/drogon/version.h" @ONLY)
|
"${PROJECT_SOURCE_DIR}/lib/inc/drogon/version.h" @ONLY)
|
||||||
|
|
||||||
if (DROGON_CXX_STANDARD EQUAL 20)
|
if (DROGON_CXX_STANDARD EQUAL 20)
|
||||||
option(USE_COROUTINE "Enable C++20 coroutine support" ON)
|
option(USE_COROUTINE "Enable C++20 coroutine support" ON)
|
||||||
@ -568,7 +569,6 @@ set(DROGON_HEADERS
|
|||||||
lib/inc/drogon/HttpFilter.h
|
lib/inc/drogon/HttpFilter.h
|
||||||
lib/inc/drogon/HttpMiddleware.h
|
lib/inc/drogon/HttpMiddleware.h
|
||||||
lib/inc/drogon/HttpRequest.h
|
lib/inc/drogon/HttpRequest.h
|
||||||
lib/inc/drogon/RequestStream.h
|
|
||||||
lib/inc/drogon/HttpResponse.h
|
lib/inc/drogon/HttpResponse.h
|
||||||
lib/inc/drogon/HttpSimpleController.h
|
lib/inc/drogon/HttpSimpleController.h
|
||||||
lib/inc/drogon/HttpTypes.h
|
lib/inc/drogon/HttpTypes.h
|
||||||
@ -584,7 +584,7 @@ set(DROGON_HEADERS
|
|||||||
lib/inc/drogon/WebSocketConnection.h
|
lib/inc/drogon/WebSocketConnection.h
|
||||||
lib/inc/drogon/WebSocketController.h
|
lib/inc/drogon/WebSocketController.h
|
||||||
lib/inc/drogon/drogon.h
|
lib/inc/drogon/drogon.h
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/lib/inc/drogon/version.h
|
lib/inc/drogon/version.h
|
||||||
lib/inc/drogon/drogon_callbacks.h
|
lib/inc/drogon/drogon_callbacks.h
|
||||||
lib/inc/drogon/PubSubService.h
|
lib/inc/drogon/PubSubService.h
|
||||||
lib/inc/drogon/drogon_test.h
|
lib/inc/drogon/drogon_test.h
|
||||||
|
@ -10,9 +10,8 @@ filter=-runtime/references
|
|||||||
# CHECK macros are from Drogon, not Google Test.
|
# CHECK macros are from Drogon, not Google Test.
|
||||||
filter=-readability/check
|
filter=-readability/check
|
||||||
|
|
||||||
# Don't warn about the use of C++11 or C++17 features.
|
# Don't warn about the use of C++11 features.
|
||||||
filter=-build/c++11
|
filter=-build/c++11
|
||||||
filter=-build/c++17
|
|
||||||
|
|
||||||
filter=-build/include_subdir
|
filter=-build/include_subdir
|
||||||
|
|
||||||
|
209
ChangeLog.md
209
ChangeLog.md
@ -4,175 +4,44 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [1.9.11] - 2025-06-20
|
## [1.10.0-beta.2] - 2024-06-18
|
||||||
|
|
||||||
### API changes list
|
### Changes
|
||||||
|
|
||||||
- Add a new overload for execSqlCoro.
|
* Proactively send buffered HTTP/2 data if send queue is too large
|
||||||
|
* Better tracking of in-flight streaming that has body to sent for lower latency
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* Fix underflow on flow tracking when sending large body
|
||||||
|
* Fix calling callback multiple times if a stream received multiple RST_STREAM frame
|
||||||
|
|
||||||
|
## [1.10.0-beta.1] - 2024-06-02
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
* Fix bad request when request path is empty
|
||||||
|
* Fix bad encoding/decoding when the server requests HPACK buffer size change
|
||||||
|
|
||||||
|
## [1.10.0-beta.0] - 2024-05-25
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Do not write to source directory during build.
|
* Add HTTP/2 Client
|
||||||
|
|
||||||
- Improve Postgres connection stability.
|
## API changes
|
||||||
|
|
||||||
- Add handleFatalError in handleClosed.
|
* New enum for HTTP/2 `Version::kHttp2`
|
||||||
|
* `HttpRequest::setVersion()` deprecated and has no affect.
|
||||||
|
* HTTP version in client is no longer controlled by `req->setVersion()` but with `newHttpClient(...., DESIRED_HTTP_VERSION)`
|
||||||
|
* It is more of a suggestion then requirement. The client negotiates and selects the highest supported one.
|
||||||
|
|
||||||
- Add -o|--output option to drogon_ctl create models.
|
## Limitations
|
||||||
|
|
||||||
- Add qrcode for WeChat official account to the README file.
|
|
||||||
|
|
||||||
- Support for iOS compiling.
|
|
||||||
|
|
||||||
- Add cors example to demonstrate cross-origin support in drogon.
|
|
||||||
|
|
||||||
- Add support for continuation frame in WebSocketMessageParser.
|
|
||||||
|
|
||||||
- Add RawParameter API to pass raw SQL parameters.
|
|
||||||
|
|
||||||
- Upgrade Windows image and re-enable tests on Windows.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix a bug in isAutoCreationClass<T>.
|
|
||||||
|
|
||||||
- Fix CI on MacOS.
|
|
||||||
|
|
||||||
- Fix issue with precision loss of double-type parameters in ORM inputs.
|
|
||||||
|
|
||||||
|
|
||||||
## [1.9.10] - 2025-02-20
|
|
||||||
|
|
||||||
### API changes list
|
|
||||||
|
|
||||||
- Add setConnectionCallback.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- ORM:Avoid unnecessary copies when returning search results.
|
|
||||||
|
|
||||||
- Improve the zh-TW README translation.
|
|
||||||
|
|
||||||
- Make quit function thread safe.
|
|
||||||
|
|
||||||
- Added path_exempt in AccessLogger plugin config to exclude desired paths.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix the issue in view generation by including the missing header file.
|
|
||||||
|
|
||||||
- Fix ci: codespell.
|
|
||||||
|
|
||||||
## [1.9.9] - 2025-01-01
|
|
||||||
|
|
||||||
### API changes list
|
|
||||||
|
|
||||||
- Added Partitioned flag for cookies.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Update FindFilesystem.cmake to check for GNU instead of GCC for CMAKE_CXX_COMPILER_ID.
|
|
||||||
|
|
||||||
- Update README.
|
|
||||||
|
|
||||||
- Chore(workflow/cmake.yml): upgrade macos runner.
|
|
||||||
|
|
||||||
- Add emptiness check to the LogStream &operator<< with std::string_view.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix a bug in plugin Redirector.
|
|
||||||
|
|
||||||
- Fix CMAKE issues mentioned in #2144 and a linking problem which manifest with gcc12.3 when building with shared libs.
|
|
||||||
|
|
||||||
- Fix: Remove dependency on locales being installed on the system.
|
|
||||||
|
|
||||||
## [1.9.8] - 2024-10-27
|
|
||||||
|
|
||||||
### API changes list
|
|
||||||
|
|
||||||
- Add in-place base64 encode and decode.
|
|
||||||
|
|
||||||
- Add check the client connection status.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Add Hodor whitelists.
|
|
||||||
|
|
||||||
- Include exception header for std::exception_ptr.
|
|
||||||
|
|
||||||
- Add support for escaped identifiers in Postgresql.
|
|
||||||
|
|
||||||
- Remove content-length header from 101 Switching Protocols response.
|
|
||||||
|
|
||||||
- Remove websocketResponseTest from windows shared library env.
|
|
||||||
|
|
||||||
- Optimize query params and allow for empty values.
|
|
||||||
|
|
||||||
- Replace rejection sampling and remove use of rand().
|
|
||||||
|
|
||||||
- Add sending customized http requests to drogon_ctl.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix coroutine continuation handle.
|
|
||||||
|
|
||||||
- Fix some bugs in plugin PromExporter.
|
|
||||||
|
|
||||||
- Fix a bug after removing content-length header in some responses.
|
|
||||||
|
|
||||||
## [1.9.7] - 2024-09-10
|
|
||||||
|
|
||||||
### API changes list
|
|
||||||
|
|
||||||
- Add coroutine mutex.
|
|
||||||
|
|
||||||
- Add requestsBufferSize function.
|
|
||||||
|
|
||||||
- Refine SQLite3 error types with new exception handling.
|
|
||||||
|
|
||||||
- Add a new method to reload SSL files on the fly.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Allow MultiPartParser to be movable.
|
|
||||||
|
|
||||||
- Add quotes to the table name in the ORM generator.
|
|
||||||
|
|
||||||
- Change stoi to stoul in the Field class.
|
|
||||||
|
|
||||||
- Modernize cookies.
|
|
||||||
|
|
||||||
- Change a log level.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Use correct libraries when compiling statically.
|
|
||||||
|
|
||||||
## [1.9.6] - 2024-07-20
|
|
||||||
|
|
||||||
### API changes list
|
|
||||||
|
|
||||||
- Add setsockopt to HttpServer.
|
|
||||||
|
|
||||||
- Support request stream.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Allow MultiPartParser to parse PATCH requests.
|
|
||||||
|
|
||||||
- Add an example of prometheus.
|
|
||||||
|
|
||||||
- Delay parsing json for HttpClient.
|
|
||||||
|
|
||||||
- Update README.md.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- Fix some compilation warnings.
|
|
||||||
|
|
||||||
- Fix typo in yaml config.
|
|
||||||
|
|
||||||
|
* No H2C (HTTP/2 over cleartext) support
|
||||||
|
* Technically supports handling server push. But no API to expose it to the user yet
|
||||||
|
* Does not support setting HTTP request dependency and priority
|
||||||
|
* Does not support trailers
|
||||||
## [1.9.5] - 2024-06-08
|
## [1.9.5] - 2024-06-08
|
||||||
|
|
||||||
### API changes list
|
### API changes list
|
||||||
@ -520,7 +389,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
- Remove unused CI files and Jekyll config.
|
- Remove unused CI files and Jekyll config.
|
||||||
|
|
||||||
- Ensure that all filters, AOP advice, and handlers are executed within the IO threads.
|
- Ensure that all filters, AOP advices, and handlers are executed within the IO threads.
|
||||||
|
|
||||||
- Update test.sh and build.sh by appending prefix "X" to string variable comparisons.
|
- Update test.sh and build.sh by appending prefix "X" to string variable comparisons.
|
||||||
|
|
||||||
@ -852,7 +721,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
- Check HTTP client is not sending requests in sync mode on the same event loop.
|
- Check HTTP client is not sending requests in sync mode on the same event loop.
|
||||||
|
|
||||||
- Start listening after beginning advice.
|
- Start listening after beginning advices.
|
||||||
|
|
||||||
- Allow using json_cpp in other sublibraries.
|
- Allow using json_cpp in other sublibraries.
|
||||||
|
|
||||||
@ -1842,22 +1711,16 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [1.0.0-beta1] - 2019-06-11
|
## [1.0.0-beta1] - 2019-06-11
|
||||||
|
|
||||||
[Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.11...HEAD
|
[Unreleased]: https://github.com/an-tao/drogon/compare/v1.9.5...HEAD
|
||||||
|
|
||||||
[1.9.11]: https://github.com/an-tao/drogon/compare/v1.9.10...v1.9.11
|
[1.10.0-beta.2]: https://github.com/drogonframework/drogon/compare/v1.10.0-beta.1...v1.10.0-beta.2
|
||||||
|
|
||||||
[1.9.10]: https://github.com/an-tao/drogon/compare/v1.9.9...v1.9.10
|
[1.10.0-beta.1]: https://github.com/drogonframework/drogon/compare/v1.10.0-beta.0...v1.10.0-beta.1
|
||||||
|
|
||||||
[1.9.9]: https://github.com/an-tao/drogon/compare/v1.9.8...v1.9.9
|
|
||||||
|
|
||||||
[1.9.8]: https://github.com/an-tao/drogon/compare/v1.9.7...v1.9.8
|
|
||||||
|
|
||||||
[1.9.7]: https://github.com/an-tao/drogon/compare/v1.9.6...v1.9.7
|
|
||||||
|
|
||||||
[1.9.6]: https://github.com/an-tao/drogon/compare/v1.9.5...v1.9.6
|
|
||||||
|
|
||||||
[1.9.5]: https://github.com/an-tao/drogon/compare/v1.9.4...v1.9.5
|
[1.9.5]: https://github.com/an-tao/drogon/compare/v1.9.4...v1.9.5
|
||||||
|
|
||||||
|
[1.10.0-beta.0]: https://github.com/an-tao/drogon/compare/v1.9.4...1.10.0-beta.0
|
||||||
|
|
||||||
[1.9.4]: https://github.com/an-tao/drogon/compare/v1.9.3...v1.9.4
|
[1.9.4]: https://github.com/an-tao/drogon/compare/v1.9.3...v1.9.4
|
||||||
|
|
||||||
[1.9.3]: https://github.com/an-tao/drogon/compare/v1.9.2...v1.9.3
|
[1.9.3]: https://github.com/an-tao/drogon/compare/v1.9.2...v1.9.3
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://github.com/drogonframework/drogon/actions)
|
[](https://github.com/drogonframework/drogon/actions)
|
||||||
[](https://conan.io/center/recipes/drogon)
|
[](https://conan.io/center/recipes/drogon)
|
||||||
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
||||||
[](https://discord.gg/3DvHY6Ewuj)
|
[](https://discord.gg/3DvHY6Ewuj)
|
||||||
@ -8,13 +8,13 @@
|
|||||||
|
|
||||||
English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md)
|
English | [简体中文](./README.zh-CN.md) | [繁體中文](./README.zh-TW.md)
|
||||||
### Overview
|
### Overview
|
||||||
**Drogon** is a C++17/20 based HTTP application framework. Drogon can be used to easily build various types of web application server programs using C++. **Drogon** is the name of a dragon from the American TV series *Game of Thrones*, which I really enjoy.
|
**Drogon** is a C++17/20 based HTTP application framework. Drogon can be used to easily build various types of web application server programs using C++. **Drogon** is the name of a dragon in the American TV series "Game of Thrones" that I really like.
|
||||||
|
|
||||||
Drogon is a cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD, HaikuOS, and Windows. Its main features are as follows:
|
Drogon is a cross-platform framework, It supports Linux, macOS, FreeBSD, OpenBSD, HaikuOS, and Windows. Its main features are as follows:
|
||||||
|
|
||||||
* Use a non-blocking I/O network lib based on epoll (kqueue under macOS/FreeBSD) to provide high-concurrency, high-performance network IO, please visit the [TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite) for more details;
|
* Use a non-blocking I/O network lib based on epoll (kqueue under macOS/FreeBSD) to provide high-concurrency, high-performance network IO, please visit the [TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite) for more details;
|
||||||
* Provide a completely asynchronous programming mode;
|
* Provide a completely asynchronous programming mode;
|
||||||
* Support Http1.0/1.1 (server side and client side);
|
* Support HTTP/2 (and 1.0/1.1) client and HTTP 1.1/1.0 server
|
||||||
* Based on template, a simple reflection mechanism is implemented to completely decouple the main program framework, controllers and views.
|
* Based on template, a simple reflection mechanism is implemented to completely decouple the main program framework, controllers and views.
|
||||||
* Support cookies and built-in sessions;
|
* Support cookies and built-in sessions;
|
||||||
* Support back-end rendering, the controller generates the data to the view to generate the Html page. Views are described by CSP template files, C++ codes are embedded into Html pages through CSP tags. And the drogon command-line tool automatically generates the C++ code files for compilation;
|
* Support back-end rendering, the controller generates the data to the view to generate the Html page. Views are described by CSP template files, C++ codes are embedded into Html pages through CSP tags. And the drogon command-line tool automatically generates the C++ code files for compilation;
|
||||||
@ -183,7 +183,7 @@ As you can see, users can use the `HttpController` to map paths and parameters a
|
|||||||
|
|
||||||
In addition, you can also find that all handler interfaces are in asynchronous mode, where the response is returned by a callback object. This design is for performance reasons because in asynchronous mode the drogon application can handle a large number of concurrent requests with a small number of threads.
|
In addition, you can also find that all handler interfaces are in asynchronous mode, where the response is returned by a callback object. This design is for performance reasons because in asynchronous mode the drogon application can handle a large number of concurrent requests with a small number of threads.
|
||||||
|
|
||||||
After compiling all of the above source files, we get a very simple web application. This is a good start. **For more information, please visit the [documentation](https://drogonframework.github.io/drogon-docs/#/) on GitHub**.
|
After compiling all of the above source files, we get a very simple web application. This is a good start. **For more information, please visit the [wiki](https://github.com/an-tao/drogon/wiki/ENG-01-Overview)**
|
||||||
|
|
||||||
## Cross-compilation
|
## Cross-compilation
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||

|

|
||||||
|
|
||||||
[](https://github.com/drogonframework/drogon/actions)
|
[](https://github.com/drogonframework/drogon/actions)
|
||||||
[](https://conan.io/center/recipes/drogon)
|
[](https://conan.io/center/recipes/drogon)
|
||||||
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
||||||
[](https://discord.gg/3DvHY6Ewuj)
|
[](https://discord.gg/3DvHY6Ewuj)
|
||||||
@ -186,7 +186,7 @@ class User : public drogon::HttpController<User>
|
|||||||
|
|
||||||
另外,你可以发现前面所有的处理函数接口都是异步的,处理器的响应是通过回调对象返回的。这种设计是出于对高性能的考虑,因为在异步模式下,可以使用少量的线程(比如和处理器核心数相等的线程)处理大量的并发请求。
|
另外,你可以发现前面所有的处理函数接口都是异步的,处理器的响应是通过回调对象返回的。这种设计是出于对高性能的考虑,因为在异步模式下,可以使用少量的线程(比如和处理器核心数相等的线程)处理大量的并发请求。
|
||||||
|
|
||||||
编译上述的所有源文件后,我们得到了一个非常简单的web应用程序,这是一个不错的开始。**请访问GitHub上的[文档](https://drogonframework.github.io/drogon-docs/#/CHN/CHN-01-%E6%A6%82%E8%BF%B0)**
|
编译上述的所有源文件后,我们得到了一个非常简单的web应用程序,这是一个不错的开始。**请访问[wiki](https://github.com/an-tao/drogon/wiki/CHN-01-概述)**
|
||||||
|
|
||||||
## 贡献方式
|
## 贡献方式
|
||||||
|
|
||||||
@ -197,9 +197,3 @@ class User : public drogon::HttpController<User>
|
|||||||
## QQ交流群:1137909452
|
## QQ交流群:1137909452
|
||||||
|
|
||||||
欢迎交流探讨。
|
欢迎交流探讨。
|
||||||
|
|
||||||
## 微信公众号:
|
|
||||||
|
|
||||||

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

|

|
||||||
|
|
||||||
[](https://github.com/drogonframework/drogon/actions)
|
[](https://github.com/drogonframework/drogon/actions)
|
||||||
[](https://conan.io/center/recipes/drogon)
|
[](https://conan.io/center/recipes/drogon)
|
||||||
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
[](https://t.me/joinchat/_mMNGv0748ZkMDAx)
|
||||||
[](https://discord.gg/3DvHY6Ewuj)
|
[](https://discord.gg/3DvHY6Ewuj)
|
||||||
@ -8,42 +8,41 @@
|
|||||||
|
|
||||||
[English](./README.md) | [简体中文](./README.zh-CN.md) | 繁體中文
|
[English](./README.md) | [简体中文](./README.zh-CN.md) | 繁體中文
|
||||||
|
|
||||||
**Drogon** 是一個基於 C++17/20 的 HTTP 應用程式框架,使用 Drogon 可以方便地用 C++ 建立各種類型的 Web App 伺服器端程式。
|
**Drogon**是一個基於C++17/20的Http應用框架,使用Drogon可以方便的使用C++構建各種類型的Web App伺服器程式。
|
||||||
|
本版本庫是github上[Drogon](https://github.com/an-tao/drogon)的鏡像庫。 **Drogon**是作者非常喜歡的美劇《冰與火之歌:權力遊戲》中的一條龍的名字(漢譯作卓耿),和龍有關但並不是dragon的誤寫,為了不至於引起不必要的誤會這裡說明一下。
|
||||||
|
|
||||||
這個版本庫是 GitHub 上 [Drogon](https://github.com/an-tao/drogon) 的鏡像庫。**Drogon** 是作者非常喜歡的美劇《冰與火之歌:權力遊戲》中的一條龍的名字(中文譯作卓耿),和龍有關但並不是 dragon 的誤寫,為了避免不必要的誤會在此說明。
|
Drogon是一個跨平台框架,它支援Linux,也支援macOS、FreeBSD/OpenBSD、HaikuOS和Windows。它的主要特點如下:
|
||||||
|
|
||||||
Drogon 是一個跨平台框架,支援 Linux、macOS、FreeBSD/OpenBSD、HaikuOS 和 Windows。主要特點如下:
|
* 網路層使用基於epoll(macOS/FreeBSD下是kqueue)的非阻塞IO框架,提供高並發、高性能的網路IO。詳細請見[TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite);
|
||||||
|
* 全異步程式設計;
|
||||||
* 網路層使用基於 epoll(macOS/FreeBSD 下是 kqueue)的非阻塞 IO 框架,提供高並行、高效能的網路 IO。詳細請見 [TFB Tests Results](https://www.techempower.com/benchmarks/#section=data-r19&hw=ph&test=composite);
|
* 支援Http1.0/1.1(server端和client端);
|
||||||
* 完全非同步的程式撰寫邏輯;
|
* 基於模板(template)實現了簡單的反射機制,使主程式框架、控制器(controller)和視圖(view)完全去耦;
|
||||||
* 支援 HTTP 1.0/1.1(伺服器端和用戶端);
|
* 支援cookies和內建的session;
|
||||||
* 基於樣板(template)實作的簡單反射機制,使主程式框架、控制器(controller)和視圖(view)完全解耦;
|
* 支援後端渲染,把控制器生成的數據交給視圖生成Html頁面,視圖由CSP模板文件描述,通過CSP標籤把C++程式碼嵌入到Html頁面,由drogon的指令列工具在編譯階段自動生成C++程式碼並編譯;
|
||||||
* 支援 cookies 和內建的 session;
|
* 支援運行期的視圖頁面動態加載(動態編譯和載入so文件);
|
||||||
* 支援後端算繪,將控制器產生的資料交給視圖產生 HTML 頁面,視圖由 CSP 樣板檔案描述,透過 CSP 標籤將 C++ 程式碼嵌入 HTML 頁面,由 drogon 的命令列工具在編譯階段自動產生 C++ 程式碼並編譯;
|
* 非常方便靈活的路徑(path)到控制器處理函數(handler)的映射方案;
|
||||||
* 支援執行期的視圖頁面動態載入(動態編譯和載入 so 檔案);
|
* 支援過濾器(filter)鏈,方便在控制器之前執行統一的邏輯(如登錄驗證、Http Method約束驗證等);
|
||||||
* 非常方便靈活的路徑(path)到控制器處理函式(handler)的對應方案;
|
* 支援https(基於OpenSSL);
|
||||||
* 支援過濾器(filter)鏈,方便在控制器之前執行統一的邏輯(如登入驗證、HTTP Method 限制驗證等);
|
* 支援websocket(server端和client端);
|
||||||
* 支援 HTTPS(基於 OpenSSL);
|
* 支援Json格式的請求和回應, 方便開發Restful API;
|
||||||
* 支援 WebSocket(伺服器端和用戶端);
|
* 支援文件下載和上傳,支援sendfile系統呼叫;
|
||||||
* 支援 JSON 格式的請求和回應,方便開發 RESTful API;
|
* 支援gzip/brotli壓縮傳輸;
|
||||||
* 支援檔案下載和上傳,支援 `sendfile` 系統呼叫;
|
* 支援pipelining;
|
||||||
* 支援 Gzip/Brotli 壓縮傳輸;
|
* 提供一個輕量的指令列工具drogon_ctl,幫助簡化各種類的創造和視圖程式碼的生成過程;
|
||||||
* 支援 pipelining;
|
* 非同步的讀寫資料庫,目前支援PostgreSQL和MySQL(MariaDB)資料庫;
|
||||||
* 提供輕量的命令列工具 `drogon_ctl`,幫助簡化各種類別的建立和視圖程式碼的產生過程;
|
* 支援異步讀寫Redis;
|
||||||
* 非同步的讀寫資料庫,目前支援 PostgreSQL 和 MySQL(MariaDB)資料庫;
|
* 基於執行序池實現sqlite3資料庫的異步讀寫,提供與上文資料庫相同的接口;
|
||||||
* 支援非同步讀寫 Redis;
|
* 支援ARM架構;
|
||||||
* 基於執行緒池實作 sqlite3 資料庫的非同步讀寫,提供與上述資料庫相同的介面;
|
* 方便的輕量級ORM實現,一般物件到資料庫的雙向映射;
|
||||||
* 支援 ARM 架構;
|
* 支援外掛,可通過設定文件在載入時動態載入;
|
||||||
* 方便的輕量級 ORM 實現,一般物件到資料庫的雙向對應;
|
* 支援內建插入點的AOP
|
||||||
* 支援外掛,可透過設定檔案在載入時動態載入;
|
* 支援C++ coroutine
|
||||||
* 支援內建插入點的 AOP;
|
|
||||||
* 支援 C++ coroutine。
|
|
||||||
|
|
||||||
## 一個非常簡單的例子
|
## 一個非常簡單的例子
|
||||||
|
|
||||||
不像大多數 C++ 框架,drogon 的主程式可以非常簡單。Drogon 使用了一些小技巧使主程式和控制器解耦。控制器的路由設定可以在控制器類別中定義或在設定檔案中完成。
|
不像大多數C++框架那樣,drogon的主程式可以非常簡單。 Drogon使用了一些小技巧使主程式和控制器去耦. 控制器的路由設定可以在控制器類別中定義或者設定文件中完成.
|
||||||
|
|
||||||
下面是一個典型主程式的樣子:
|
下面是一個典型的主程式的樣子:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
@ -59,7 +58,7 @@ int main()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
如果使用設定檔案,可以進一步簡化成:
|
如果使用設定文件,可以進一步簡化成這樣:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
@ -70,7 +69,7 @@ int main()
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
當然,Drogon 也提供了一些函式,讓使用者可以在 `main()` 函式中直接加入控制器邏輯,例如,使用者可以註冊一個 lambda 處理常式到 drogon 框架中,如下所示:
|
當然,Drogon也提供了一些函數,使使用者可以在main()函數中直接添加控制器邏輯,比如,使用者可以註冊一個lambda處理器到drogon框架中,如下所示:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
app().registerHandler("/test?username={name}",
|
app().registerHandler("/test?username={name}",
|
||||||
@ -87,7 +86,9 @@ app().registerHandler("/test?username={name}",
|
|||||||
{Get,"LoginFilter"});
|
{Get,"LoginFilter"});
|
||||||
```
|
```
|
||||||
|
|
||||||
這看起來很方便,但不適用於複雜的場景,試想如果有數十個或數百個處理函式要註冊進框架,`main()` 函式將變得難以閱讀。顯然,讓每個包含處理函式的類別在自己的定義中完成註冊是更好的選擇。所以,除非你的應用邏輯非常簡單,我們不建議使用上述介面,更好的做法是建立一個 HttpSimpleController 類別,如下:
|
|
||||||
|
這看起來是很方便,但是這並不適用於復雜的場景,試想假如有數十個或者數百個處理函數要註冊進框架,main()函數將膨脹到不可讀的程度。顯然,讓每個包含處理函數的類在自己的定義中完成註冊是更好的選擇。所以,除非你的應用邏輯非常簡單,我們不推薦使用上述接口,更好的實踐是,我們可以創造一個HttpSimpleController類別,如下:
|
||||||
|
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
/// The TestCtrl.h file
|
/// The TestCtrl.h file
|
||||||
@ -116,9 +117,9 @@ void TestCtrl::asyncHandleHttpRequest(const HttpRequestPtr& req,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**上述程式的大部分程式碼都可以由 `drogon_ctl` 指令產生**(使用指令 `drogon_ctl create controller TestCtr`)。使用者只需要加入自己的業務邏輯。在這個範例中,當用戶端存取 URL `http://ip/test` 時,控制器簡單地回傳一個 `Hello, world!` 頁面。
|
**上面程式的大部分程式碼都可以由`drogon_ctl`指令創造**(這個指令是`drogon_ctl create controller TestCtr`)。使用者所需做的就是添加自己的業務邏輯。在這個例子中,當客戶端訪問URL`http://ip/test`時,控制器簡單的回傳了一個`Hello, world!`頁面。
|
||||||
|
|
||||||
對於 JSON 格式的回應,我們可以這樣建立控制器:
|
對於JSON格式的回應,我們可以像下面這樣創造控制器:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
/// The header file
|
/// The header file
|
||||||
@ -147,7 +148,7 @@ void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
讓我們更進一步,透過 HttpController 類別建立一個 RESTful API 的範例,如下所示(省略實作檔案):
|
讓我們更進一步,通過HttpController類別創造一個RESTful API的例子,如下所示(忽略了實做文件):
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
/// The header file
|
/// The header file
|
||||||
@ -181,18 +182,18 @@ class User : public drogon::HttpController<User>
|
|||||||
} // namespace api
|
} // namespace api
|
||||||
```
|
```
|
||||||
|
|
||||||
如你所見,透過 `HttpController` 類別,使用者可以同時對應路徑和路徑參數,這對 RESTful API 應用來說非常方便。
|
如你所見,通過`HttpController`類別,使用者可以同時映射路徑和路徑參數,這對RESTful API應用來說非常方便。
|
||||||
|
|
||||||
另外,你可以發現前面所有的處理函式介面都是非同步的,處理器的回應是透過回呼物件回傳的。這種設計是考慮到效能,因為在非同步模式下,可以使用少量的執行緒(例如和處理器核心數相等的執行緒)處理大量的並行請求。
|
另外,你可以發現前面所有的處理函數接口都是異步的,處理器的回應是通過回調對象回傳的。這種設計是出於對高性能的考慮,因為在異步模式下,可以使用少量的執行序(比如和處理器核心數相等的執行序)處理大量的並發請求。
|
||||||
|
|
||||||
編譯上述所有原始檔案後,我們得到了一個非常簡單的網頁應用程式,這是一個不錯的開始。**請瀏覽 GitHub 上的[文件](https://drogonframework.github.io/drogon-docs/#/CHN/CHN-01-%E6%A6%82%E8%BF%B0)**
|
編譯上述的所有源文件後,我們得到了一個非常簡單的web應用程式,這是一個不錯的開始。 **請瀏覽[wiki](https://github.com/an-tao/drogon/wiki/CHN-01-概述)**
|
||||||
|
|
||||||
## 貢獻方式
|
## 貢獻方式
|
||||||
|
|
||||||
歡迎您的貢獻。請閱讀[貢獻指南](CONTRIBUTING.md)以取得更多資訊。
|
歡迎您的貢獻。請閱讀[貢獻指南](CONTRIBUTING.md)以獲取更多的信息。
|
||||||
|
|
||||||
<a href="https://github.com/drogonframework/drogon/graphs/contributors"><img src="https://contributors-svg.opencollective.com/drogon/contributors.svg?width=890&button=false" alt="Code contributors" /></a>
|
<a href="https://github.com/drogonframework/drogon/graphs/contributors"><img src="https://contributors-svg.opencollective.com/drogon/contributors.svg?width=890&button=false" alt="Code contributors" /></a>
|
||||||
|
|
||||||
## QQ 交流群:1137909452
|
## QQ交流群:1137909452
|
||||||
|
|
||||||
歡迎交流討論。
|
歡迎交流探討。
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# ParseAndAddDrogonTests(${PROJECT_NAME}) #
|
# ParseAndAddDrogonTests(${PROJECT_NAME}) #
|
||||||
#==================================================================================================#
|
#==================================================================================================#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.5...3.31)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
# This removes the contents between
|
# This removes the contents between
|
||||||
# - block comments (i.e. /* ... */)
|
# - block comments (i.e. /* ... */)
|
||||||
|
@ -212,7 +212,7 @@ if(CXX_FILESYSTEM_HAVE_FS)
|
|||||||
]] code @ONLY)
|
]] code @ONLY)
|
||||||
|
|
||||||
# HACK: Needed to compile correctly on Yocto Linux
|
# HACK: Needed to compile correctly on Yocto Linux
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GCC" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
|
||||||
OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||||
set(CMAKE_REQUIRED_FLAGS ${prev_req_flags} -std=c++17)
|
set(CMAKE_REQUIRED_FLAGS ${prev_req_flags} -std=c++17)
|
||||||
endif ()
|
endif ()
|
||||||
|
@ -51,9 +51,7 @@ if(Jsoncpp_FOUND)
|
|||||||
COMMAND awk "{ printf \$3 }"
|
COMMAND awk "{ printf \$3 }"
|
||||||
COMMAND sed -e "s/\"//g"
|
COMMAND sed -e "s/\"//g"
|
||||||
OUTPUT_VARIABLE jsoncpp_ver)
|
OUTPUT_VARIABLE jsoncpp_ver)
|
||||||
if(NOT Jsoncpp_FIND_QUIETLY)
|
message(STATUS "jsoncpp version:" ${jsoncpp_ver})
|
||||||
message(STATUS "jsoncpp version:" ${jsoncpp_ver})
|
|
||||||
endif()
|
|
||||||
if(jsoncpp_ver LESS 1.7)
|
if(jsoncpp_ver LESS 1.7)
|
||||||
message(
|
message(
|
||||||
FATAL_ERROR
|
FATAL_ERROR
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
"session_timeout": 0,
|
"session_timeout": 0,
|
||||||
//string value of SameSite attribute of the Set-Cookie HTTP response header
|
//string value of SameSite attribute of the Set-Cookie HTTP response header
|
||||||
//valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
|
//valid value is either 'Null' (default), 'Lax', 'Strict' or 'None'
|
||||||
"session_same_site": "Null",
|
"session_same_site" : "Null",
|
||||||
//session_cookie_key: The cookie key of the session, "JSESSIONID" by default
|
//session_cookie_key: The cookie key of the session, "JSESSIONID" by default
|
||||||
"session_cookie_key": "JSESSIONID",
|
"session_cookie_key": "JSESSIONID",
|
||||||
//session_max_age: The max age of the session cookie, -1 by default
|
//session_max_age: The max age of the session cookie, -1 by default
|
||||||
@ -310,10 +310,7 @@
|
|||||||
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
|
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
|
||||||
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
||||||
// will be rejected.
|
// will be rejected.
|
||||||
"enabled_compressed_request": false,
|
"enabled_compressed_request": false
|
||||||
// enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
|
|
||||||
// See the wiki for more details.
|
|
||||||
"enable_request_stream": false,
|
|
||||||
},
|
},
|
||||||
//plugins: Define all plugins running in the application
|
//plugins: Define all plugins running in the application
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
@ -283,9 +283,6 @@ app:
|
|||||||
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
||||||
# will be rejected.
|
# will be rejected.
|
||||||
enabled_compressed_request: false
|
enabled_compressed_request: false
|
||||||
# enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
|
|
||||||
# See the wiki for more details.
|
|
||||||
enable_request_stream: false
|
|
||||||
# plugins: Define all plugins running in the application
|
# plugins: Define all plugins running in the application
|
||||||
plugins:
|
plugins:
|
||||||
# name: The class name of the plugin
|
# name: The class name of the plugin
|
||||||
|
@ -42,8 +42,7 @@ std::string create::detail()
|
|||||||
"create a plugin named class_name\n\n"
|
"create a plugin named class_name\n\n"
|
||||||
"drogon_ctl create project <project_name> //"
|
"drogon_ctl create project <project_name> //"
|
||||||
"create a project named project_name\n\n"
|
"create a project named project_name\n\n"
|
||||||
"drogon_ctl create model <model_path> [-o <output path>] "
|
"drogon_ctl create model <model_path> [--table=<table_name>] [-f]//"
|
||||||
"[--table=<table_name>] [-f]//"
|
|
||||||
"create model classes in model_path\n";
|
"create model classes in model_path\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,17 +66,6 @@ static std::string escapeConnString(const std::string &str)
|
|||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string drogon_ctl::escapeIdentifier(const std::string &identifier,
|
|
||||||
const std::string &rdbms)
|
|
||||||
{
|
|
||||||
if (rdbms != "postgresql")
|
|
||||||
{
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "\\\"" + identifier + "\\\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::map<std::string, std::vector<ConvertMethod>> getConvertMethods(
|
static std::map<std::string, std::vector<ConvertMethod>> getConvertMethods(
|
||||||
const Json::Value &convertColumns)
|
const Json::Value &convertColumns)
|
||||||
{
|
{
|
||||||
@ -177,7 +166,7 @@ void create_model::createModelClassFromPG(
|
|||||||
auto className = nameTransform(tableName, true);
|
auto className = nameTransform(tableName, true);
|
||||||
HttpViewData data;
|
HttpViewData data;
|
||||||
data["className"] = className;
|
data["className"] = className;
|
||||||
data["tableName"] = tableName;
|
data["tableName"] = toLower(tableName);
|
||||||
data["hasPrimaryKey"] = (int)0;
|
data["hasPrimaryKey"] = (int)0;
|
||||||
data["primaryKeyName"] = "";
|
data["primaryKeyName"] = "";
|
||||||
data["dbName"] = dbname_;
|
data["dbName"] = dbname_;
|
||||||
@ -471,7 +460,7 @@ void create_model::createModelClassFromMysql(
|
|||||||
data["convertMethods"] = convertMethods;
|
data["convertMethods"] = convertMethods;
|
||||||
std::vector<ColumnInfo> cols;
|
std::vector<ColumnInfo> cols;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
*client << "desc `" + tableName + "`" << Mode::Blocking >>
|
*client << "desc " + tableName << Mode::Blocking >>
|
||||||
[&i, &cols](bool isNull,
|
[&i, &cols](bool isNull,
|
||||||
const std::string &field,
|
const std::string &field,
|
||||||
const std::string &type,
|
const std::string &type,
|
||||||
@ -826,7 +815,6 @@ void create_model::createModel(const std::string &path,
|
|||||||
auto restfulApiConfig = config["restful_api_controllers"];
|
auto restfulApiConfig = config["restful_api_controllers"];
|
||||||
auto relationships = getRelationships(config["relationships"]);
|
auto relationships = getRelationships(config["relationships"]);
|
||||||
auto convertMethods = getConvertMethods(config["convert"]);
|
auto convertMethods = getConvertMethods(config["convert"]);
|
||||||
drogon::utils::createPath(path);
|
|
||||||
if (dbType == "postgresql")
|
if (dbType == "postgresql")
|
||||||
{
|
{
|
||||||
#if USE_POSTGRESQL
|
#if USE_POSTGRESQL
|
||||||
@ -1174,9 +1162,7 @@ void create_model::createModel(const std::string &path,
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
infile >> configJsonRoot;
|
infile >> configJsonRoot;
|
||||||
createModel(outputPath_.empty() ? path : outputPath_,
|
createModel(path, configJsonRoot, singleModelName);
|
||||||
configJsonRoot,
|
|
||||||
singleModelName);
|
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception)
|
catch (const std::exception &exception)
|
||||||
{
|
{
|
||||||
@ -1214,22 +1200,6 @@ void create_model::handleCommand(std::vector<std::string> ¶meters)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (auto iter = parameters.begin(); iter != parameters.end();)
|
|
||||||
{
|
|
||||||
auto &file = *iter;
|
|
||||||
if (file == "-o" || file == "--output")
|
|
||||||
{
|
|
||||||
iter = parameters.erase(iter);
|
|
||||||
if (iter != parameters.end())
|
|
||||||
{
|
|
||||||
outputPath_ = *iter;
|
|
||||||
iter = parameters.erase(iter);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto const &path : parameters)
|
for (auto const &path : parameters)
|
||||||
{
|
{
|
||||||
createModel(path, singleModelName);
|
createModel(path, singleModelName);
|
||||||
|
@ -78,9 +78,6 @@ inline std::string nameTransform(const std::string &origName, bool isType)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string escapeIdentifier(const std::string &identifier,
|
|
||||||
const std::string &rdbms);
|
|
||||||
|
|
||||||
class PivotTable
|
class PivotTable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -429,6 +426,5 @@ class create_model : public DrObject<create_model>, public CommandHandler
|
|||||||
const Json::Value &restfulApiConfig);
|
const Json::Value &restfulApiConfig);
|
||||||
std::string dbname_;
|
std::string dbname_;
|
||||||
bool forceOverwrite_{false};
|
bool forceOverwrite_{false};
|
||||||
std::string outputPath_;
|
|
||||||
};
|
};
|
||||||
} // namespace drogon_ctl
|
} // namespace drogon_ctl
|
||||||
|
@ -411,7 +411,6 @@ void create_view::newViewSourceFile(std::ofstream &file,
|
|||||||
"automatically,don't modify it!\n";
|
"automatically,don't modify it!\n";
|
||||||
file << "#include \"" << namespacePrefix << className << ".h\"\n";
|
file << "#include \"" << namespacePrefix << className << ".h\"\n";
|
||||||
file << "#include <drogon/utils/OStringStream.h>\n";
|
file << "#include <drogon/utils/OStringStream.h>\n";
|
||||||
file << "#include <drogon/utils/Utilities.h>\n";
|
|
||||||
file << "#include <string>\n";
|
file << "#include <string>\n";
|
||||||
file << "#include <map>\n";
|
file << "#include <map>\n";
|
||||||
file << "#include <vector>\n";
|
file << "#include <vector>\n";
|
||||||
|
@ -19,10 +19,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <json/json.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
@ -37,10 +33,9 @@ std::string press::detail()
|
|||||||
" -t num number of threads(default : 1)\n"
|
" -t num number of threads(default : 1)\n"
|
||||||
" -c num concurrent connections(default : 1)\n"
|
" -c num concurrent connections(default : 1)\n"
|
||||||
" -k disable SSL certificate validation(default: enable)\n"
|
" -k disable SSL certificate validation(default: enable)\n"
|
||||||
" -f customize http request json file(default: disenable)\n"
|
|
||||||
" -q no progress indication(default: show)\n\n"
|
" -q no progress indication(default: show)\n\n"
|
||||||
"example: drogon_ctl press -n 10000 -c 100 -t 4 -q "
|
"example: drogon_ctl press -n 10000 -c 100 -t 4 -q "
|
||||||
"http://localhost:8080/index.html -f ./http_request.json\n";
|
"http://localhost:8080/index.html\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void outputErrorAndExit(const std::string_view &err)
|
void outputErrorAndExit(const std::string_view &err)
|
||||||
@ -156,24 +151,6 @@ void press::handleCommand(std::vector<std::string> ¶meters)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (param.find("-f") == 0)
|
|
||||||
{
|
|
||||||
if (param == "-f")
|
|
||||||
{
|
|
||||||
++iter;
|
|
||||||
if (iter == parameters.end())
|
|
||||||
{
|
|
||||||
outputErrorAndExit("No http request json file!");
|
|
||||||
}
|
|
||||||
httpRequestJsonFile_ = *iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
httpRequestJsonFile_ = param.substr(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (param == "-k")
|
else if (param == "-k")
|
||||||
{
|
{
|
||||||
certValidation_ = false;
|
certValidation_ = false;
|
||||||
@ -213,118 +190,6 @@ void press::handleCommand(std::vector<std::string> ¶meters)
|
|||||||
path_ = url_.substr(posOfPath);
|
path_ = url_.substr(posOfPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
http_request.json
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"header": {
|
|
||||||
"token": "e2e9d0fe-dd14-4eaf-8ac1-0997730a805d"
|
|
||||||
},
|
|
||||||
"body": {
|
|
||||||
"passwd": "123456",
|
|
||||||
"account": "10001"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (!httpRequestJsonFile_.empty())
|
|
||||||
{
|
|
||||||
Json::Value httpRequestJson;
|
|
||||||
std::ifstream httpRequestFile(httpRequestJsonFile_,
|
|
||||||
std::ifstream::binary);
|
|
||||||
if (!httpRequestFile.is_open())
|
|
||||||
{
|
|
||||||
outputErrorAndExit(std::string{"No "} + httpRequestJsonFile_);
|
|
||||||
}
|
|
||||||
httpRequestFile >> httpRequestJson;
|
|
||||||
|
|
||||||
if (!httpRequestJson.isMember("method"))
|
|
||||||
{
|
|
||||||
outputErrorAndExit("No contain method");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto methodStr = httpRequestJson["method"].asString();
|
|
||||||
std::transform(methodStr.begin(),
|
|
||||||
methodStr.end(),
|
|
||||||
methodStr.begin(),
|
|
||||||
::toupper);
|
|
||||||
|
|
||||||
auto toHttpMethod = [&]() -> drogon::HttpMethod {
|
|
||||||
if (methodStr == "GET")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Get;
|
|
||||||
}
|
|
||||||
else if (methodStr == "POST")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Post;
|
|
||||||
}
|
|
||||||
else if (methodStr == "HEAD")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Head;
|
|
||||||
}
|
|
||||||
else if (methodStr == "PUT")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Put;
|
|
||||||
}
|
|
||||||
else if (methodStr == "DELETE")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Delete;
|
|
||||||
}
|
|
||||||
else if (methodStr == "OPTIONS")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Options;
|
|
||||||
}
|
|
||||||
else if (methodStr == "PATCH")
|
|
||||||
{
|
|
||||||
return drogon::HttpMethod::Patch;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outputErrorAndExit("invalid method");
|
|
||||||
}
|
|
||||||
return drogon::HttpMethod::Get;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::string> header;
|
|
||||||
if (httpRequestJson.isMember("header"))
|
|
||||||
{
|
|
||||||
auto &jsonValue = httpRequestJson["header"];
|
|
||||||
for (const auto &key : jsonValue.getMemberNames())
|
|
||||||
{
|
|
||||||
if (jsonValue[key].isString())
|
|
||||||
{
|
|
||||||
header[key] = jsonValue[key].asString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
header[key] = jsonValue[key].toStyledString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string body;
|
|
||||||
if (httpRequestJson.isMember("body"))
|
|
||||||
{
|
|
||||||
Json::FastWriter fastWriter;
|
|
||||||
body = fastWriter.write(httpRequestJson["body"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
createHttpRequestFunc_ = [this,
|
|
||||||
method = toHttpMethod(),
|
|
||||||
body = std::move(body),
|
|
||||||
header =
|
|
||||||
std::move(header)]() -> HttpRequestPtr {
|
|
||||||
auto request = HttpRequest::newHttpRequest();
|
|
||||||
request->setPath(path_);
|
|
||||||
request->setMethod(method);
|
|
||||||
for (const auto &[field, val] : header)
|
|
||||||
request->addHeader(field, val);
|
|
||||||
if (!body.empty())
|
|
||||||
request->setBody(body);
|
|
||||||
return request;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::cout << "host=" << host_ << std::endl;
|
// std::cout << "host=" << host_ << std::endl;
|
||||||
// std::cout << "path=" << path_ << std::endl;
|
// std::cout << "path=" << path_ << std::endl;
|
||||||
doTesting();
|
doTesting();
|
||||||
@ -367,19 +232,9 @@ void press::sendRequest(const HttpClientPtr &client)
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto request = HttpRequest::newHttpRequest();
|
||||||
HttpRequestPtr request;
|
request->setPath(path_);
|
||||||
if (createHttpRequestFunc_)
|
request->setMethod(Get);
|
||||||
{
|
|
||||||
request = createHttpRequestFunc_();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
request = HttpRequest::newHttpRequest();
|
|
||||||
request->setPath(path_);
|
|
||||||
request->setMethod(Get);
|
|
||||||
}
|
|
||||||
|
|
||||||
// std::cout << "send!" << std::endl;
|
// std::cout << "send!" << std::endl;
|
||||||
client->sendRequest(
|
client->sendRequest(
|
||||||
request,
|
request,
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
#include <drogon/HttpClient.h>
|
#include <drogon/HttpClient.h>
|
||||||
#include <trantor/utils/Date.h>
|
#include <trantor/utils/Date.h>
|
||||||
#include <trantor/net/EventLoopThreadPool.h>
|
#include <trantor/net/EventLoopThreadPool.h>
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -63,8 +62,6 @@ class press : public DrObject<press>, public CommandHandler
|
|||||||
size_t numOfThreads_{1};
|
size_t numOfThreads_{1};
|
||||||
size_t numOfRequests_{1};
|
size_t numOfRequests_{1};
|
||||||
size_t numOfConnections_{1};
|
size_t numOfConnections_{1};
|
||||||
std::string httpRequestJsonFile_;
|
|
||||||
std::function<HttpRequestPtr()> createHttpRequestFunc_;
|
|
||||||
bool certValidation_{true};
|
bool certValidation_{true};
|
||||||
bool processIndication_{true};
|
bool processIndication_{true};
|
||||||
std::string url_;
|
std::string url_;
|
||||||
|
@ -310,10 +310,7 @@
|
|||||||
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
|
// Currently only gzip and br are supported. Note: max_memory_body_size and max_body_size applies twice for compressed requests.
|
||||||
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
// Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
||||||
// will be rejected.
|
// will be rejected.
|
||||||
"enabled_compressed_request": false,
|
"enabled_compressed_request": false
|
||||||
// enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
|
|
||||||
// See the wiki for more details.
|
|
||||||
"enable_request_stream": false,
|
|
||||||
},
|
},
|
||||||
//plugins: Define all plugins running in the application
|
//plugins: Define all plugins running in the application
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
@ -283,9 +283,6 @@ app:
|
|||||||
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
# Once when receiving and once when decompressing. i.e. if the decompressed body is larger than max_body_size, the request
|
||||||
# will be rejected.
|
# will be rejected.
|
||||||
enabled_compressed_request: false
|
enabled_compressed_request: false
|
||||||
# enable_request_stream: Defaults to false. If true the server will enable stream mode for http requests.
|
|
||||||
# See the wiki for more details.
|
|
||||||
enable_request_stream: false
|
|
||||||
# plugins: Define all plugins running in the application
|
# plugins: Define all plugins running in the application
|
||||||
plugins:
|
plugins:
|
||||||
# name: The class name of the plugin
|
# name: The class name of the plugin
|
||||||
|
@ -76,7 +76,7 @@ else
|
|||||||
|
|
||||||
<%c++for(auto col:cols){
|
<%c++for(auto col:cols){
|
||||||
%>
|
%>
|
||||||
const std::string [[className]]::Cols::_{%col.colName_%} = "{%escapeIdentifier(col.colName_, rdbms)%}";
|
const std::string [[className]]::Cols::_{%col.colName_%} = "{%col.colName_%}";
|
||||||
<%c++
|
<%c++
|
||||||
}%>
|
}%>
|
||||||
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
|
<%c++if(@@.get<int>("hasPrimaryKey")<=1){%>
|
||||||
@ -102,7 +102,7 @@ if(!schema.empty())
|
|||||||
{
|
{
|
||||||
$$<<schema<<".";
|
$$<<schema<<".";
|
||||||
}
|
}
|
||||||
%>{%escapeIdentifier(@@.get<std::string>("tableName"), rdbms)%}";
|
%>[[tableName]]";
|
||||||
|
|
||||||
const std::vector<typename [[className]]::MetaData> [[className]]::metaData_={
|
const std::vector<typename [[className]]::MetaData> [[className]]::metaData_={
|
||||||
<%c++for(size_t i=0;i<cols.size();i++){
|
<%c++for(size_t i=0;i<cols.size();i++){
|
||||||
|
@ -31,10 +31,7 @@ add_executable(redis_simple redis/main.cc
|
|||||||
add_executable(redis_chat redis_chat/main.cc
|
add_executable(redis_chat redis_chat/main.cc
|
||||||
redis_chat/controllers/Chat.cc)
|
redis_chat/controllers/Chat.cc)
|
||||||
|
|
||||||
add_executable(async_stream async_stream/main.cc
|
add_executable(async_stream async_stream/main.cc)
|
||||||
async_stream/RequestStreamExampleCtrl.cc)
|
|
||||||
|
|
||||||
add_executable(cors cors/main.cc)
|
|
||||||
|
|
||||||
set(example_targets
|
set(example_targets
|
||||||
benchmark
|
benchmark
|
||||||
@ -47,8 +44,7 @@ set(example_targets
|
|||||||
jsonstore
|
jsonstore
|
||||||
redis_simple
|
redis_simple
|
||||||
redis_chat
|
redis_chat
|
||||||
async_stream
|
async_stream)
|
||||||
cors)
|
|
||||||
|
|
||||||
# Add warnings for our example targets--some warnings (such as -Wunused-parameter) only appear
|
# Add warnings for our example targets--some warnings (such as -Wunused-parameter) only appear
|
||||||
# when the templated functions are instantiated at their point of use.
|
# when the templated functions are instantiated at their point of use.
|
||||||
|
@ -2,21 +2,19 @@
|
|||||||
|
|
||||||
The following examples can help you understand how to use Drogon:
|
The following examples can help you understand how to use Drogon:
|
||||||
|
|
||||||
1. [helloworld](https://github.com/drogonframework/drogon/tree/master/examples/helloworld) - The multiple ways of "Hello, World!"
|
1. [helloworld](https://github.com/an-tao/drogon/tree/master/examples/helloworld) - The multiple ways of "Hello, World!"
|
||||||
2. [client_example](https://github.com/drogonframework/drogon/tree/master/examples/client_example/main.cc) - A client example
|
2. [client_example](https://github.com/an-tao/drogon/tree/master/examples/client_example/main.cc) - A client example.
|
||||||
3. [websocket_client](https://github.com/drogonframework/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client
|
3. [websocket_client](https://github.com/an-tao/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client
|
||||||
4. [login_session](https://github.com/drogonframework/drogon/tree/master/examples/login_session) - How to use the built-in session system to handle login and out
|
4. [login_session](https://github.com/an-tao/drogon/tree/master/examples/login_session) - How to use the built-in session system to handle login and out
|
||||||
5. [file_upload](https://github.com/drogonframework/drogon/tree/master/examples/file_upload) - How to handle file uploads in Drogon
|
5. [file_upload](https://github.com/an-tao/drogon/tree/master/examples/file_upload) - How to handle file uploads in Drogon
|
||||||
6. [simple_reverse_proxy](https://github.com/drogonframework/drogon/tree/master/examples/simple_reverse_proxy) - An example showing how to use Drogon as a HTTP reverse
|
6. [simple_reverse_proxy](https://github.com/an-tao/drogon/tree/master/examples/simple_reverse_proxy) - A Example showing how to use drogon as a http reverse
|
||||||
proxy with a simple round robin
|
proxy with a simple round robin.
|
||||||
7. [benchmark](https://github.com/drogonframework/drogon/tree/master/examples/benchmark) - Basic benchmark(https://github.com/drogonframework/drogon/wiki/13-Benchmarks) example
|
7. [benchmark](https://github.com/an-tao/drogon/tree/master/examples/benchmark) - Basic benchmark example. see [wiki benchmarks](https://github.com/an-tao/drogon/wiki/13-Benchmarks)
|
||||||
8. [jsonstore](https://github.com/drogonframework/drogon/tree/master/examples/jsonstore) - Implementation of a [jsonstore](https://github.com/bluzi/jsonstore)-like storage service that is concurrent and stores in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon
|
8. [jsonstore](https://github.com/an-tao/drogon/tree/master/examples/jsonstore) - Implementation of a [jsonstore](https://github.com/bluzi/jsonstore)-like storage service that is concurrent and stores in memory. Serving as a showcase on how to build a minimally useful RESTful APIs in Drogon.
|
||||||
9. [redis](https://github.com/drogonframework/drogon/tree/master/examples/redis) - A simple example of Redis
|
9. [redis](https://github.com/an-tao/drogon/tree/master/examples/redis) - A simple example of Redis
|
||||||
10. [websocket_server](https://github.com/drogonframework/drogon/tree/master/examples/websocket_server) - A example websocket chat room server
|
10. [websocket_server](https://github.com/drogonframework/drogon/tree/master/examples/websocket_server) - Example WebSocker chat room server
|
||||||
11. [redis_cache](https://github.com/drogonframework/drogon/tree/master/examples/redis_cache) - An example for using coroutines of Redis clients
|
11. [redis_cache](https://github.com/an-tao/drogon/tree/master/examples/redis_cache) - An example for using coroutines of redis clients
|
||||||
12. [redis_chat](https://github.com/drogonframework/drogon/tree/master/examples/redis_chat) - A chatroom server built with websocket and Redis pub/sub service
|
12. [redis_chat](https://github.com/an-tao/drogon/tree/master/examples/redis_chat) - A chatroom server built with websocket and redis pub/sub service.
|
||||||
13. [prometheus_example](https://github.com/drogonframework/drogon/tree/master/examples/prometheus_example) - An example of how to use the Prometheus exporter in Drogon
|
|
||||||
14. [cors](https://github.com/drogonframework/drogon/tree/master/examples/cors) - An example demonstrating how to implement CORS (Cross-Origin Resource Sharing) support in Drogon
|
|
||||||
|
|
||||||
### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite
|
### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite
|
||||||
|
|
||||||
@ -24,4 +22,4 @@ I created a benchmark suite for the `tfb`, see [here](https://github.com/TechEmp
|
|||||||
|
|
||||||
### Another test suite
|
### Another test suite
|
||||||
|
|
||||||
I also created a test suite for another web frameworks benchmark repository, see [here](https://github.com/the-benchmarker/web-frameworks/tree/master/cpp/drogon). In this project, Drogon is used as a sub-module (locally include in the project).
|
I also created a test suite for another web frameworks benchmark repository, see [here](https://github.com/the-benchmarker/web-frameworks/tree/master/cpp/drogon), in this project, drogon is used as a sub-module (locally include in the project).
|
||||||
|
@ -1,167 +0,0 @@
|
|||||||
#include <drogon/drogon.h>
|
|
||||||
#include <drogon/HttpController.h>
|
|
||||||
#include <drogon/HttpRequest.h>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
class StreamEchoReader : public RequestStreamReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
StreamEchoReader(ResponseStreamPtr respStream)
|
|
||||||
: respStream_(std::move(respStream))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamData(const char *data, size_t length) override
|
|
||||||
{
|
|
||||||
LOG_INFO << "onStreamData[" << length << "]";
|
|
||||||
respStream_->send({data, length});
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamFinish(std::exception_ptr ptr) override
|
|
||||||
{
|
|
||||||
if (ptr)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::rethrow_exception(ptr);
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
LOG_ERROR << "onStreamError: " << e.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_INFO << "onStreamFinish";
|
|
||||||
}
|
|
||||||
respStream_->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ResponseStreamPtr respStream_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RequestStreamExampleCtrl : public HttpController<RequestStreamExampleCtrl>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
METHOD_LIST_BEGIN
|
|
||||||
ADD_METHOD_TO(RequestStreamExampleCtrl::stream_echo, "/stream_echo", Post);
|
|
||||||
ADD_METHOD_TO(RequestStreamExampleCtrl::stream_upload,
|
|
||||||
"/stream_upload",
|
|
||||||
Post);
|
|
||||||
METHOD_LIST_END
|
|
||||||
|
|
||||||
void stream_echo(
|
|
||||||
const HttpRequestPtr &,
|
|
||||||
RequestStreamPtr &&stream,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
|
||||||
{
|
|
||||||
auto resp = drogon::HttpResponse::newAsyncStreamResponse(
|
|
||||||
[stream](ResponseStreamPtr respStream) {
|
|
||||||
stream->setStreamReader(
|
|
||||||
std::make_shared<StreamEchoReader>(std::move(respStream)));
|
|
||||||
});
|
|
||||||
callback(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stream_upload(
|
|
||||||
const HttpRequestPtr &req,
|
|
||||||
RequestStreamPtr &&stream,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
|
||||||
{
|
|
||||||
struct Entry
|
|
||||||
{
|
|
||||||
MultipartHeader header;
|
|
||||||
std::string tmpName;
|
|
||||||
std::ofstream file;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto files = std::make_shared<std::vector<Entry>>();
|
|
||||||
auto reader = RequestStreamReader::newMultipartReader(
|
|
||||||
req,
|
|
||||||
[files](MultipartHeader &&header) {
|
|
||||||
LOG_INFO << "Multipart name: " << header.name
|
|
||||||
<< ", filename:" << header.filename
|
|
||||||
<< ", contentType:" << header.contentType;
|
|
||||||
|
|
||||||
files->push_back({std::move(header)});
|
|
||||||
auto tmpName = drogon::utils::genRandomString(40);
|
|
||||||
if (!files->back().header.filename.empty())
|
|
||||||
{
|
|
||||||
files->back().tmpName = tmpName;
|
|
||||||
files->back().file.open("uploads/" + tmpName,
|
|
||||||
std::ios::trunc);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[files](const char *data, size_t length) {
|
|
||||||
if (files->back().tmpName.empty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto ¤tFile = files->back().file;
|
|
||||||
if (length == 0)
|
|
||||||
{
|
|
||||||
LOG_INFO << "file finish";
|
|
||||||
if (currentFile.is_open())
|
|
||||||
{
|
|
||||||
currentFile.flush();
|
|
||||||
currentFile.close();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG_INFO << "data[" << length << "]: ";
|
|
||||||
if (currentFile.is_open())
|
|
||||||
{
|
|
||||||
LOG_INFO << "write file";
|
|
||||||
currentFile.write(data, length);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_ERROR << "file not open";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[files, callback = std::move(callback)](std::exception_ptr ex) {
|
|
||||||
if (ex)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::rethrow_exception(std::move(ex));
|
|
||||||
}
|
|
||||||
catch (const StreamError &e)
|
|
||||||
{
|
|
||||||
LOG_ERROR << "stream error: " << e.what();
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
LOG_ERROR << "multipart error: " << e.what();
|
|
||||||
}
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setStatusCode(k400BadRequest);
|
|
||||||
resp->setBody("error\n");
|
|
||||||
callback(resp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_INFO << "stream finish, received " << files->size()
|
|
||||||
<< " files";
|
|
||||||
Json::Value respJson;
|
|
||||||
for (const auto &item : *files)
|
|
||||||
{
|
|
||||||
if (item.tmpName.empty())
|
|
||||||
continue;
|
|
||||||
Json::Value entry;
|
|
||||||
entry["name"] = item.header.name;
|
|
||||||
entry["filename"] = item.header.filename;
|
|
||||||
entry["tmpName"] = item.tmpName;
|
|
||||||
respJson.append(entry);
|
|
||||||
}
|
|
||||||
auto resp = HttpResponse::newHttpJsonResponse(respJson);
|
|
||||||
callback(resp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream->setStreamReader(std::move(reader));
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,33 +1,15 @@
|
|||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <memory>
|
||||||
#include <mutex>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <trantor/utils/Logger.h>
|
|
||||||
#include <trantor/net/callbacks.h>
|
|
||||||
#include <trantor/net/TcpConnection.h>
|
|
||||||
|
|
||||||
using namespace drogon;
|
using namespace drogon;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
std::mutex mutex;
|
|
||||||
std::unordered_map<trantor::TcpConnectionPtr, std::function<void()>>
|
|
||||||
connMapping;
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
app().registerHandler(
|
app().registerHandler(
|
||||||
"/stream",
|
"/stream",
|
||||||
[](const HttpRequestPtr &req,
|
[](const HttpRequestPtr &,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
std::function<void(const HttpResponsePtr &)> &&callback) {
|
||||||
const auto &weakConnPtr = req->getConnectionPtr();
|
|
||||||
if (auto connPtr = weakConnPtr.lock())
|
|
||||||
{
|
|
||||||
std::lock_guard lk(mutex);
|
|
||||||
connMapping.emplace(std::move(connPtr), [] {
|
|
||||||
LOG_INFO << "call stop or other options!!!!";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
auto resp = drogon::HttpResponse::newAsyncStreamResponse(
|
auto resp = drogon::HttpResponse::newAsyncStreamResponse(
|
||||||
[](drogon::ResponseStreamPtr stream) {
|
[](drogon::ResponseStreamPtr stream) {
|
||||||
std::thread([stream =
|
std::thread([stream =
|
||||||
@ -46,68 +28,6 @@ int main()
|
|||||||
callback(resp);
|
callback(resp);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Example: register a stream-mode function handler
|
|
||||||
app().registerHandler(
|
|
||||||
"/stream_req",
|
|
||||||
[](const HttpRequestPtr &req,
|
|
||||||
RequestStreamPtr &&stream,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
|
||||||
if (!stream)
|
|
||||||
{
|
|
||||||
LOG_INFO << "stream mode is not enabled";
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setStatusCode(k400BadRequest);
|
|
||||||
resp->setBody("no stream");
|
|
||||||
callback(resp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto reader = RequestStreamReader::newReader(
|
|
||||||
[](const char *data, size_t length) {
|
|
||||||
LOG_INFO << "piece[" << length
|
|
||||||
<< "]: " << std::string_view{data, length};
|
|
||||||
},
|
|
||||||
[callback = std::move(callback)](std::exception_ptr ex) {
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
if (ex)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::rethrow_exception(std::move(ex));
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
LOG_ERROR << "stream error: " << e.what();
|
|
||||||
}
|
|
||||||
resp->setStatusCode(k400BadRequest);
|
|
||||||
resp->setBody("error\n");
|
|
||||||
callback(resp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_INFO << "stream finish";
|
|
||||||
resp->setBody("success\n");
|
|
||||||
callback(resp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream->setStreamReader(std::move(reader));
|
|
||||||
},
|
|
||||||
{Post});
|
|
||||||
|
|
||||||
LOG_INFO << "Server running on 127.0.0.1:8848";
|
LOG_INFO << "Server running on 127.0.0.1:8848";
|
||||||
app().enableRequestStream(); // This is for request stream.
|
|
||||||
app().setConnectionCallback([](const trantor::TcpConnectionPtr &conn) {
|
|
||||||
if (conn->disconnected())
|
|
||||||
{
|
|
||||||
std::lock_guard lk(mutex);
|
|
||||||
if (auto it = connMapping.find(conn); it != connMapping.end())
|
|
||||||
{
|
|
||||||
LOG_INFO << "disconnect";
|
|
||||||
connMapping[conn]();
|
|
||||||
connMapping.erase(conn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
app().addListener("127.0.0.1", 8848).run();
|
app().addListener("127.0.0.1", 8848).run();
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,6 @@ int main()
|
|||||||
std::cout << "count=" << nth_resp << std::endl;
|
std::cout << "count=" << nth_resp << std::endl;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
std::cout << "requestsBufferSize:" << client->requestsBufferSize()
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app().run();
|
app().run();
|
||||||
|
@ -1,153 +0,0 @@
|
|||||||
#include <drogon/HttpAppFramework.h>
|
|
||||||
#include <drogon/HttpResponse.h>
|
|
||||||
#include <drogon/drogon.h>
|
|
||||||
#include "trantor/utils/Logger.h"
|
|
||||||
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
/// Configure Cross-Origin Resource Sharing (CORS) support.
|
|
||||||
///
|
|
||||||
/// This function registers both synchronous pre-processing advice for handling
|
|
||||||
/// OPTIONS preflight requests and post-handling advice to inject CORS headers
|
|
||||||
/// into all responses dynamically based on the incoming request headers.
|
|
||||||
void setupCors()
|
|
||||||
{
|
|
||||||
// Register sync advice to handle CORS preflight (OPTIONS) requests
|
|
||||||
drogon::app().registerSyncAdvice([](const drogon::HttpRequestPtr &req)
|
|
||||||
-> drogon::HttpResponsePtr {
|
|
||||||
if (req->method() == drogon::HttpMethod::Options)
|
|
||||||
{
|
|
||||||
auto resp = drogon::HttpResponse::newHttpResponse();
|
|
||||||
|
|
||||||
// Set Access-Control-Allow-Origin header based on the Origin
|
|
||||||
// request header
|
|
||||||
const auto &origin = req->getHeader("Origin");
|
|
||||||
if (!origin.empty())
|
|
||||||
{
|
|
||||||
resp->addHeader("Access-Control-Allow-Origin", origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set Access-Control-Allow-Methods based on the requested method
|
|
||||||
const auto &requestMethod =
|
|
||||||
req->getHeader("Access-Control-Request-Method");
|
|
||||||
if (!requestMethod.empty())
|
|
||||||
{
|
|
||||||
resp->addHeader("Access-Control-Allow-Methods", requestMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow credentials to be included in cross-origin requests
|
|
||||||
resp->addHeader("Access-Control-Allow-Credentials", "true");
|
|
||||||
|
|
||||||
// Set allowed headers from the Access-Control-Request-Headers
|
|
||||||
// header
|
|
||||||
const auto &requestHeaders =
|
|
||||||
req->getHeader("Access-Control-Request-Headers");
|
|
||||||
if (!requestHeaders.empty())
|
|
||||||
{
|
|
||||||
resp->addHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::move(resp);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register post-handling advice to add CORS headers to all responses
|
|
||||||
drogon::app().registerPostHandlingAdvice(
|
|
||||||
[](const drogon::HttpRequestPtr &req,
|
|
||||||
const drogon::HttpResponsePtr &resp) -> void {
|
|
||||||
// Set Access-Control-Allow-Origin based on the Origin request
|
|
||||||
// header
|
|
||||||
const auto &origin = req->getHeader("Origin");
|
|
||||||
if (!origin.empty())
|
|
||||||
{
|
|
||||||
resp->addHeader("Access-Control-Allow-Origin", origin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reflect the requested Access-Control-Request-Method back in the
|
|
||||||
// response
|
|
||||||
const auto &requestMethod =
|
|
||||||
req->getHeader("Access-Control-Request-Method");
|
|
||||||
if (!requestMethod.empty())
|
|
||||||
{
|
|
||||||
resp->addHeader("Access-Control-Allow-Methods", requestMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow credentials to be included in cross-origin requests
|
|
||||||
resp->addHeader("Access-Control-Allow-Credentials", "true");
|
|
||||||
|
|
||||||
// Reflect the requested Access-Control-Request-Headers back
|
|
||||||
const auto &requestHeaders =
|
|
||||||
req->getHeader("Access-Control-Request-Headers");
|
|
||||||
if (!requestHeaders.empty())
|
|
||||||
{
|
|
||||||
resp->addHeader("Access-Control-Allow-Headers", requestHeaders);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function to start the Drogon application with CORS-enabled routes.
|
|
||||||
* This example includes:
|
|
||||||
* - A simple GET endpoint `/hello` that returns a greeting message.
|
|
||||||
* - A POST endpoint `/echo` that echoes back the request body.
|
|
||||||
* You can test with curl to test the CORS support:
|
|
||||||
*
|
|
||||||
```
|
|
||||||
curl -i -X OPTIONS http://localhost:8000/echo \
|
|
||||||
-H "Origin: http://localhost:3000" \
|
|
||||||
-H "Access-Control-Request-Method: POST" \
|
|
||||||
-H "Access-Control-Request-Headers: Content-Type"
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```
|
|
||||||
curl -i -X POST http://localhost:8000/echo \
|
|
||||||
-H "Origin: http://localhost:3000" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"key":"value"}'
|
|
||||||
```
|
|
||||||
*/
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// Listen on port 8000 for all interfaces
|
|
||||||
app().addListener("0.0.0.0", 8000);
|
|
||||||
|
|
||||||
// Setup CORS support
|
|
||||||
setupCors();
|
|
||||||
|
|
||||||
// Register /hello route for GET and OPTIONS methods
|
|
||||||
app().registerHandler(
|
|
||||||
"/hello",
|
|
||||||
[](const HttpRequestPtr &req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setBody("Hello from Drogon!");
|
|
||||||
|
|
||||||
// Log client IP address
|
|
||||||
LOG_INFO << "Request to /hello from " << req->getPeerAddr().toIp();
|
|
||||||
|
|
||||||
callback(resp);
|
|
||||||
},
|
|
||||||
{Get, Options});
|
|
||||||
|
|
||||||
// Register /echo route for POST and OPTIONS methods
|
|
||||||
app().registerHandler(
|
|
||||||
"/echo",
|
|
||||||
[](const HttpRequestPtr &req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setBody(std::string("Echo: ").append(req->getBody()));
|
|
||||||
|
|
||||||
// Log client IP and request body
|
|
||||||
LOG_INFO << "Request to /echo from " << req->getPeerAddr().toIp();
|
|
||||||
LOG_INFO << "Echo content: " << req->getBody();
|
|
||||||
|
|
||||||
callback(resp);
|
|
||||||
},
|
|
||||||
{Post, Options});
|
|
||||||
|
|
||||||
// Start the application main loop
|
|
||||||
app().run();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,11 +1,3 @@
|
|||||||
#include <trantor/utils/Logger.h>
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#else
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <drogon/drogon.h>
|
#include <drogon/drogon.h>
|
||||||
using namespace drogon;
|
using namespace drogon;
|
||||||
|
|
||||||
@ -16,10 +8,8 @@ int main()
|
|||||||
// sent to Drogon
|
// sent to Drogon
|
||||||
app().registerHandler(
|
app().registerHandler(
|
||||||
"/",
|
"/",
|
||||||
[](const HttpRequestPtr &request,
|
[](const HttpRequestPtr &,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) {
|
std::function<void(const HttpResponsePtr &)> &&callback) {
|
||||||
LOG_INFO << "connected:"
|
|
||||||
<< (request->connected() ? "true" : "false");
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
auto resp = HttpResponse::newHttpResponse();
|
||||||
resp->setBody("Hello, World!");
|
resp->setBody("Hello, World!");
|
||||||
callback(resp);
|
callback(resp);
|
||||||
@ -71,23 +61,6 @@ int main()
|
|||||||
},
|
},
|
||||||
{Get});
|
{Get});
|
||||||
|
|
||||||
app()
|
|
||||||
.setBeforeListenSockOptCallback([](int fd) {
|
|
||||||
LOG_INFO << "setBeforeListenSockOptCallback:" << fd;
|
|
||||||
#ifdef _WIN32
|
|
||||||
#elif __linux__
|
|
||||||
int enable = 1;
|
|
||||||
if (setsockopt(
|
|
||||||
fd, IPPROTO_TCP, TCP_FASTOPEN, &enable, sizeof(enable)) ==
|
|
||||||
-1)
|
|
||||||
{
|
|
||||||
LOG_INFO << "setsockopt TCP_FASTOPEN failed";
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#endif
|
|
||||||
})
|
|
||||||
.setAfterAcceptSockOptCallback([](int) {});
|
|
||||||
|
|
||||||
// Ask Drogon to listen on 127.0.0.1 port 8848. Drogon supports listening
|
// Ask Drogon to listen on 127.0.0.1 port 8848. Drogon supports listening
|
||||||
// on multiple IP addresses by adding multiple listeners. For example, if
|
// on multiple IP addresses by adding multiple listeners. For example, if
|
||||||
// you want the server also listen on 127.0.0.1 port 5555. Just add another
|
// you want the server also listen on 127.0.0.1 port 5555. Just add another
|
||||||
|
561
examples/prometheus_example/.gitignore
vendored
561
examples/prometheus_example/.gitignore
vendored
@ -1,561 +0,0 @@
|
|||||||
# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,visualstudio,visualstudiocode,cmake,c,c++
|
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,visualstudio,visualstudiocode,cmake,c,c++
|
|
||||||
|
|
||||||
### C ###
|
|
||||||
# Prerequisites
|
|
||||||
*.d
|
|
||||||
|
|
||||||
# Object files
|
|
||||||
*.o
|
|
||||||
*.ko
|
|
||||||
*.obj
|
|
||||||
*.elf
|
|
||||||
|
|
||||||
# Linker output
|
|
||||||
*.ilk
|
|
||||||
*.map
|
|
||||||
*.exp
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
*.gch
|
|
||||||
*.pch
|
|
||||||
|
|
||||||
# Libraries
|
|
||||||
*.lib
|
|
||||||
*.a
|
|
||||||
*.la
|
|
||||||
*.lo
|
|
||||||
|
|
||||||
# Shared objects (inc. Windows DLLs)
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.so.*
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
*.exe
|
|
||||||
*.out
|
|
||||||
*.app
|
|
||||||
*.i*86
|
|
||||||
*.x86_64
|
|
||||||
*.hex
|
|
||||||
|
|
||||||
# Debug files
|
|
||||||
*.dSYM/
|
|
||||||
*.su
|
|
||||||
*.idb
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# Kernel Module Compile Results
|
|
||||||
*.mod*
|
|
||||||
*.cmd
|
|
||||||
.tmp_versions/
|
|
||||||
modules.order
|
|
||||||
Module.symvers
|
|
||||||
Mkfile.old
|
|
||||||
dkms.conf
|
|
||||||
|
|
||||||
### C++ ###
|
|
||||||
# Prerequisites
|
|
||||||
|
|
||||||
# Compiled Object files
|
|
||||||
*.slo
|
|
||||||
|
|
||||||
# Precompiled Headers
|
|
||||||
|
|
||||||
# Linker files
|
|
||||||
|
|
||||||
# Debugger Files
|
|
||||||
|
|
||||||
# Compiled Dynamic libraries
|
|
||||||
|
|
||||||
# Fortran module files
|
|
||||||
*.mod
|
|
||||||
*.smod
|
|
||||||
|
|
||||||
# Compiled Static libraries
|
|
||||||
*.lai
|
|
||||||
|
|
||||||
# Executables
|
|
||||||
|
|
||||||
### CMake ###
|
|
||||||
CMakeLists.txt.user
|
|
||||||
CMakeCache.txt
|
|
||||||
CMakeFiles
|
|
||||||
CMakeScripts
|
|
||||||
Testing
|
|
||||||
Makefile
|
|
||||||
cmake_install.cmake
|
|
||||||
install_manifest.txt
|
|
||||||
compile_commands.json
|
|
||||||
CTestTestfile.cmake
|
|
||||||
_deps
|
|
||||||
CMakeUserPresets.json
|
|
||||||
|
|
||||||
### CMake Patch ###
|
|
||||||
# External projects
|
|
||||||
*-prefix/
|
|
||||||
|
|
||||||
### Intellij+all ###
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
### Intellij+all Patch ###
|
|
||||||
# Ignores the whole .idea folder and all .iml files
|
|
||||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
|
||||||
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
|
||||||
|
|
||||||
*.iml
|
|
||||||
modules.xml
|
|
||||||
.idea/misc.xml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
# Sonarlint plugin
|
|
||||||
.idea/sonarlint
|
|
||||||
|
|
||||||
### VisualStudioCode ###
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
### VisualStudioCode Patch ###
|
|
||||||
# Ignore all local history of files
|
|
||||||
.history
|
|
||||||
.ionide
|
|
||||||
|
|
||||||
### VisualStudio ###
|
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
|
||||||
## files generated by popular Visual Studio add-ons.
|
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.rsuser
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Mono auto generated files
|
|
||||||
mono_crash.*
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
[Ww][Ii][Nn]32/
|
|
||||||
[Aa][Rr][Mm]/
|
|
||||||
[Aa][Rr][Mm]64/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
[Ll]ogs/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUnit
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
nunit-*.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
|
|
||||||
# ASP.NET Scaffolding
|
|
||||||
ScaffoldingReadMe.txt
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_h.h
|
|
||||||
*.meta
|
|
||||||
*.iobj
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*_wpftmp.csproj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Coverlet is a free, cross platform Code Coverage Tool
|
|
||||||
coverage*[.json, .xml, .info]
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# NuGet Symbol Packages
|
|
||||||
*.snupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
*.appxbundle
|
|
||||||
*.appxupload
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!?*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
|
||||||
*.dbmdl
|
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
*- [Bb]ackup.rdl
|
|
||||||
*- [Bb]ackup ([0-9]).rdl
|
|
||||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# CodeRush personal settings
|
|
||||||
.cr/personal
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|
||||||
# Local History for Visual Studio
|
|
||||||
.localhistory/
|
|
||||||
|
|
||||||
# BeatPulse healthcheck temp database
|
|
||||||
healthchecksdb
|
|
||||||
|
|
||||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
|
||||||
MigrationBackup/
|
|
||||||
|
|
||||||
# Ionide (cross platform F# VS Code tools) working folder
|
|
||||||
.ionide/
|
|
||||||
|
|
||||||
# Fody - auto-generated XML schema
|
|
||||||
FodyWeavers.xsd
|
|
||||||
|
|
||||||
### VisualStudio Patch ###
|
|
||||||
# Additional files built by Visual Studio
|
|
||||||
*.tlog
|
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,visualstudio,visualstudiocode,cmake,c,c++
|
|
@ -1,75 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
|
||||||
project(prometheus_example CXX)
|
|
||||||
|
|
||||||
include(CheckIncludeFileCXX)
|
|
||||||
|
|
||||||
check_include_file_cxx(any HAS_ANY)
|
|
||||||
check_include_file_cxx(string_view HAS_STRING_VIEW)
|
|
||||||
check_include_file_cxx(coroutine HAS_COROUTINE)
|
|
||||||
if (NOT "${CMAKE_CXX_STANDARD}" STREQUAL "")
|
|
||||||
# Do nothing
|
|
||||||
elseif (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
elseif (HAS_ANY AND HAS_STRING_VIEW)
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
else ()
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} main.cc)
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# If you include the drogon source code locally in your project, use this method
|
|
||||||
# to add drogon
|
|
||||||
# add_subdirectory(drogon)
|
|
||||||
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
|
|
||||||
#
|
|
||||||
# and comment out the following lines
|
|
||||||
find_package(Drogon CONFIG REQUIRED)
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
if (CMAKE_CXX_STANDARD LESS 17)
|
|
||||||
message(FATAL_ERROR "c++17 or higher is required")
|
|
||||||
elseif (CMAKE_CXX_STANDARD LESS 20)
|
|
||||||
message(STATUS "use c++17")
|
|
||||||
else ()
|
|
||||||
message(STATUS "use c++20")
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
aux_source_directory(controllers CTL_SRC)
|
|
||||||
aux_source_directory(filters FILTER_SRC)
|
|
||||||
aux_source_directory(plugins PLUGIN_SRC)
|
|
||||||
aux_source_directory(models MODEL_SRC)
|
|
||||||
|
|
||||||
drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR})
|
|
||||||
# use the following line to create views with namespaces.
|
|
||||||
# drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
|
|
||||||
# ${CMAKE_CURRENT_BINARY_DIR} TRUE)
|
|
||||||
# use the following line to create views with namespace CHANGE_ME prefixed
|
|
||||||
# and path namespaces.
|
|
||||||
# drogon_create_views(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/views
|
|
||||||
# ${CMAKE_CURRENT_BINARY_DIR} TRUE CHANGE_ME)
|
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME}
|
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/models)
|
|
||||||
target_sources(${PROJECT_NAME}
|
|
||||||
PRIVATE
|
|
||||||
${SRC_DIR}
|
|
||||||
${CTL_SRC}
|
|
||||||
${FILTER_SRC}
|
|
||||||
${PLUGIN_SRC}
|
|
||||||
${MODEL_SRC})
|
|
||||||
# ##############################################################################
|
|
||||||
# uncomment the following line for dynamically loading views
|
|
||||||
# set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
|
|
||||||
add_subdirectory(test)
|
|
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
"listeners": [
|
|
||||||
{
|
|
||||||
"address": "0.0.0.0",
|
|
||||||
"port": 5555,
|
|
||||||
"https": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "drogon::plugin::PromExporter",
|
|
||||||
"dependencies": [],
|
|
||||||
"config": {
|
|
||||||
"path": "/metrics",
|
|
||||||
"collectors":[
|
|
||||||
{
|
|
||||||
"name": "http_requests_total",
|
|
||||||
"help": "The total number of http requests",
|
|
||||||
"type": "counter",
|
|
||||||
"labels": ["method", "path"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "http_request_duration_seconds",
|
|
||||||
"help": "The processing time of http requests, in seconds",
|
|
||||||
"type": "histogram",
|
|
||||||
"labels": ["method", "path"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
#include "PromTestCtrl.h"
|
|
||||||
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
void PromTestCtrl::fast(const HttpRequestPtr &req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
|
||||||
{
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setBody("Hello, world!");
|
|
||||||
callback(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
drogon::AsyncTask PromTestCtrl::slow(
|
|
||||||
const HttpRequestPtr req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> callback)
|
|
||||||
{
|
|
||||||
// sleep for a random time between 1 and 3 seconds
|
|
||||||
static std::once_flag flag;
|
|
||||||
std::call_once(flag, []() { srand(time(nullptr)); });
|
|
||||||
auto duration = 1 + (rand() % 3);
|
|
||||||
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
|
|
||||||
co_await drogon::sleepCoro(loop, std::chrono::seconds(duration));
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setBody("Hello, world!");
|
|
||||||
callback(resp);
|
|
||||||
co_return;
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <drogon/HttpController.h>
|
|
||||||
#include <drogon/utils/coroutine.h>
|
|
||||||
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
class PromTestCtrl : public drogon::HttpController<PromTestCtrl>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
METHOD_LIST_BEGIN
|
|
||||||
ADD_METHOD_TO(PromTestCtrl::fast, "/fast", "PromStat");
|
|
||||||
ADD_METHOD_TO(PromTestCtrl::slow, "/slow", "PromStat");
|
|
||||||
METHOD_LIST_END
|
|
||||||
|
|
||||||
void fast(const HttpRequestPtr &req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
|
||||||
drogon::AsyncTask slow(
|
|
||||||
const HttpRequestPtr req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> callback);
|
|
||||||
};
|
|
@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* PromStat.cc
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "PromStat.h"
|
|
||||||
#include <drogon/plugins/PromExporter.h>
|
|
||||||
#include <drogon/utils/monitoring/Counter.h>
|
|
||||||
#include <drogon/utils/monitoring/Histogram.h>
|
|
||||||
#include <drogon/HttpAppFramework.h>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
using namespace std::literals::chrono_literals;
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
Task<HttpResponsePtr> PromStat::invoke(const HttpRequestPtr &req,
|
|
||||||
MiddlewareNextAwaiter &&next)
|
|
||||||
{
|
|
||||||
std::string path{req->matchedPathPattern()};
|
|
||||||
auto method = req->methodString();
|
|
||||||
auto promExporter = app().getPlugin<drogon::plugin::PromExporter>();
|
|
||||||
if (promExporter)
|
|
||||||
{
|
|
||||||
auto collector =
|
|
||||||
promExporter->getCollector<drogon::monitoring::Counter>(
|
|
||||||
"http_requests_total");
|
|
||||||
if (collector)
|
|
||||||
{
|
|
||||||
collector->metric({method, path})->increment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto start = trantor::Date::date();
|
|
||||||
auto resp = co_await next;
|
|
||||||
if (promExporter)
|
|
||||||
{
|
|
||||||
auto collector =
|
|
||||||
promExporter->getCollector<drogon::monitoring::Histogram>(
|
|
||||||
"http_request_duration_seconds");
|
|
||||||
if (collector)
|
|
||||||
{
|
|
||||||
static const std::vector<double> boundaries{
|
|
||||||
0.0001, 0.001, 0.01, 0.1, 0.5, 1, 2, 3};
|
|
||||||
auto end = trantor::Date::date();
|
|
||||||
auto duration =
|
|
||||||
end.microSecondsSinceEpoch() - start.microSecondsSinceEpoch();
|
|
||||||
collector->metric({method, path}, boundaries, 1h, 6)
|
|
||||||
->observe((double)duration / 1000000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
co_return resp;
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* PromStat.h
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <drogon/HttpMiddleware.h>
|
|
||||||
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
class PromStat : public HttpCoroMiddleware<PromStat>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PromStat()
|
|
||||||
{
|
|
||||||
void(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~PromStat()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<HttpResponsePtr> invoke(const HttpRequestPtr &req,
|
|
||||||
MiddlewareNextAwaiter &&next) override;
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
#include <drogon/drogon.h>
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
drogon::app().loadConfigFile("../config.json").run();
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
|
||||||
project(prometheus_example_test CXX)
|
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} test_main.cc)
|
|
||||||
|
|
||||||
# ##############################################################################
|
|
||||||
# If you include the drogon source code locally in your project, use this method
|
|
||||||
# to add drogon
|
|
||||||
# target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
|
|
||||||
#
|
|
||||||
# and comment out the following lines
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)
|
|
||||||
|
|
||||||
ParseAndAddDrogonTests(${PROJECT_NAME})
|
|
@ -1,32 +0,0 @@
|
|||||||
#define DROGON_TEST_MAIN
|
|
||||||
#include <drogon/drogon_test.h>
|
|
||||||
#include <drogon/drogon.h>
|
|
||||||
|
|
||||||
DROGON_TEST(BasicTest)
|
|
||||||
{
|
|
||||||
// Add your tests here
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
std::promise<void> p1;
|
|
||||||
std::future<void> f1 = p1.get_future();
|
|
||||||
|
|
||||||
// Start the main loop on another thread
|
|
||||||
std::thread thr([&]() {
|
|
||||||
// Queues the promise to be fulfilled after starting the loop
|
|
||||||
app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
|
|
||||||
app().run();
|
|
||||||
});
|
|
||||||
|
|
||||||
// The future is only satisfied after the event loop started
|
|
||||||
f1.get();
|
|
||||||
int status = test::run(argc, argv);
|
|
||||||
|
|
||||||
// Ask the event loop to shutdown and wait
|
|
||||||
app().getLoop()->queueInLoop([]() { app().quit(); });
|
|
||||||
thr.join();
|
|
||||||
return status;
|
|
||||||
}
|
|
@ -36,7 +36,12 @@ class DROGON_EXPORT Cookie
|
|||||||
* @param key key of the cookie
|
* @param key key of the cookie
|
||||||
* @param value value of the cookie
|
* @param value value of the cookie
|
||||||
*/
|
*/
|
||||||
Cookie(std::string key, std::string value)
|
Cookie(const std::string &key, const std::string &value)
|
||||||
|
: key_(key), value_(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Cookie(std::string &&key, std::string &&value)
|
||||||
: key_(std::move(key)), value_(std::move(value))
|
: key_(std::move(key)), value_(std::move(value))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -84,9 +89,6 @@ class DROGON_EXPORT Cookie
|
|||||||
domain_ = domain;
|
domain_ = domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the domain of the cookie.
|
|
||||||
*/
|
|
||||||
void setDomain(std::string &&domain)
|
void setDomain(std::string &&domain)
|
||||||
{
|
{
|
||||||
domain_ = std::move(domain);
|
domain_ = std::move(domain);
|
||||||
@ -100,9 +102,6 @@ class DROGON_EXPORT Cookie
|
|||||||
path_ = path;
|
path_ = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the path of the cookie.
|
|
||||||
*/
|
|
||||||
void setPath(std::string &&path)
|
void setPath(std::string &&path)
|
||||||
{
|
{
|
||||||
path_ = std::move(path);
|
path_ = std::move(path);
|
||||||
@ -116,9 +115,6 @@ class DROGON_EXPORT Cookie
|
|||||||
key_ = key;
|
key_ = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the key of the cookie.
|
|
||||||
*/
|
|
||||||
void setKey(std::string &&key)
|
void setKey(std::string &&key)
|
||||||
{
|
{
|
||||||
key_ = std::move(key);
|
key_ = std::move(key);
|
||||||
@ -132,9 +128,6 @@ class DROGON_EXPORT Cookie
|
|||||||
value_ = value;
|
value_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the value of the cookie.
|
|
||||||
*/
|
|
||||||
void setValue(std::string &&value)
|
void setValue(std::string &&value)
|
||||||
{
|
{
|
||||||
value_ = std::move(value);
|
value_ = std::move(value);
|
||||||
@ -156,18 +149,6 @@ class DROGON_EXPORT Cookie
|
|||||||
sameSite_ = sameSite;
|
sameSite_ = sameSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the partitioned status of the cookie
|
|
||||||
*/
|
|
||||||
void setPartitioned(bool partitioned)
|
|
||||||
{
|
|
||||||
partitioned_ = partitioned;
|
|
||||||
if (partitioned)
|
|
||||||
{
|
|
||||||
setSecure(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the string value of the cookie
|
* @brief Get the string value of the cookie
|
||||||
*/
|
*/
|
||||||
@ -294,17 +275,6 @@ class DROGON_EXPORT Cookie
|
|||||||
return secure_;
|
return secure_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Check if the cookie is partitioned.
|
|
||||||
*
|
|
||||||
* @return true means the cookie is partitioned.
|
|
||||||
* @return false means the cookie is not partitioned.
|
|
||||||
*/
|
|
||||||
bool isPartitioned() const
|
|
||||||
{
|
|
||||||
return partitioned_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the max-age of the cookie
|
* @brief Get the max-age of the cookie
|
||||||
*/
|
*/
|
||||||
@ -373,15 +343,21 @@ class DROGON_EXPORT Cookie
|
|||||||
* @brief Converts a string value to its associated enum class SameSite
|
* @brief Converts a string value to its associated enum class SameSite
|
||||||
* value
|
* value
|
||||||
*/
|
*/
|
||||||
static SameSite convertString2SameSite(std::string_view sameSite)
|
static SameSite convertString2SameSite(const std::string_view &sameSite)
|
||||||
{
|
{
|
||||||
if (stricmp(sameSite, "lax"))
|
if (stricmp(sameSite, "lax"))
|
||||||
|
{
|
||||||
return Cookie::SameSite::kLax;
|
return Cookie::SameSite::kLax;
|
||||||
if (stricmp(sameSite, "strict"))
|
}
|
||||||
|
else if (stricmp(sameSite, "strict"))
|
||||||
|
{
|
||||||
return Cookie::SameSite::kStrict;
|
return Cookie::SameSite::kStrict;
|
||||||
if (stricmp(sameSite, "none"))
|
}
|
||||||
|
else if (stricmp(sameSite, "none"))
|
||||||
|
{
|
||||||
return Cookie::SameSite::kNone;
|
return Cookie::SameSite::kNone;
|
||||||
if (!stricmp(sameSite, "null"))
|
}
|
||||||
|
else if (!stricmp(sameSite, "null"))
|
||||||
{
|
{
|
||||||
LOG_WARN
|
LOG_WARN
|
||||||
<< "'" << sameSite
|
<< "'" << sameSite
|
||||||
@ -396,20 +372,34 @@ class DROGON_EXPORT Cookie
|
|||||||
* @brief Converts an enum class SameSite value to its associated string
|
* @brief Converts an enum class SameSite value to its associated string
|
||||||
* value
|
* value
|
||||||
*/
|
*/
|
||||||
static std::string_view convertSameSite2String(SameSite sameSite)
|
static const std::string_view &convertSameSite2String(SameSite sameSite)
|
||||||
{
|
{
|
||||||
switch (sameSite)
|
switch (sameSite)
|
||||||
{
|
{
|
||||||
case SameSite::kLax:
|
case SameSite::kLax:
|
||||||
return "Lax";
|
{
|
||||||
|
static std::string_view sv{"Lax"};
|
||||||
|
return sv;
|
||||||
|
}
|
||||||
case SameSite::kStrict:
|
case SameSite::kStrict:
|
||||||
return "Strict";
|
{
|
||||||
|
static std::string_view sv{"Strict"};
|
||||||
|
return sv;
|
||||||
|
}
|
||||||
case SameSite::kNone:
|
case SameSite::kNone:
|
||||||
return "None";
|
{
|
||||||
|
static std::string_view sv{"None"};
|
||||||
|
return sv;
|
||||||
|
}
|
||||||
case SameSite::kNull:
|
case SameSite::kNull:
|
||||||
return "Null";
|
{
|
||||||
default:
|
static std::string_view sv{"Null"};
|
||||||
return "UNDEFINED";
|
return sv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
static std::string_view sv{"UNDEFINED"};
|
||||||
|
return sv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +407,6 @@ class DROGON_EXPORT Cookie
|
|||||||
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
|
trantor::Date expiresDate_{(std::numeric_limits<int64_t>::max)()};
|
||||||
bool httpOnly_{true};
|
bool httpOnly_{true};
|
||||||
bool secure_{false};
|
bool secure_{false};
|
||||||
bool partitioned_{false};
|
|
||||||
std::string domain_;
|
std::string domain_;
|
||||||
std::string path_;
|
std::string path_;
|
||||||
std::string key_;
|
std::string key_;
|
||||||
|
@ -61,9 +61,9 @@ template <typename T>
|
|||||||
struct isAutoCreationClass
|
struct isAutoCreationClass
|
||||||
{
|
{
|
||||||
template <class C>
|
template <class C>
|
||||||
static constexpr auto check(C *) -> std::enable_if_t<
|
static constexpr auto check(C *)
|
||||||
std::is_same_v<decltype(C::isAutoCreation), const bool>,
|
-> std::enable_if_t<std::is_same_v<decltype(C::isAutoCreation), bool>,
|
||||||
bool>
|
bool>
|
||||||
{
|
{
|
||||||
return C::isAutoCreation;
|
return C::isAutoCreation;
|
||||||
}
|
}
|
||||||
|
@ -43,16 +43,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#if defined(__APPLE__) && defined(__MACH__) && \
|
|
||||||
(defined(__ENVIRONMENT_IPHONE_OS__) || \
|
|
||||||
defined(__IPHONE_OS_VERSION_MIN_REQUIRED))
|
|
||||||
// iOS
|
|
||||||
#define TARGET_OS_IOS 1
|
|
||||||
#else
|
|
||||||
// not iOS
|
|
||||||
#define TARGET_OS_IOS 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace drogon
|
namespace drogon
|
||||||
{
|
{
|
||||||
// the drogon banner
|
// the drogon banner
|
||||||
@ -359,7 +349,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
|||||||
|
|
||||||
/// Register an advice called before routing
|
/// Register an advice called before routing
|
||||||
/**
|
/**
|
||||||
* @param advice is called after all the synchronous advice return
|
* @param advice is called after all the synchronous advices return
|
||||||
* nullptr and before the request is routed to any handler. The parameters
|
* nullptr and before the request is routed to any handler. The parameters
|
||||||
* of the advice are same as those of the doFilter method of the Filter
|
* of the advice are same as those of the doFilter method of the Filter
|
||||||
* class.
|
* class.
|
||||||
@ -816,15 +806,6 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
|||||||
const std::vector<std::pair<std::string, std::string>>
|
const std::vector<std::pair<std::string, std::string>>
|
||||||
&sslConfCmds) = 0;
|
&sslConfCmds) = 0;
|
||||||
|
|
||||||
/// Reload the global cert file and private key file for https server
|
|
||||||
/// Note: The goal of this method is not to make the framework
|
|
||||||
/// use the new SSL path, but rather to reload the new content
|
|
||||||
/// from the old path while the framework is still running.
|
|
||||||
/// Typically, when our SSL is about to expire,
|
|
||||||
/// we need to reload the SSL. The purpose of this function
|
|
||||||
/// is to use the new SSL certificate without stopping the framework.
|
|
||||||
virtual HttpAppFramework &reloadSSLFiles() = 0;
|
|
||||||
|
|
||||||
/// Add plugins
|
/// Add plugins
|
||||||
/**
|
/**
|
||||||
* @param configs The plugins array
|
* @param configs The plugins array
|
||||||
@ -1007,7 +988,7 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
|||||||
virtual HttpAppFramework &setFileTypes(
|
virtual HttpAppFramework &setFileTypes(
|
||||||
const std::vector<std::string> &types) = 0;
|
const std::vector<std::string> &types) = 0;
|
||||||
|
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
/// Enable supporting for dynamic views loading.
|
/// Enable supporting for dynamic views loading.
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -1609,34 +1590,6 @@ class DROGON_EXPORT HttpAppFramework : public trantor::NonCopyable
|
|||||||
*/
|
*/
|
||||||
virtual int64_t getConnectionCount() const = 0;
|
virtual int64_t getConnectionCount() const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the before listen setsockopt callback.
|
|
||||||
*
|
|
||||||
* @param cb This callback will be called before the listen
|
|
||||||
*/
|
|
||||||
virtual HttpAppFramework &setBeforeListenSockOptCallback(
|
|
||||||
std::function<void(int)> cb) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the after accept setsockopt callback.
|
|
||||||
*
|
|
||||||
* @param cb This callback will be called after accept
|
|
||||||
*/
|
|
||||||
virtual HttpAppFramework &setAfterAcceptSockOptCallback(
|
|
||||||
std::function<void(int)> cb) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the client disconnect or connect callback.
|
|
||||||
*
|
|
||||||
* @param cb This callback will be called, when the client disconnect or
|
|
||||||
* connect
|
|
||||||
*/
|
|
||||||
virtual HttpAppFramework &setConnectionCallback(
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> cb) = 0;
|
|
||||||
|
|
||||||
virtual HttpAppFramework &enableRequestStream(bool enable = true) = 0;
|
|
||||||
virtual bool isRequestStreamEnabled() const = 0;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void registerHttpController(
|
virtual void registerHttpController(
|
||||||
const std::string &pathPattern,
|
const std::string &pathPattern,
|
||||||
|
@ -164,7 +164,6 @@ class HttpBinderBase
|
|||||||
std::function<void(const HttpResponsePtr &)> &&callback) = 0;
|
std::function<void(const HttpResponsePtr &)> &&callback) = 0;
|
||||||
virtual size_t paramCount() = 0;
|
virtual size_t paramCount() = 0;
|
||||||
virtual const std::string &handlerName() const = 0;
|
virtual const std::string &handlerName() const = 0;
|
||||||
virtual bool isStreamHandler() = 0;
|
|
||||||
|
|
||||||
virtual ~HttpBinderBase()
|
virtual ~HttpBinderBase()
|
||||||
{
|
{
|
||||||
@ -219,11 +218,6 @@ class HttpBinder : public HttpBinderBase
|
|||||||
return traits::arity;
|
return traits::arity;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStreamHandler() override
|
|
||||||
{
|
|
||||||
return traits::isStreamHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
|
HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
|
||||||
{
|
{
|
||||||
static_assert(traits::isHTTPFunction,
|
static_assert(traits::isHTTPFunction,
|
||||||
@ -272,7 +266,6 @@ class HttpBinder : public HttpBinderBase
|
|||||||
|
|
||||||
template <typename... Values,
|
template <typename... Values,
|
||||||
std::size_t Boundary = argument_count,
|
std::size_t Boundary = argument_count,
|
||||||
bool isStreamHandler = traits::isStreamHandler,
|
|
||||||
bool isCoroutine = traits::isCoroutine>
|
bool isCoroutine = traits::isCoroutine>
|
||||||
void run(std::deque<std::string> &pathArguments,
|
void run(std::deque<std::string> &pathArguments,
|
||||||
const HttpRequestPtr &req,
|
const HttpRequestPtr &req,
|
||||||
@ -351,17 +344,7 @@ class HttpBinder : public HttpBinderBase
|
|||||||
{
|
{
|
||||||
// Explicit copy because `callFunction` moves it
|
// Explicit copy because `callFunction` moves it
|
||||||
auto cb = callback;
|
auto cb = callback;
|
||||||
if constexpr (isStreamHandler)
|
callFunction(req, cb, std::move(values)...);
|
||||||
{
|
|
||||||
callFunction(req,
|
|
||||||
createRequestStream(req),
|
|
||||||
cb,
|
|
||||||
std::move(values)...);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
callFunction(req, cb, std::move(values)...);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (const std::exception &except)
|
catch (const std::exception &except)
|
||||||
{
|
{
|
||||||
@ -376,7 +359,6 @@ class HttpBinder : public HttpBinderBase
|
|||||||
#ifdef __cpp_impl_coroutine
|
#ifdef __cpp_impl_coroutine
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static_assert(!isStreamHandler);
|
|
||||||
[this](HttpRequestPtr req,
|
[this](HttpRequestPtr req,
|
||||||
std::function<void(const HttpResponsePtr &)> callback,
|
std::function<void(const HttpResponsePtr &)> callback,
|
||||||
Values &&...values) -> AsyncTask {
|
Values &&...values) -> AsyncTask {
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include <drogon/HttpRequest.h>
|
#include <drogon/HttpRequest.h>
|
||||||
#include <trantor/utils/NonCopyable.h>
|
#include <trantor/utils/NonCopyable.h>
|
||||||
#include <trantor/net/EventLoop.h>
|
#include <trantor/net/EventLoop.h>
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <future>
|
#include <future>
|
||||||
@ -179,18 +178,17 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
|
|||||||
*/
|
*/
|
||||||
virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
|
virtual void setSockOptCallback(std::function<void(int)> cb) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return the number of unsent http requests in the current http
|
|
||||||
* client cache buffer
|
|
||||||
*/
|
|
||||||
virtual std::size_t requestsBufferSize() = 0;
|
|
||||||
|
|
||||||
/// Set the pipelining depth, which is the number of requests that are not
|
/// Set the pipelining depth, which is the number of requests that are not
|
||||||
/// responding.
|
/// responding.
|
||||||
/**
|
/**
|
||||||
* If this method is not called, the default depth value is 0 which means
|
* If this method is not called, the default depth value is 0 which means
|
||||||
* the pipelining is disabled. For details about pipelining, see
|
* the pipelining is disabled. For details about pipelining, see
|
||||||
* rfc2616-8.1.2.2
|
* rfc2616-8.1.2.2
|
||||||
|
*
|
||||||
|
* @param depth The depth value.
|
||||||
|
* @note This option is only valid for HTTP/1.x. If the client running in
|
||||||
|
* HTTP/2 mode, this settings have no effect. The maximum concurrent
|
||||||
|
* requests for HTTP/2 is 100 (unless the server has a different setting).
|
||||||
*/
|
*/
|
||||||
virtual void setPipeliningDepth(size_t depth) = 0;
|
virtual void setPipeliningDepth(size_t depth) = 0;
|
||||||
|
|
||||||
@ -245,15 +243,20 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
|
|||||||
* enabled for HTTPS.
|
* enabled for HTTPS.
|
||||||
* @param validateCert If the parameter is set to true, the client validates
|
* @param validateCert If the parameter is set to true, the client validates
|
||||||
* the server certificate when SSL handshaking.
|
* the server certificate when SSL handshaking.
|
||||||
|
* @param targetVersion The target TLS version to use for HTTPS connections.
|
||||||
|
* This is a mere hint, and the actual version used will depend on the
|
||||||
|
* server and the client's capabilities.
|
||||||
* @return HttpClientPtr The smart pointer to the new client object.
|
* @return HttpClientPtr The smart pointer to the new client object.
|
||||||
* @note: The ip parameter support for both ipv4 and ipv6 address
|
* @note: The ip parameter support for both ipv4 and ipv6 address
|
||||||
*/
|
*/
|
||||||
static HttpClientPtr newHttpClient(const std::string &ip,
|
static HttpClientPtr newHttpClient(
|
||||||
uint16_t port,
|
const std::string &ip,
|
||||||
bool useSSL = false,
|
uint16_t port,
|
||||||
trantor::EventLoop *loop = nullptr,
|
bool useSSL = false,
|
||||||
bool useOldTLS = false,
|
trantor::EventLoop *loop = nullptr,
|
||||||
bool validateCert = true);
|
bool useOldTLS = false,
|
||||||
|
bool validateCert = true,
|
||||||
|
std::optional<Version> targetVersion = std::nullopt);
|
||||||
|
|
||||||
/// Get the event loop of the client;
|
/// Get the event loop of the client;
|
||||||
virtual trantor::EventLoop *getLoop() = 0;
|
virtual trantor::EventLoop *getLoop() = 0;
|
||||||
@ -310,6 +313,16 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
|
|||||||
const std::vector<std::pair<std::string, std::string>>
|
const std::vector<std::pair<std::string, std::string>>
|
||||||
&sslConfCmds) = 0;
|
&sslConfCmds) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief get the protocol version used by the HTTP connection
|
||||||
|
* @return std::optional<Version> the protocol version used by the HTTP
|
||||||
|
*
|
||||||
|
* NOTE: It could return std::nullopt if the connection is not established
|
||||||
|
* or is still negotiating the protocol. This is IMPORTANT as the client
|
||||||
|
* COULD be lazy and not connecting until the first request arrives.
|
||||||
|
*/
|
||||||
|
virtual std::optional<Version> protocolVersion() const = 0;
|
||||||
|
|
||||||
/// Create a Http client using the hostString to connect to server
|
/// Create a Http client using the hostString to connect to server
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -336,15 +349,25 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
|
|||||||
* @param validateCert If the parameter is set to true, the client validates
|
* @param validateCert If the parameter is set to true, the client validates
|
||||||
* the server certificate when SSL handshaking.
|
* the server certificate when SSL handshaking.
|
||||||
*
|
*
|
||||||
|
* @param targetVersion The target HTTP version that the client will attempt
|
||||||
|
* to use. **THIS IS ONLY A HINT**. The server may choose to ignore this as
|
||||||
|
* the server may not support the requested version. The preference is as
|
||||||
|
* follows:
|
||||||
|
* HTTP/2 > HTTP/1.1 > HTTP/1.0
|
||||||
|
* Note that there is no way to auto-detect HTTP/1.0 servers, so it is only
|
||||||
|
* used if explicitly requested.
|
||||||
|
*
|
||||||
* @note Don't add path and parameters in hostString, the request path and
|
* @note Don't add path and parameters in hostString, the request path and
|
||||||
* parameters should be set in HttpRequestPtr when calling the sendRequest()
|
* parameters should be set in HttpRequestPtr when calling the sendRequest()
|
||||||
* method.
|
* method.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static HttpClientPtr newHttpClient(const std::string &hostString,
|
static HttpClientPtr newHttpClient(
|
||||||
trantor::EventLoop *loop = nullptr,
|
const std::string &hostString,
|
||||||
bool useOldTLS = false,
|
trantor::EventLoop *loop = nullptr,
|
||||||
bool validateCert = true);
|
bool useOldTLS = false,
|
||||||
|
bool validateCert = true,
|
||||||
|
std::optional<Version> targetVersion = std::nullopt);
|
||||||
|
|
||||||
virtual ~HttpClient()
|
virtual ~HttpClient()
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <trantor/net/TcpConnection.h>
|
|
||||||
|
|
||||||
namespace drogon
|
namespace drogon
|
||||||
{
|
{
|
||||||
@ -180,17 +179,6 @@ class DROGON_EXPORT HttpRequest
|
|||||||
return cookies();
|
return cookies();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return content length parsed from the Content-Length header
|
|
||||||
* If no Content-Length header, return null.
|
|
||||||
*/
|
|
||||||
virtual size_t realContentLength() const = 0;
|
|
||||||
|
|
||||||
size_t getRealContentLength() const
|
|
||||||
{
|
|
||||||
return realContentLength();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the query string of the request.
|
/// Get the query string of the request.
|
||||||
/**
|
/**
|
||||||
* The query string is the substring after the '?' in the URL string.
|
* The query string is the substring after the '?' in the URL string.
|
||||||
@ -274,6 +262,7 @@ class DROGON_EXPORT HttpRequest
|
|||||||
/**
|
/**
|
||||||
* kHttp10 means Http version is 1.0
|
* kHttp10 means Http version is 1.0
|
||||||
* kHttp11 means Http version is 1.1
|
* kHttp11 means Http version is 1.1
|
||||||
|
* kHttp20 means Http version is 2.0
|
||||||
*/
|
*/
|
||||||
virtual Version version() const = 0;
|
virtual Version version() const = 0;
|
||||||
|
|
||||||
@ -450,7 +439,8 @@ class DROGON_EXPORT HttpRequest
|
|||||||
virtual void setCustomContentTypeString(const std::string &type) = 0;
|
virtual void setCustomContentTypeString(const std::string &type) = 0;
|
||||||
|
|
||||||
/// Add a cookie
|
/// Add a cookie
|
||||||
virtual void addCookie(std::string key, std::string value) = 0;
|
virtual void addCookie(const std::string &key,
|
||||||
|
const std::string &value) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the request object to the pass-through mode or not. It's not
|
* @brief Set the request object to the pass-through mode or not. It's not
|
||||||
@ -505,11 +495,6 @@ class DROGON_EXPORT HttpRequest
|
|||||||
virtual void setContentTypeString(const char *typeString,
|
virtual void setContentTypeString(const char *typeString,
|
||||||
size_t typeStringLength) = 0;
|
size_t typeStringLength) = 0;
|
||||||
|
|
||||||
virtual bool connected() const noexcept = 0;
|
|
||||||
|
|
||||||
virtual const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
|
|
||||||
const noexcept = 0;
|
|
||||||
|
|
||||||
virtual ~HttpRequest()
|
virtual ~HttpRequest()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -328,6 +328,7 @@ class DROGON_EXPORT HttpResponse
|
|||||||
/**
|
/**
|
||||||
* kHttp10 means Http version is 1.0
|
* kHttp10 means Http version is 1.0
|
||||||
* kHttp11 means Http version is 1.1
|
* kHttp11 means Http version is 1.1
|
||||||
|
* kHttp2 means Http version is 2.0
|
||||||
*/
|
*/
|
||||||
virtual Version version() const = 0;
|
virtual Version version() const = 0;
|
||||||
|
|
||||||
|
@ -89,11 +89,14 @@ enum HttpStatusCode
|
|||||||
k511NetworkAuthenticationRequired = 511
|
k511NetworkAuthenticationRequired = 511
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Add an option to use default HTTP version
|
||||||
|
// as the server may not support HTTP/2.
|
||||||
enum class Version
|
enum class Version
|
||||||
{
|
{
|
||||||
kUnknown = 0,
|
kUnknown = 0,
|
||||||
kHttp10,
|
kHttp10,
|
||||||
kHttp11
|
kHttp11,
|
||||||
|
kHttp2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ContentType
|
enum ContentType
|
||||||
|
@ -123,8 +123,6 @@ class DROGON_EXPORT MultiPartParser
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MultiPartParser(){};
|
MultiPartParser(){};
|
||||||
MultiPartParser(const MultiPartParser &other) = default; // Copyable
|
|
||||||
MultiPartParser(MultiPartParser &&other) = default; // Movable
|
|
||||||
~MultiPartParser(){};
|
~MultiPartParser(){};
|
||||||
/// Get files, This method should be called after calling the parse()
|
/// Get files, This method should be called after calling the parse()
|
||||||
/// method.
|
/// method.
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @file RequestStream.h
|
|
||||||
* @author Nitromelon
|
|
||||||
*
|
|
||||||
* Copyright 2024, Nitromelon. All rights reserved.
|
|
||||||
* https://github.com/drogonframework/drogon
|
|
||||||
* Use of this source code is governed by a MIT license
|
|
||||||
* that can be found in the License file.
|
|
||||||
*
|
|
||||||
* Drogon
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <drogon/exports.h>
|
|
||||||
#include <string>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
namespace drogon
|
|
||||||
{
|
|
||||||
class HttpRequest;
|
|
||||||
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
|
||||||
|
|
||||||
class RequestStreamReader;
|
|
||||||
using RequestStreamReaderPtr = std::shared_ptr<RequestStreamReader>;
|
|
||||||
|
|
||||||
struct MultipartHeader
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string filename;
|
|
||||||
std::string contentType;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DROGON_EXPORT RequestStream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~RequestStream() = default;
|
|
||||||
virtual void setStreamReader(RequestStreamReaderPtr reader) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
using RequestStreamPtr = std::shared_ptr<RequestStream>;
|
|
||||||
|
|
||||||
namespace internal
|
|
||||||
{
|
|
||||||
DROGON_EXPORT RequestStreamPtr createRequestStream(const HttpRequestPtr &req);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class StreamErrorCode
|
|
||||||
{
|
|
||||||
kNone = 0,
|
|
||||||
kBadRequest,
|
|
||||||
kConnectionBroken
|
|
||||||
};
|
|
||||||
|
|
||||||
class StreamError final : public std::exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
const char *what() const noexcept override
|
|
||||||
{
|
|
||||||
return message_.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamErrorCode code() const
|
|
||||||
{
|
|
||||||
return code_;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamError(StreamErrorCode code, const std::string &message)
|
|
||||||
: message_(message), code_(code)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamError(StreamErrorCode code, std::string &&message)
|
|
||||||
: message_(std::move(message)), code_(code)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamError() = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string message_;
|
|
||||||
StreamErrorCode code_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interface for stream request reading.
|
|
||||||
* User should create an implementation class, or use built-in handlers
|
|
||||||
*/
|
|
||||||
class DROGON_EXPORT RequestStreamReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~RequestStreamReader() = default;
|
|
||||||
virtual void onStreamData(const char *, size_t) = 0;
|
|
||||||
virtual void onStreamFinish(std::exception_ptr) = 0;
|
|
||||||
|
|
||||||
using StreamDataCallback = std::function<void(const char *, size_t)>;
|
|
||||||
using StreamFinishCallback = std::function<void(std::exception_ptr)>;
|
|
||||||
|
|
||||||
// Create a handler with default implementation
|
|
||||||
static RequestStreamReaderPtr newReader(StreamDataCallback dataCb,
|
|
||||||
StreamFinishCallback finishCb);
|
|
||||||
|
|
||||||
// A handler that drops all data
|
|
||||||
static RequestStreamReaderPtr newNullReader();
|
|
||||||
|
|
||||||
using MultipartHeaderCallback = std::function<void(MultipartHeader header)>;
|
|
||||||
|
|
||||||
static RequestStreamReaderPtr newMultipartReader(
|
|
||||||
const HttpRequestPtr &req,
|
|
||||||
MultipartHeaderCallback headerCb,
|
|
||||||
StreamDataCallback dataCb,
|
|
||||||
StreamFinishCallback finishCb);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace drogon
|
|
@ -36,7 +36,6 @@ namespace plugin
|
|||||||
// "show_microseconds": true,
|
// "show_microseconds": true,
|
||||||
// "custom_time_format": "",
|
// "custom_time_format": "",
|
||||||
// "use_real_ip": false
|
// "use_real_ip": false
|
||||||
// "path_exempt": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@endcode
|
@endcode
|
||||||
@ -99,10 +98,6 @@ namespace plugin
|
|||||||
* Enable the plugin by adding the configuration to the list of plugins in the
|
* Enable the plugin by adding the configuration to the list of plugins in the
|
||||||
* configuration file.
|
* configuration file.
|
||||||
*
|
*
|
||||||
* path_exempt: must be a string or a string array, present a regular expression
|
|
||||||
* (for matching the path of a request) or a regular expression list for URLs
|
|
||||||
* that don't have to be logged.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
|
class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
|
||||||
{
|
{
|
||||||
@ -122,8 +117,6 @@ class DROGON_EXPORT AccessLogger : public drogon::Plugin<AccessLogger>
|
|||||||
bool useCustomTimeFormat_{false};
|
bool useCustomTimeFormat_{false};
|
||||||
std::string timeFormat_;
|
std::string timeFormat_;
|
||||||
static bool useRealIp_;
|
static bool useRealIp_;
|
||||||
std::regex exemptRegex_;
|
|
||||||
bool regexFlag_{false};
|
|
||||||
|
|
||||||
using LogFunction = std::function<void(trantor::LogStream &,
|
using LogFunction = std::function<void(trantor::LogStream &,
|
||||||
const drogon::HttpRequestPtr &,
|
const drogon::HttpRequestPtr &,
|
||||||
|
@ -71,9 +71,7 @@ IPs or users. the default value is 600.
|
|||||||
"ip_capacity": 0,
|
"ip_capacity": 0,
|
||||||
"user_capacity": 0
|
"user_capacity": 0
|
||||||
},...
|
},...
|
||||||
],
|
]
|
||||||
// Trusted proxy ip or cidr
|
|
||||||
"trust_ips": ["127.0.0.1", "172.16.0.0/12"],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@endcode
|
@endcode
|
||||||
@ -139,14 +137,12 @@ class DROGON_EXPORT Hodor : public drogon::Plugin<Hodor>
|
|||||||
std::function<HttpResponsePtr(const drogon::HttpRequestPtr &)>
|
std::function<HttpResponsePtr(const drogon::HttpRequestPtr &)>
|
||||||
rejectResponseFactory_;
|
rejectResponseFactory_;
|
||||||
|
|
||||||
RealIpResolver::CIDRs trustCIDRs_;
|
|
||||||
|
|
||||||
void onHttpRequest(const drogon::HttpRequestPtr &,
|
void onHttpRequest(const drogon::HttpRequestPtr &,
|
||||||
AdviceCallback &&,
|
AdviceCallback &&,
|
||||||
AdviceChainCallback &&);
|
AdviceChainCallback &&);
|
||||||
bool checkLimit(const drogon::HttpRequestPtr &req,
|
bool checkLimit(const drogon::HttpRequestPtr &req,
|
||||||
const LimitStrategy &strategy,
|
const LimitStrategy &strategy,
|
||||||
const trantor::InetAddress &ip,
|
const std::string &ip,
|
||||||
const std::optional<std::string> &userId);
|
const std::optional<std::string> &userId);
|
||||||
HttpResponsePtr rejectResponse_;
|
HttpResponsePtr rejectResponse_;
|
||||||
};
|
};
|
||||||
|
@ -57,6 +57,7 @@ class DROGON_EXPORT RealIpResolver : public drogon::Plugin<RealIpResolver>
|
|||||||
private:
|
private:
|
||||||
const trantor::InetAddress &getRealAddr(
|
const trantor::InetAddress &getRealAddr(
|
||||||
const drogon::HttpRequestPtr &req) const;
|
const drogon::HttpRequestPtr &req) const;
|
||||||
|
bool matchCidr(const trantor::InetAddress &addr) const;
|
||||||
|
|
||||||
struct CIDR
|
struct CIDR
|
||||||
{
|
{
|
||||||
@ -65,12 +66,7 @@ class DROGON_EXPORT RealIpResolver : public drogon::Plugin<RealIpResolver>
|
|||||||
in_addr_t mask_{32};
|
in_addr_t mask_{32};
|
||||||
};
|
};
|
||||||
|
|
||||||
using CIDRs = std::vector<CIDR>;
|
std::vector<CIDR> trustCIDRs_;
|
||||||
static bool matchCidr(const trantor::InetAddress &addr,
|
|
||||||
const CIDRs &trustCIDRs);
|
|
||||||
|
|
||||||
friend class Hodor;
|
|
||||||
CIDRs trustCIDRs_;
|
|
||||||
std::string fromHeader_;
|
std::string fromHeader_;
|
||||||
std::string attributeKey_;
|
std::string attributeKey_;
|
||||||
bool useXForwardedFor_{false};
|
bool useXForwardedFor_{false};
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <drogon/DrObject.h>
|
#include <drogon/DrObject.h>
|
||||||
#include <drogon/RequestStream.h>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@ -47,84 +46,7 @@ struct resumable_type : std::false_type
|
|||||||
template <typename>
|
template <typename>
|
||||||
struct FunctionTraits;
|
struct FunctionTraits;
|
||||||
|
|
||||||
//
|
// functor,lambda,std::function...
|
||||||
// Basic match, inherited by all other matches
|
|
||||||
//
|
|
||||||
template <typename ReturnType, typename... Arguments>
|
|
||||||
struct FunctionTraits<ReturnType (*)(Arguments...)>
|
|
||||||
{
|
|
||||||
using result_type = ReturnType;
|
|
||||||
|
|
||||||
template <std::size_t Index>
|
|
||||||
using argument =
|
|
||||||
typename std::tuple_element_t<Index, std::tuple<Arguments...>>;
|
|
||||||
|
|
||||||
static const std::size_t arity = sizeof...(Arguments);
|
|
||||||
using class_type = void;
|
|
||||||
using return_type = ReturnType;
|
|
||||||
static const bool isHTTPFunction = false;
|
|
||||||
static const bool isClassFunction = false;
|
|
||||||
static const bool isStreamHandler = false;
|
|
||||||
static const bool isDrObjectClass = false;
|
|
||||||
static const bool isCoroutine = false;
|
|
||||||
|
|
||||||
static const std::string name()
|
|
||||||
{
|
|
||||||
return std::string("Normal or Static Function");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Match normal functions
|
|
||||||
//
|
|
||||||
|
|
||||||
// normal function for HTTP handling
|
|
||||||
template <typename ReturnType, typename... Arguments>
|
|
||||||
struct FunctionTraits<
|
|
||||||
ReturnType (*)(const HttpRequestPtr &req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
|
||||||
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
|
||||||
{
|
|
||||||
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
|
|
||||||
static const bool isCoroutine = false;
|
|
||||||
using class_type = void;
|
|
||||||
using first_param_type = HttpRequestPtr;
|
|
||||||
using return_type = ReturnType;
|
|
||||||
};
|
|
||||||
|
|
||||||
// normal function with custom request object
|
|
||||||
template <typename T, typename ReturnType, typename... Arguments>
|
|
||||||
struct FunctionTraits<
|
|
||||||
ReturnType (*)(T &&customReq,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
|
||||||
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
|
||||||
{
|
|
||||||
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
|
|
||||||
static const bool isCoroutine = false;
|
|
||||||
using class_type = void;
|
|
||||||
using first_param_type = T;
|
|
||||||
using return_type = ReturnType;
|
|
||||||
};
|
|
||||||
|
|
||||||
// normal function with stream handler
|
|
||||||
template <typename ReturnType, typename... Arguments>
|
|
||||||
struct FunctionTraits<
|
|
||||||
ReturnType (*)(const HttpRequestPtr &req,
|
|
||||||
RequestStreamPtr &&streamCtx,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
|
||||||
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
|
||||||
{
|
|
||||||
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
|
|
||||||
static const bool isCoroutine = false;
|
|
||||||
static const bool isStreamHandler = true;
|
|
||||||
using class_type = void;
|
|
||||||
using first_param_type = HttpRequestPtr;
|
|
||||||
using return_type = ReturnType;
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Match functor,lambda,std::function... inherits normal function matches
|
|
||||||
//
|
|
||||||
template <typename Function>
|
template <typename Function>
|
||||||
struct FunctionTraits
|
struct FunctionTraits
|
||||||
: public FunctionTraits<
|
: public FunctionTraits<
|
||||||
@ -140,11 +62,7 @@ struct FunctionTraits
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
// class instance method of const object
|
||||||
// Match class functions, inherits normal function matches
|
|
||||||
//
|
|
||||||
|
|
||||||
// class const method
|
|
||||||
template <typename ClassType, typename ReturnType, typename... Arguments>
|
template <typename ClassType, typename ReturnType, typename... Arguments>
|
||||||
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
|
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
|
||||||
: FunctionTraits<ReturnType (*)(Arguments...)>
|
: FunctionTraits<ReturnType (*)(Arguments...)>
|
||||||
@ -160,7 +78,7 @@ struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// class non-const method
|
// class instance method of non-const object
|
||||||
template <typename ClassType, typename ReturnType, typename... Arguments>
|
template <typename ClassType, typename ReturnType, typename... Arguments>
|
||||||
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...)>
|
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...)>
|
||||||
: FunctionTraits<ReturnType (*)(Arguments...)>
|
: FunctionTraits<ReturnType (*)(Arguments...)>
|
||||||
@ -176,9 +94,30 @@ struct FunctionTraits<ReturnType (ClassType::*)(Arguments...)>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
// normal function for HTTP handling
|
||||||
// Match coroutine functions
|
template <typename ReturnType, typename... Arguments>
|
||||||
//
|
struct FunctionTraits<
|
||||||
|
ReturnType (*)(const HttpRequestPtr &req,
|
||||||
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
|
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
||||||
|
{
|
||||||
|
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
|
||||||
|
static const bool isCoroutine = false;
|
||||||
|
using class_type = void;
|
||||||
|
using first_param_type = HttpRequestPtr;
|
||||||
|
using return_type = ReturnType;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ReturnType, typename... Arguments>
|
||||||
|
struct FunctionTraits<
|
||||||
|
ReturnType (*)(HttpRequestPtr &req,
|
||||||
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
|
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
||||||
|
{
|
||||||
|
static const bool isHTTPFunction = false;
|
||||||
|
using class_type = void;
|
||||||
|
};
|
||||||
|
|
||||||
#ifdef __cpp_impl_coroutine
|
#ifdef __cpp_impl_coroutine
|
||||||
template <typename... Arguments>
|
template <typename... Arguments>
|
||||||
struct FunctionTraits<
|
struct FunctionTraits<
|
||||||
@ -219,20 +158,6 @@ struct FunctionTraits<Task<HttpResponsePtr> (*)(HttpRequestPtr req,
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//
|
|
||||||
// Bad matches
|
|
||||||
//
|
|
||||||
|
|
||||||
template <typename ReturnType, typename... Arguments>
|
|
||||||
struct FunctionTraits<
|
|
||||||
ReturnType (*)(HttpRequestPtr &req,
|
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback,
|
|
||||||
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
|
||||||
{
|
|
||||||
static const bool isHTTPFunction = false;
|
|
||||||
using class_type = void;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename ReturnType, typename... Arguments>
|
template <typename ReturnType, typename... Arguments>
|
||||||
struct FunctionTraits<
|
struct FunctionTraits<
|
||||||
ReturnType (*)(HttpRequestPtr &&req,
|
ReturnType (*)(HttpRequestPtr &&req,
|
||||||
@ -243,5 +168,43 @@ struct FunctionTraits<
|
|||||||
using class_type = void;
|
using class_type = void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// normal function for HTTP handling
|
||||||
|
template <typename T, typename ReturnType, typename... Arguments>
|
||||||
|
struct FunctionTraits<
|
||||||
|
ReturnType (*)(T &&customReq,
|
||||||
|
std::function<void(const HttpResponsePtr &)> &&callback,
|
||||||
|
Arguments...)> : FunctionTraits<ReturnType (*)(Arguments...)>
|
||||||
|
{
|
||||||
|
static const bool isHTTPFunction = !resumable_type<ReturnType>::value;
|
||||||
|
static const bool isCoroutine = false;
|
||||||
|
using class_type = void;
|
||||||
|
using first_param_type = T;
|
||||||
|
using return_type = ReturnType;
|
||||||
|
};
|
||||||
|
|
||||||
|
// normal function
|
||||||
|
template <typename ReturnType, typename... Arguments>
|
||||||
|
struct FunctionTraits<ReturnType (*)(Arguments...)>
|
||||||
|
{
|
||||||
|
using result_type = ReturnType;
|
||||||
|
|
||||||
|
template <std::size_t Index>
|
||||||
|
using argument =
|
||||||
|
typename std::tuple_element_t<Index, std::tuple<Arguments...>>;
|
||||||
|
|
||||||
|
static const std::size_t arity = sizeof...(Arguments);
|
||||||
|
using class_type = void;
|
||||||
|
using return_type = ReturnType;
|
||||||
|
static const bool isHTTPFunction = false;
|
||||||
|
static const bool isClassFunction = false;
|
||||||
|
static const bool isDrObjectClass = false;
|
||||||
|
static const bool isCoroutine = false;
|
||||||
|
|
||||||
|
static const std::string name()
|
||||||
|
{
|
||||||
|
return std::string("Normal or Static Function");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace internal
|
||||||
} // namespace drogon
|
} // namespace drogon
|
||||||
|
@ -134,85 +134,47 @@ constexpr size_t base64EncodedLength(size_t in_len, bool padded = true)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the string to base64 format.
|
/// Encode the string to base64 format.
|
||||||
DROGON_EXPORT void base64Encode(const unsigned char *bytesToEncode,
|
DROGON_EXPORT std::string base64Encode(const unsigned char *bytes_to_encode,
|
||||||
size_t inLen,
|
size_t in_len,
|
||||||
unsigned char *outputBuffer,
|
bool url_safe = false,
|
||||||
bool urlSafe = false,
|
bool padded = true);
|
||||||
bool padded = true);
|
|
||||||
|
|
||||||
/// Encode the string to base64 format.
|
|
||||||
inline std::string base64Encode(const unsigned char *bytesToEncode,
|
|
||||||
size_t inLen,
|
|
||||||
bool urlSafe = false,
|
|
||||||
bool padded = true)
|
|
||||||
{
|
|
||||||
std::string ret;
|
|
||||||
ret.resize(base64EncodedLength(inLen, padded));
|
|
||||||
base64Encode(
|
|
||||||
bytesToEncode, inLen, (unsigned char *)ret.data(), urlSafe, padded);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode the string to base64 format.
|
/// Encode the string to base64 format.
|
||||||
inline std::string base64Encode(std::string_view data,
|
inline std::string base64Encode(std::string_view data,
|
||||||
bool urlSafe = false,
|
bool url_safe = false,
|
||||||
bool padded = true)
|
bool padded = true)
|
||||||
{
|
{
|
||||||
return base64Encode((unsigned char *)data.data(),
|
return base64Encode((unsigned char *)data.data(),
|
||||||
data.size(),
|
data.size(),
|
||||||
urlSafe,
|
url_safe,
|
||||||
padded);
|
padded);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the string to base64 format with no padding.
|
/// Encode the string to base64 format with no padding.
|
||||||
inline void base64EncodeUnpadded(const unsigned char *bytesToEncode,
|
inline std::string base64EncodeUnpadded(const unsigned char *bytes_to_encode,
|
||||||
size_t inLen,
|
size_t in_len,
|
||||||
unsigned char *outputBuffer,
|
bool url_safe = false)
|
||||||
bool urlSafe = false)
|
|
||||||
{
|
{
|
||||||
base64Encode(bytesToEncode, inLen, outputBuffer, urlSafe, false);
|
return base64Encode(bytes_to_encode, in_len, url_safe, false);
|
||||||
}
|
|
||||||
|
|
||||||
/// Encode the string to base64 format with no padding.
|
|
||||||
inline std::string base64EncodeUnpadded(const unsigned char *bytesToEncode,
|
|
||||||
size_t inLen,
|
|
||||||
bool urlSafe = false)
|
|
||||||
{
|
|
||||||
return base64Encode(bytesToEncode, inLen, urlSafe, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode the string to base64 format with no padding.
|
/// Encode the string to base64 format with no padding.
|
||||||
inline std::string base64EncodeUnpadded(std::string_view data,
|
inline std::string base64EncodeUnpadded(std::string_view data,
|
||||||
bool urlSafe = false)
|
bool url_safe = false)
|
||||||
{
|
{
|
||||||
return base64Encode(data, urlSafe, false);
|
return base64Encode(data, url_safe, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the decoded length of base64.
|
/// Get the decoded length of base64.
|
||||||
constexpr size_t base64DecodedLength(size_t inLen)
|
constexpr size_t base64DecodedLength(size_t in_len)
|
||||||
{
|
{
|
||||||
return (inLen * 3) / 4;
|
return (in_len * 3) / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode the base64 format string.
|
/// Decode the base64 format string.
|
||||||
/// Return the number of bytes written.
|
DROGON_EXPORT std::string base64Decode(std::string_view encoded_string);
|
||||||
DROGON_EXPORT size_t base64Decode(const char *encodedString,
|
|
||||||
size_t inLen,
|
|
||||||
unsigned char *outputBuffer);
|
|
||||||
|
|
||||||
/// Decode the base64 format string.
|
|
||||||
inline std::string base64Decode(std::string_view encodedString)
|
|
||||||
{
|
|
||||||
auto inLen = encodedString.size();
|
|
||||||
std::string ret;
|
|
||||||
ret.resize(base64DecodedLength(inLen));
|
|
||||||
ret.resize(
|
|
||||||
base64Decode(encodedString.data(), inLen, (unsigned char *)ret.data()));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
DROGON_EXPORT std::vector<char> base64DecodeToVector(
|
DROGON_EXPORT std::vector<char> base64DecodeToVector(
|
||||||
std::string_view encodedString);
|
std::string_view encoded_string);
|
||||||
|
|
||||||
/// Check if the string need decoding
|
/// Check if the string need decoding
|
||||||
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);
|
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);
|
||||||
@ -301,12 +263,6 @@ DROGON_EXPORT std::string brotliDecompress(const char *data,
|
|||||||
DROGON_EXPORT char *getHttpFullDate(
|
DROGON_EXPORT char *getHttpFullDate(
|
||||||
const trantor::Date &date = trantor::Date::now());
|
const trantor::Date &date = trantor::Date::now());
|
||||||
|
|
||||||
DROGON_EXPORT const std::string &getHttpFullDateStr(
|
|
||||||
const trantor::Date &date = trantor::Date::now());
|
|
||||||
|
|
||||||
DROGON_EXPORT void dateToCustomFormattedString(const std::string &fmtStr,
|
|
||||||
std::string &str,
|
|
||||||
const trantor::Date &date);
|
|
||||||
/// Get the trantor::Date object according to the http full date string
|
/// Get the trantor::Date object according to the http full date string
|
||||||
/**
|
/**
|
||||||
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
|
* Returns trantor::Date(std::numeric_limits<int64_t>::max()) upon failure.
|
||||||
@ -584,8 +540,7 @@ namespace trantor
|
|||||||
{
|
{
|
||||||
inline LogStream &operator<<(LogStream &ls, const std::string_view &v)
|
inline LogStream &operator<<(LogStream &ls, const std::string_view &v)
|
||||||
{
|
{
|
||||||
if (!v.empty())
|
ls.append(v.data(), v.length());
|
||||||
ls.append(v.data(), v.length());
|
|
||||||
return ls;
|
return ls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ struct [[nodiscard]] Task
|
|||||||
|
|
||||||
std::optional<T> value;
|
std::optional<T> value;
|
||||||
std::exception_ptr exception_;
|
std::exception_ptr exception_;
|
||||||
std::coroutine_handle<> continuation_{std::noop_coroutine()};
|
std::coroutine_handle<> continuation_;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto operator co_await() const noexcept
|
auto operator co_await() const noexcept
|
||||||
@ -332,7 +332,7 @@ struct [[nodiscard]] Task<void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::exception_ptr exception_;
|
std::exception_ptr exception_;
|
||||||
std::coroutine_handle<> continuation_{std::noop_coroutine()};
|
std::coroutine_handle<> continuation_;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto operator co_await() const noexcept
|
auto operator co_await() const noexcept
|
||||||
@ -811,180 +811,4 @@ inline internal::EventLoopAwaiter<T> queueInLoopCoro(trantor::EventLoop *loop,
|
|||||||
return internal::EventLoopAwaiter<T>(std::move(task), loop);
|
return internal::EventLoopAwaiter<T>(std::move(task), loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Mutex final
|
|
||||||
{
|
|
||||||
class ScopedCoroMutexAwaiter;
|
|
||||||
class CoroMutexAwaiter;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Mutex() noexcept : state_(unlockedValue()), waiters_(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Mutex(const Mutex &) = delete;
|
|
||||||
Mutex(Mutex &&) = delete;
|
|
||||||
Mutex &operator=(const Mutex &) = delete;
|
|
||||||
Mutex &operator=(Mutex &&) = delete;
|
|
||||||
|
|
||||||
~Mutex()
|
|
||||||
{
|
|
||||||
[[maybe_unused]] auto state = state_.load(std::memory_order_relaxed);
|
|
||||||
assert(state == unlockedValue() || state == nullptr);
|
|
||||||
assert(waiters_ == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool try_lock() noexcept
|
|
||||||
{
|
|
||||||
void *oldValue = unlockedValue();
|
|
||||||
return state_.compare_exchange_strong(oldValue,
|
|
||||||
nullptr,
|
|
||||||
std::memory_order_acquire,
|
|
||||||
std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] ScopedCoroMutexAwaiter scoped_lock(
|
|
||||||
trantor::EventLoop *loop =
|
|
||||||
trantor::EventLoop::getEventLoopOfCurrentThread()) noexcept
|
|
||||||
{
|
|
||||||
return ScopedCoroMutexAwaiter(*this, loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] CoroMutexAwaiter lock(
|
|
||||||
trantor::EventLoop *loop =
|
|
||||||
trantor::EventLoop::getEventLoopOfCurrentThread()) noexcept
|
|
||||||
{
|
|
||||||
return CoroMutexAwaiter(*this, loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void unlock() noexcept
|
|
||||||
{
|
|
||||||
assert(state_.load(std::memory_order_relaxed) != unlockedValue());
|
|
||||||
auto *waitersHead = waiters_;
|
|
||||||
if (waitersHead == nullptr)
|
|
||||||
{
|
|
||||||
void *currentState = state_.load(std::memory_order_relaxed);
|
|
||||||
if (currentState == nullptr)
|
|
||||||
{
|
|
||||||
const bool releasedLock =
|
|
||||||
state_.compare_exchange_strong(currentState,
|
|
||||||
unlockedValue(),
|
|
||||||
std::memory_order_release,
|
|
||||||
std::memory_order_relaxed);
|
|
||||||
if (releasedLock)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentState = state_.exchange(nullptr, std::memory_order_acquire);
|
|
||||||
assert(currentState != unlockedValue());
|
|
||||||
assert(currentState != nullptr);
|
|
||||||
auto *waiter = static_cast<CoroMutexAwaiter *>(currentState);
|
|
||||||
do
|
|
||||||
{
|
|
||||||
auto *temp = waiter->next_;
|
|
||||||
waiter->next_ = waitersHead;
|
|
||||||
waitersHead = waiter;
|
|
||||||
waiter = temp;
|
|
||||||
} while (waiter != nullptr);
|
|
||||||
}
|
|
||||||
assert(waitersHead != nullptr);
|
|
||||||
waiters_ = waitersHead->next_;
|
|
||||||
if (waitersHead->loop_)
|
|
||||||
{
|
|
||||||
auto handle = waitersHead->handle_;
|
|
||||||
waitersHead->loop_->runInLoop([handle] { handle.resume(); });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
waitersHead->handle_.resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
class CoroMutexAwaiter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CoroMutexAwaiter(Mutex &mutex, trantor::EventLoop *loop) noexcept
|
|
||||||
: mutex_(mutex), loop_(loop)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool await_ready() noexcept
|
|
||||||
{
|
|
||||||
return mutex_.try_lock();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool await_suspend(std::coroutine_handle<> handle) noexcept
|
|
||||||
{
|
|
||||||
handle_ = handle;
|
|
||||||
return mutex_.asynclockImpl(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void await_resume() noexcept
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class Mutex;
|
|
||||||
|
|
||||||
Mutex &mutex_;
|
|
||||||
trantor::EventLoop *loop_;
|
|
||||||
std::coroutine_handle<> handle_;
|
|
||||||
CoroMutexAwaiter *next_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ScopedCoroMutexAwaiter : public CoroMutexAwaiter
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ScopedCoroMutexAwaiter(Mutex &mutex, trantor::EventLoop *loop)
|
|
||||||
: CoroMutexAwaiter(mutex, loop)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto await_resume() noexcept
|
|
||||||
{
|
|
||||||
return std::unique_lock<Mutex>{mutex_, std::adopt_lock};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool asynclockImpl(CoroMutexAwaiter *awaiter)
|
|
||||||
{
|
|
||||||
void *oldValue = state_.load(std::memory_order_relaxed);
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (oldValue == unlockedValue())
|
|
||||||
{
|
|
||||||
void *newValue = nullptr;
|
|
||||||
if (state_.compare_exchange_weak(oldValue,
|
|
||||||
newValue,
|
|
||||||
std::memory_order_acquire,
|
|
||||||
std::memory_order_relaxed))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
void *newValue = awaiter;
|
|
||||||
awaiter->next_ = static_cast<CoroMutexAwaiter *>(oldValue);
|
|
||||||
if (state_.compare_exchange_weak(oldValue,
|
|
||||||
newValue,
|
|
||||||
std::memory_order_release,
|
|
||||||
std::memory_order_relaxed))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void *unlockedValue() noexcept
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::atomic<void *> state_;
|
|
||||||
CoroMutexAwaiter *waiters_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace drogon
|
} // namespace drogon
|
||||||
|
@ -60,10 +60,8 @@ class Collector : public CollectorBase
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Arguments>
|
|
||||||
const std::shared_ptr<T> &metric(
|
const std::shared_ptr<T> &metric(
|
||||||
const std::vector<std::string> &labelValues,
|
const std::vector<std::string> &labelValues) noexcept(false)
|
||||||
Arguments... args) noexcept(false)
|
|
||||||
{
|
{
|
||||||
if (labelValues.size() != labelsNames_.size())
|
if (labelValues.size() != labelsNames_.size())
|
||||||
{
|
{
|
||||||
@ -77,8 +75,7 @@ class Collector : public CollectorBase
|
|||||||
{
|
{
|
||||||
return iter->second;
|
return iter->second;
|
||||||
}
|
}
|
||||||
auto metric =
|
auto metric = std::make_shared<T>(name_, labelsNames_, labelValues);
|
||||||
std::make_shared<T>(name_, labelsNames_, labelValues, args...);
|
|
||||||
metrics_[labelValues] = metric;
|
metrics_[labelValues] = metric;
|
||||||
return metrics_[labelValues];
|
return metrics_[labelValues];
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class Gauge : public Metric
|
|||||||
|
|
||||||
static std::string_view type()
|
static std::string_view type()
|
||||||
{
|
{
|
||||||
return "gauge";
|
return "counter";
|
||||||
}
|
}
|
||||||
|
|
||||||
void setToCurrentTime()
|
void setToCurrentTime()
|
||||||
|
@ -68,7 +68,6 @@ class DROGON_EXPORT Histogram : public Metric
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeBuckets_.emplace_back();
|
timeBuckets_.emplace_back();
|
||||||
timeBuckets_.back().buckets.resize(bucketBoundaries.size() + 1);
|
|
||||||
// check the bucket boundaries are sorted
|
// check the bucket boundaries are sorted
|
||||||
for (size_t i = 1; i < bucketBoundaries.size(); i++)
|
for (size_t i = 1; i < bucketBoundaries.size(); i++)
|
||||||
{
|
{
|
||||||
|
@ -20,10 +20,10 @@
|
|||||||
namespace drogon
|
namespace drogon
|
||||||
{
|
{
|
||||||
|
|
||||||
static void doAdviceChain(
|
static void doAdvicesChain(
|
||||||
const std::vector<std::function<void(const HttpRequestPtr &,
|
const std::vector<std::function<void(const HttpRequestPtr &,
|
||||||
AdviceCallback &&,
|
AdviceCallback &&,
|
||||||
AdviceChainCallback &&)>> &adviceChain,
|
AdviceChainCallback &&)>> &advices,
|
||||||
size_t index,
|
size_t index,
|
||||||
const HttpRequestImplPtr &req,
|
const HttpRequestImplPtr &req,
|
||||||
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
||||||
@ -88,7 +88,7 @@ void AopAdvice::passPreRoutingAdvices(
|
|||||||
|
|
||||||
auto callbackPtr =
|
auto callbackPtr =
|
||||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||||
doAdviceChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
|
doAdvicesChain(preRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
|
void AopAdvice::passPostRoutingObservers(const HttpRequestImplPtr &req) const
|
||||||
@ -114,7 +114,7 @@ void AopAdvice::passPostRoutingAdvices(
|
|||||||
|
|
||||||
auto callbackPtr =
|
auto callbackPtr =
|
||||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||||
doAdviceChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
|
doAdvicesChain(postRoutingAdvices_, 0, req, std::move(callbackPtr));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
|
void AopAdvice::passPreHandlingObservers(const HttpRequestImplPtr &req) const
|
||||||
@ -140,7 +140,7 @@ void AopAdvice::passPreHandlingAdvices(
|
|||||||
|
|
||||||
auto callbackPtr =
|
auto callbackPtr =
|
||||||
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
std::make_shared<std::decay_t<decltype(callback)>>(std::move(callback));
|
||||||
doAdviceChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
|
doAdvicesChain(preHandlingAdvices_, 0, req, std::move(callbackPtr));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AopAdvice::passPostHandlingAdvices(const HttpRequestImplPtr &req,
|
void AopAdvice::passPostHandlingAdvices(const HttpRequestImplPtr &req,
|
||||||
@ -161,43 +161,43 @@ void AopAdvice::passPreSendingAdvices(const HttpRequestImplPtr &req,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void doAdviceChain(
|
static void doAdvicesChain(
|
||||||
const std::vector<std::function<void(const HttpRequestPtr &,
|
const std::vector<std::function<void(const HttpRequestPtr &,
|
||||||
AdviceCallback &&,
|
AdviceCallback &&,
|
||||||
AdviceChainCallback &&)>> &adviceChain,
|
AdviceChainCallback &&)>> &advices,
|
||||||
size_t index,
|
size_t index,
|
||||||
const HttpRequestImplPtr &req,
|
const HttpRequestImplPtr &req,
|
||||||
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
std::shared_ptr<const std::function<void(const HttpResponsePtr &)>>
|
||||||
&&callbackPtr)
|
&&callbackPtr)
|
||||||
{
|
{
|
||||||
if (index < adviceChain.size())
|
if (index < advices.size())
|
||||||
{
|
{
|
||||||
auto &advice = adviceChain[index];
|
auto &advice = advices[index];
|
||||||
advice(
|
advice(
|
||||||
req,
|
req,
|
||||||
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
|
[/*copy*/ callbackPtr](const HttpResponsePtr &resp) {
|
||||||
(*callbackPtr)(resp);
|
(*callbackPtr)(resp);
|
||||||
},
|
},
|
||||||
[index, req, callbackPtr, &adviceChain]() mutable {
|
[index, req, callbackPtr, &advices]() mutable {
|
||||||
auto ioLoop = req->getLoop();
|
auto ioLoop = req->getLoop();
|
||||||
if (ioLoop && !ioLoop->isInLoopThread())
|
if (ioLoop && !ioLoop->isInLoopThread())
|
||||||
{
|
{
|
||||||
ioLoop->queueInLoop([index,
|
ioLoop->queueInLoop([index,
|
||||||
req,
|
req,
|
||||||
callbackPtr = std::move(callbackPtr),
|
callbackPtr = std::move(callbackPtr),
|
||||||
&adviceChain]() mutable {
|
&advices]() mutable {
|
||||||
doAdviceChain(adviceChain,
|
doAdvicesChain(advices,
|
||||||
index + 1,
|
index + 1,
|
||||||
req,
|
req,
|
||||||
std::move(callbackPtr));
|
std::move(callbackPtr));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
doAdviceChain(adviceChain,
|
doAdvicesChain(advices,
|
||||||
index + 1,
|
index + 1,
|
||||||
req,
|
req,
|
||||||
std::move(callbackPtr));
|
std::move(callbackPtr));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -113,46 +113,6 @@ void AccessLogger::initAndStart(const Json::Value &config)
|
|||||||
}
|
}
|
||||||
createLogFunctions(format);
|
createLogFunctions(format);
|
||||||
auto logPath = config.get("log_path", "").asString();
|
auto logPath = config.get("log_path", "").asString();
|
||||||
|
|
||||||
if (config.isMember("path_exempt"))
|
|
||||||
{
|
|
||||||
if (config["path_exempt"].isArray())
|
|
||||||
{
|
|
||||||
const auto &exempts = config["path_exempt"];
|
|
||||||
size_t exemptsCount = exempts.size();
|
|
||||||
if (exemptsCount)
|
|
||||||
{
|
|
||||||
std::string regexString;
|
|
||||||
size_t len = 0;
|
|
||||||
for (const auto &exempt : exempts)
|
|
||||||
{
|
|
||||||
assert(exempt.isString());
|
|
||||||
len += exempt.size();
|
|
||||||
}
|
|
||||||
regexString.reserve((exemptsCount * (1 + 2)) - 1 + len);
|
|
||||||
|
|
||||||
const auto last = --exempts.end();
|
|
||||||
for (auto exempt = exempts.begin(); exempt != last; ++exempt)
|
|
||||||
regexString.append("(")
|
|
||||||
.append(exempt->asString())
|
|
||||||
.append(")|");
|
|
||||||
regexString.append("(").append(last->asString()).append(")");
|
|
||||||
|
|
||||||
exemptRegex_ = std::regex(regexString);
|
|
||||||
regexFlag_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (config["path_exempt"].isString())
|
|
||||||
{
|
|
||||||
exemptRegex_ = std::regex(config["path_exempt"].asString());
|
|
||||||
regexFlag_ = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_ERROR << "path_exempt must be a string or string array!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DROGON_SPDLOG_SUPPORT
|
#ifdef DROGON_SPDLOG_SUPPORT
|
||||||
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
|
auto logWithSpdlog = trantor::Logger::hasSpdLogSupport() &&
|
||||||
config.get("use_spdlog", false).asBool();
|
config.get("use_spdlog", false).asBool();
|
||||||
@ -268,17 +228,7 @@ void AccessLogger::initAndStart(const Json::Value &config)
|
|||||||
drogon::app().registerPreSendingAdvice(
|
drogon::app().registerPreSendingAdvice(
|
||||||
[this](const drogon::HttpRequestPtr &req,
|
[this](const drogon::HttpRequestPtr &req,
|
||||||
const drogon::HttpResponsePtr &resp) {
|
const drogon::HttpResponsePtr &resp) {
|
||||||
if (regexFlag_)
|
logging(LOG_RAW_TO(logIndex_), req, resp);
|
||||||
{
|
|
||||||
if (!std::regex_match(req->path(), exemptRegex_))
|
|
||||||
{
|
|
||||||
logging(LOG_RAW_TO(logIndex_), req, resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logging(LOG_RAW_TO(logIndex_), req, resp);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +383,7 @@ static void loadApp(const Json::Value &app)
|
|||||||
{
|
{
|
||||||
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
|
drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP);
|
||||||
}
|
}
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
// dynamic views
|
// dynamic views
|
||||||
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
|
auto enableDynamicViews = app.get("load_dynamic_views", false).asBool();
|
||||||
if (enableDynamicViews)
|
if (enableDynamicViews)
|
||||||
@ -524,9 +524,6 @@ static void loadApp(const Json::Value &app)
|
|||||||
bool enableCompressedRequests =
|
bool enableCompressedRequests =
|
||||||
app.get("enabled_compressed_request", false).asBool();
|
app.get("enabled_compressed_request", false).asBool();
|
||||||
drogon::app().enableCompressedRequest(enableCompressedRequests);
|
drogon::app().enableCompressedRequest(enableCompressedRequests);
|
||||||
|
|
||||||
drogon::app().enableRequestStream(
|
|
||||||
app.get("enable_request_stream", false).asBool());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loadDbClients(const Json::Value &dbClients)
|
static void loadDbClients(const Json::Value &dbClients)
|
||||||
|
@ -41,11 +41,6 @@ struct ControllerBinderBase
|
|||||||
virtual void handleRequest(
|
virtual void handleRequest(
|
||||||
const HttpRequestImplPtr &req,
|
const HttpRequestImplPtr &req,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) const = 0;
|
std::function<void(const HttpResponsePtr &)> &&callback) const = 0;
|
||||||
|
|
||||||
virtual bool isStreamHandler() const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RouteResult
|
struct RouteResult
|
||||||
|
@ -19,18 +19,16 @@ using namespace drogon;
|
|||||||
|
|
||||||
std::string Cookie::cookieString() const
|
std::string Cookie::cookieString() const
|
||||||
{
|
{
|
||||||
constexpr std::string_view prefix = "Set-Cookie: ";
|
std::string ret = "Set-Cookie: ";
|
||||||
std::string ret;
|
|
||||||
// reserve space to reduce frequency allocation
|
// reserve space to reduce frequency allocation
|
||||||
ret.reserve(prefix.size() + key_.size() + value_.size() + 30);
|
ret.reserve(ret.size() + key_.size() + value_.size() + 30);
|
||||||
ret = prefix;
|
|
||||||
ret.append(key_).append("=").append(value_).append("; ");
|
ret.append(key_).append("=").append(value_).append("; ");
|
||||||
if (expiresDate_.microSecondsSinceEpoch() !=
|
if (expiresDate_.microSecondsSinceEpoch() !=
|
||||||
(std::numeric_limits<int64_t>::max)() &&
|
(std::numeric_limits<int64_t>::max)() &&
|
||||||
expiresDate_.microSecondsSinceEpoch() >= 0)
|
expiresDate_.microSecondsSinceEpoch() >= 0)
|
||||||
{
|
{
|
||||||
ret.append("Expires=")
|
ret.append("Expires=")
|
||||||
.append(utils::getHttpFullDateStr(expiresDate_))
|
.append(utils::getHttpFullDate(expiresDate_))
|
||||||
.append("; ");
|
.append("; ");
|
||||||
}
|
}
|
||||||
if (maxAge_.has_value())
|
if (maxAge_.has_value())
|
||||||
@ -69,7 +67,7 @@ std::string Cookie::cookieString() const
|
|||||||
ret.append("SameSite=Lax; ");
|
ret.append("SameSite=Lax; ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((secure_ && sameSite_ != SameSite::kNone) || partitioned_)
|
if (secure_ && sameSite_ != SameSite::kNone)
|
||||||
{
|
{
|
||||||
ret.append("Secure; ");
|
ret.append("Secure; ");
|
||||||
}
|
}
|
||||||
@ -77,10 +75,6 @@ std::string Cookie::cookieString() const
|
|||||||
{
|
{
|
||||||
ret.append("HttpOnly; ");
|
ret.append("HttpOnly; ");
|
||||||
}
|
}
|
||||||
if (partitioned_)
|
|
||||||
{
|
|
||||||
ret.append("Partitioned; ");
|
|
||||||
}
|
|
||||||
ret.resize(ret.length() - 2); // delete last semicolon
|
ret.resize(ret.length() - 2); // delete last semicolon
|
||||||
ret.append("\r\n");
|
ret.append("\r\n");
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -105,17 +105,6 @@ void Hodor::initAndStart(const Json::Value &config)
|
|||||||
limitStrategies_.emplace_back(makeLimitStrategy(subLimit));
|
limitStrategies_.emplace_back(makeLimitStrategy(subLimit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Json::Value &trustIps = config["trust_ips"];
|
|
||||||
if (!trustIps.isNull() && !trustIps.isArray())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Invalid trusted_ips. Should be array.");
|
|
||||||
}
|
|
||||||
for (const auto &ipOrCidr : trustIps)
|
|
||||||
{
|
|
||||||
trustCIDRs_.emplace_back(ipOrCidr.asString());
|
|
||||||
}
|
|
||||||
|
|
||||||
app().registerPreHandlingAdvice([this](const drogon::HttpRequestPtr &req,
|
app().registerPreHandlingAdvice([this](const drogon::HttpRequestPtr &req,
|
||||||
AdviceCallback &&acb,
|
AdviceCallback &&acb,
|
||||||
AdviceChainCallback &&accb) {
|
AdviceChainCallback &&accb) {
|
||||||
@ -130,13 +119,9 @@ void Hodor::shutdown()
|
|||||||
|
|
||||||
bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
|
bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
|
||||||
const LimitStrategy &strategy,
|
const LimitStrategy &strategy,
|
||||||
const trantor::InetAddress &ip,
|
const std::string &ip,
|
||||||
const std::optional<std::string> &userId)
|
const std::optional<std::string> &userId)
|
||||||
{
|
{
|
||||||
if (RealIpResolver::matchCidr(ip, trustCIDRs_))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (strategy.regexFlag)
|
if (strategy.regexFlag)
|
||||||
{
|
{
|
||||||
if (!std::regex_match(req->path(), strategy.urlsRegex))
|
if (!std::regex_match(req->path(), strategy.urlsRegex))
|
||||||
@ -155,7 +140,7 @@ bool Hodor::checkLimit(const drogon::HttpRequestPtr &req,
|
|||||||
{
|
{
|
||||||
RateLimiterPtr limiterPtr;
|
RateLimiterPtr limiterPtr;
|
||||||
strategy.ipLimiterMapPtr->modify(
|
strategy.ipLimiterMapPtr->modify(
|
||||||
ip.toIpNetEndian(),
|
ip,
|
||||||
[this, &limiterPtr, &strategy](RateLimiterPtr &ptr) {
|
[this, &limiterPtr, &strategy](RateLimiterPtr &ptr) {
|
||||||
if (!ptr)
|
if (!ptr)
|
||||||
{
|
{
|
||||||
@ -222,9 +207,10 @@ void Hodor::onHttpRequest(const drogon::HttpRequestPtr &req,
|
|||||||
drogon::AdviceCallback &&adviceCallback,
|
drogon::AdviceCallback &&adviceCallback,
|
||||||
drogon::AdviceChainCallback &&chainCallback)
|
drogon::AdviceChainCallback &&chainCallback)
|
||||||
{
|
{
|
||||||
const trantor::InetAddress &ip =
|
auto ip =
|
||||||
useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
|
(useRealIpResolver_ ? drogon::plugin::RealIpResolver::GetRealAddr(req)
|
||||||
: req->peerAddr();
|
: req->peerAddr())
|
||||||
|
.toIpNetEndian();
|
||||||
std::optional<std::string> userId;
|
std::optional<std::string> userId;
|
||||||
if (userIdGetter_)
|
if (userIdGetter_)
|
||||||
{
|
{
|
||||||
|
105
lib/src/Http1xTransport.cc
Normal file
105
lib/src/Http1xTransport.cc
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#include "Http1xTransport.h"
|
||||||
|
#include "HttpResponseParser.h"
|
||||||
|
|
||||||
|
using namespace drogon;
|
||||||
|
|
||||||
|
Http1xTransport::Http1xTransport(trantor::TcpConnectionPtr connPtr,
|
||||||
|
Version version,
|
||||||
|
size_t *bytesSent,
|
||||||
|
size_t *bytesReceived)
|
||||||
|
: connPtr(connPtr),
|
||||||
|
bytesSent_(bytesSent),
|
||||||
|
bytesReceived_(bytesReceived),
|
||||||
|
version_(version)
|
||||||
|
{
|
||||||
|
connPtr->setContext(std::make_shared<HttpResponseParser>(connPtr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Http1xTransport::sendRequestInLoop(const HttpRequestPtr &req,
|
||||||
|
HttpReqCallback &&callback)
|
||||||
|
{
|
||||||
|
sendReq(req);
|
||||||
|
pipeliningCallbacks_.emplace(std::move(req), std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Http1xTransport::onRecvMessage(const trantor::TcpConnectionPtr &conn,
|
||||||
|
trantor::MsgBuffer *msg)
|
||||||
|
{
|
||||||
|
auto responseParser = connPtr->getContext<HttpResponseParser>();
|
||||||
|
assert(responseParser != nullptr);
|
||||||
|
assert(connPtr.get() == conn.get());
|
||||||
|
|
||||||
|
// LOG_TRACE << "###:" << msg->readableBytes();
|
||||||
|
auto msgSize = msg->readableBytes();
|
||||||
|
while (msg->readableBytes() > 0)
|
||||||
|
{
|
||||||
|
if (pipeliningCallbacks_.empty())
|
||||||
|
{
|
||||||
|
LOG_ERROR << "More responses than expected!";
|
||||||
|
connPtr->shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &firstReq = pipeliningCallbacks_.front();
|
||||||
|
if (firstReq.first->method() == Head)
|
||||||
|
{
|
||||||
|
responseParser->setForHeadMethod();
|
||||||
|
}
|
||||||
|
if (!responseParser->parseResponse(msg))
|
||||||
|
{
|
||||||
|
*bytesReceived_ += (msgSize - msg->readableBytes());
|
||||||
|
errorCallback(ReqResult::BadResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (responseParser->gotAll())
|
||||||
|
{
|
||||||
|
auto resp = responseParser->responseImpl();
|
||||||
|
responseParser->reset();
|
||||||
|
*bytesReceived_ += (msgSize - msg->readableBytes());
|
||||||
|
msgSize = msg->readableBytes();
|
||||||
|
respCallback(resp, std::move(firstReq), conn);
|
||||||
|
|
||||||
|
pipeliningCallbacks_.pop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*bytesReceived_ += (msgSize - msg->readableBytes());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Http1xTransport::~Http1xTransport()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Http1xTransport::handleConnectionClose()
|
||||||
|
{
|
||||||
|
auto responseParser = connPtr->getContext<HttpResponseParser>();
|
||||||
|
if (responseParser && responseParser->parseResponseOnClose() &&
|
||||||
|
responseParser->gotAll())
|
||||||
|
{
|
||||||
|
auto &firstReq = pipeliningCallbacks_.front();
|
||||||
|
if (firstReq.first->method() == Head)
|
||||||
|
{
|
||||||
|
responseParser->setForHeadMethod();
|
||||||
|
}
|
||||||
|
auto resp = responseParser->responseImpl();
|
||||||
|
responseParser->reset();
|
||||||
|
respCallback(resp, std::move(firstReq), connPtr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Http1xTransport::sendReq(const HttpRequestPtr &req)
|
||||||
|
{
|
||||||
|
trantor::MsgBuffer buffer;
|
||||||
|
assert(req);
|
||||||
|
auto implPtr = static_cast<HttpRequestImpl *>(req.get());
|
||||||
|
assert(version_ == Version::kHttp10 || version_ == Version::kHttp11);
|
||||||
|
implPtr->appendToBuffer(&buffer, version_);
|
||||||
|
LOG_TRACE << "Send request:"
|
||||||
|
<< std::string_view(buffer.peek(), buffer.readableBytes());
|
||||||
|
*bytesSent_ += buffer.readableBytes();
|
||||||
|
connPtr->send(std::move(buffer));
|
||||||
|
}
|
52
lib/src/Http1xTransport.h
Normal file
52
lib/src/Http1xTransport.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <trantor/net/EventLoop.h>
|
||||||
|
#include <trantor/net/TcpClient.h>
|
||||||
|
#include <list>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
#include "HttpTransport.h"
|
||||||
|
|
||||||
|
namespace drogon
|
||||||
|
{
|
||||||
|
class Http1xTransport : public HttpTransport
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> pipeliningCallbacks_;
|
||||||
|
trantor::TcpConnectionPtr connPtr;
|
||||||
|
size_t *bytesSent_;
|
||||||
|
size_t *bytesReceived_;
|
||||||
|
Version version_{Version::kHttp11};
|
||||||
|
|
||||||
|
void sendReq(const HttpRequestPtr &req);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Http1xTransport(trantor::TcpConnectionPtr connPtr,
|
||||||
|
Version version,
|
||||||
|
size_t *bytesSent,
|
||||||
|
size_t *bytesReceived);
|
||||||
|
virtual ~Http1xTransport();
|
||||||
|
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||||
|
HttpReqCallback &&callback) override;
|
||||||
|
void onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||||
|
trantor::MsgBuffer *) override;
|
||||||
|
|
||||||
|
size_t requestsInFlight() const override
|
||||||
|
{
|
||||||
|
return pipeliningCallbacks_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleConnectionClose() override;
|
||||||
|
|
||||||
|
void onError(ReqResult result) override
|
||||||
|
{
|
||||||
|
while (!pipeliningCallbacks_.empty())
|
||||||
|
{
|
||||||
|
auto &cb = pipeliningCallbacks_.front().second;
|
||||||
|
cb(result, nullptr);
|
||||||
|
pipeliningCallbacks_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace drogon
|
1533
lib/src/Http2Transport.cc
Normal file
1533
lib/src/Http2Transport.cc
Normal file
File diff suppressed because it is too large
Load Diff
553
lib/src/Http2Transport.h
Normal file
553
lib/src/Http2Transport.h
Normal file
@ -0,0 +1,553 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HttpTransport.h"
|
||||||
|
#include "HttpResponseImpl.h"
|
||||||
|
#include "hpack.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
namespace drogon
|
||||||
|
{
|
||||||
|
|
||||||
|
using namespace EricHpack;
|
||||||
|
|
||||||
|
namespace internal
|
||||||
|
{
|
||||||
|
// Quick and dirty ByteStream implementation and extensions so we can use it
|
||||||
|
// to read from the buffer, safely. At least it checks for buffer overflows
|
||||||
|
// in debug mode.
|
||||||
|
struct ByteStream
|
||||||
|
{
|
||||||
|
ByteStream(uint8_t *ptr, size_t length) : ptr(ptr), length(length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteStream(const trantor::MsgBuffer &buffer, size_t length)
|
||||||
|
: ptr((uint8_t *)buffer.peek()), length(length)
|
||||||
|
{
|
||||||
|
assert(length <= buffer.readableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t readU24BE()
|
||||||
|
{
|
||||||
|
assert(length >= 3 && offset <= length - 3);
|
||||||
|
uint32_t res =
|
||||||
|
ptr[offset] << 16 | ptr[offset + 1] << 8 | ptr[offset + 2];
|
||||||
|
offset += 3;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t readU32BE()
|
||||||
|
{
|
||||||
|
assert(length >= 4 && offset <= length - 4);
|
||||||
|
uint32_t res = ptr[offset] << 24 | ptr[offset + 1] << 16 |
|
||||||
|
ptr[offset + 2] << 8 | ptr[offset + 3];
|
||||||
|
offset += 4;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, int32_t> readBI31BE()
|
||||||
|
{
|
||||||
|
assert(length >= 4 && offset <= length - 4);
|
||||||
|
int32_t res = ptr[offset] << 24 | ptr[offset + 1] << 16 |
|
||||||
|
ptr[offset + 2] << 8 | ptr[offset + 3];
|
||||||
|
offset += 4;
|
||||||
|
constexpr int32_t mask = 0x7fffffff;
|
||||||
|
bool flag = res & (~mask);
|
||||||
|
res &= mask;
|
||||||
|
return {flag, res};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t readU16BE()
|
||||||
|
{
|
||||||
|
assert(length >= 2 && offset <= length - 2);
|
||||||
|
uint16_t res = ptr[offset] << 8 | ptr[offset + 1];
|
||||||
|
offset += 2;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t readU8()
|
||||||
|
{
|
||||||
|
assert(length >= 1 && offset <= length - 1);
|
||||||
|
return ptr[offset++];
|
||||||
|
}
|
||||||
|
|
||||||
|
void read(uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
assert((length >= size && offset <= length - size) || size == 0);
|
||||||
|
assert(buffer != nullptr);
|
||||||
|
assert(ptr != nullptr);
|
||||||
|
memcpy(buffer, ptr + offset, size);
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void skip(size_t n)
|
||||||
|
{
|
||||||
|
assert((length >= n && offset <= length - n) || n == 0);
|
||||||
|
offset += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t remaining() const
|
||||||
|
{
|
||||||
|
return length - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t *ptr;
|
||||||
|
size_t length;
|
||||||
|
size_t offset = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// DITTO but for serialization
|
||||||
|
struct OByteStream
|
||||||
|
{
|
||||||
|
void writeU24BE(uint32_t value)
|
||||||
|
{
|
||||||
|
assert(value <= 0xffffff);
|
||||||
|
value = htonl(value);
|
||||||
|
buffer.append((char *)&value + 1, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeU32BE(uint32_t value)
|
||||||
|
{
|
||||||
|
value = htonl(value);
|
||||||
|
buffer.append((char *)&value, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeU16BE(uint16_t value)
|
||||||
|
{
|
||||||
|
value = htons(value);
|
||||||
|
buffer.append((char *)&value, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeU8(uint8_t value)
|
||||||
|
{
|
||||||
|
buffer.append((char *)&value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pad(size_t size, uint8_t value = 0)
|
||||||
|
{
|
||||||
|
buffer.ensureWritableBytes(size);
|
||||||
|
auto ptr = (uint8_t *)buffer.peek() + buffer.readableBytes();
|
||||||
|
memset(ptr, value, size);
|
||||||
|
buffer.hasWritten(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const uint8_t *ptr, size_t size)
|
||||||
|
{
|
||||||
|
buffer.append((char *)ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const std::string_view &str)
|
||||||
|
{
|
||||||
|
buffer.append(str.data(), str.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void overwriteU24BE(size_t offset, uint32_t value)
|
||||||
|
{
|
||||||
|
assert(value <= 0xffffff);
|
||||||
|
assert(offset <= buffer.readableBytes() - 3);
|
||||||
|
assert(buffer.writableBytes() >= 3);
|
||||||
|
auto ptr = (uint8_t *)buffer.peek() + offset;
|
||||||
|
ptr[0] = value >> 16;
|
||||||
|
ptr[1] = value >> 8;
|
||||||
|
ptr[2] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void overwriteU8(size_t offset, uint8_t value)
|
||||||
|
{
|
||||||
|
assert(offset <= buffer.readableBytes() - 1);
|
||||||
|
assert(buffer.writableBytes() >= 1);
|
||||||
|
auto ptr = (uint8_t *)buffer.peek() + offset;
|
||||||
|
ptr[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *peek()
|
||||||
|
{
|
||||||
|
return (uint8_t *)buffer.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const
|
||||||
|
{
|
||||||
|
return buffer.readableBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
trantor::MsgBuffer buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SettingsFrame
|
||||||
|
{
|
||||||
|
SettingsFrame() = default;
|
||||||
|
|
||||||
|
SettingsFrame(bool ack) : ack(ack)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ack = false;
|
||||||
|
std::vector<std::pair<uint16_t, uint32_t>> settings;
|
||||||
|
|
||||||
|
static std::optional<SettingsFrame> parse(ByteStream &payload,
|
||||||
|
uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WindowUpdateFrame
|
||||||
|
{
|
||||||
|
WindowUpdateFrame() = default;
|
||||||
|
|
||||||
|
WindowUpdateFrame(uint32_t windowSizeIncrement)
|
||||||
|
: windowSizeIncrement(windowSizeIncrement)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t windowSizeIncrement = 0;
|
||||||
|
|
||||||
|
static std::optional<WindowUpdateFrame> parse(ByteStream &payload,
|
||||||
|
uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HeadersFrame
|
||||||
|
{
|
||||||
|
HeadersFrame() = default;
|
||||||
|
|
||||||
|
HeadersFrame(std::vector<uint8_t> headerBlockFragment,
|
||||||
|
bool endHeaders,
|
||||||
|
bool endStream)
|
||||||
|
: headerBlockFragment(std::move(headerBlockFragment)),
|
||||||
|
endHeaders(endHeaders),
|
||||||
|
endStream(endStream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadersFrame(const uint8_t *ptr,
|
||||||
|
size_t size,
|
||||||
|
bool endHeaders,
|
||||||
|
bool endStream)
|
||||||
|
: headerBlockFragment(ptr, ptr + size),
|
||||||
|
endHeaders(endHeaders),
|
||||||
|
endStream(endStream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t padLength = 0;
|
||||||
|
bool exclusive = false;
|
||||||
|
uint32_t streamDependency = 0;
|
||||||
|
uint8_t weight = 0;
|
||||||
|
std::vector<uint8_t> headerBlockFragment;
|
||||||
|
bool endHeaders = false;
|
||||||
|
bool endStream = false;
|
||||||
|
|
||||||
|
static std::optional<HeadersFrame> parse(ByteStream &payload,
|
||||||
|
uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GoAwayFrame
|
||||||
|
{
|
||||||
|
GoAwayFrame() = default;
|
||||||
|
|
||||||
|
GoAwayFrame(uint32_t lastStreamId,
|
||||||
|
uint32_t errorCode,
|
||||||
|
const std::string &additionalDebugData)
|
||||||
|
: lastStreamId(lastStreamId),
|
||||||
|
errorCode(errorCode),
|
||||||
|
additionalDebugData((const uint8_t *)additionalDebugData.data(),
|
||||||
|
(const uint8_t *)additionalDebugData.data() +
|
||||||
|
additionalDebugData.size())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t lastStreamId = 0;
|
||||||
|
uint32_t errorCode = 0;
|
||||||
|
std::vector<uint8_t> additionalDebugData;
|
||||||
|
|
||||||
|
static std::optional<GoAwayFrame> parse(ByteStream &payload, uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DataFrame
|
||||||
|
{
|
||||||
|
DataFrame() = default;
|
||||||
|
|
||||||
|
DataFrame(std::vector<uint8_t> data, bool endStream)
|
||||||
|
: data(std::move(data)), endStream(endStream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DataFrame(const uint8_t *ptr, size_t size, bool endStream)
|
||||||
|
: data(std::vector<uint8_t>(ptr, ptr + size)), endStream(endStream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit DataFrame(std::string_view data, bool endStream)
|
||||||
|
: data(data), endStream(endStream)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t padLength = 0;
|
||||||
|
std::variant<std::vector<uint8_t>, std::string_view> data;
|
||||||
|
bool endStream = false;
|
||||||
|
|
||||||
|
std::pair<const uint8_t *, size_t> getData() const
|
||||||
|
{
|
||||||
|
if (std::holds_alternative<std::vector<uint8_t>>(data))
|
||||||
|
{
|
||||||
|
auto &vec = std::get<std::vector<uint8_t>>(data);
|
||||||
|
return {vec.data(), vec.size()};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto &str = std::get<std::string_view>(data);
|
||||||
|
return {(const uint8_t *)str.data(), str.size()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<DataFrame> parse(ByteStream &payload, uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PingFrame
|
||||||
|
{
|
||||||
|
PingFrame() = default;
|
||||||
|
|
||||||
|
PingFrame(std::array<uint8_t, 8> opaqueData, bool ack)
|
||||||
|
: opaqueData(opaqueData), ack(ack)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, 8> opaqueData;
|
||||||
|
bool ack = false;
|
||||||
|
|
||||||
|
static std::optional<PingFrame> parse(ByteStream &payload, uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContinuationFrame
|
||||||
|
{
|
||||||
|
ContinuationFrame() = default;
|
||||||
|
|
||||||
|
ContinuationFrame(std::vector<uint8_t> headerBlockFragment, bool endHeaders)
|
||||||
|
: headerBlockFragment(std::move(headerBlockFragment)),
|
||||||
|
endHeaders(endHeaders)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ContinuationFrame(const uint8_t *ptr, size_t size, bool endHeaders)
|
||||||
|
: headerBlockFragment(ptr, ptr + size), endHeaders(endHeaders)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> headerBlockFragment;
|
||||||
|
bool endHeaders = false;
|
||||||
|
|
||||||
|
static std::optional<ContinuationFrame> parse(ByteStream &payload,
|
||||||
|
uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RstStreamFrame
|
||||||
|
{
|
||||||
|
RstStreamFrame() = default;
|
||||||
|
|
||||||
|
RstStreamFrame(uint32_t errorCode) : errorCode(errorCode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t errorCode = 0;
|
||||||
|
|
||||||
|
static std::optional<RstStreamFrame> parse(ByteStream &payload,
|
||||||
|
uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PushPromiseFrame
|
||||||
|
{
|
||||||
|
PushPromiseFrame() = default;
|
||||||
|
|
||||||
|
PushPromiseFrame(uint32_t promisedStreamId,
|
||||||
|
std::vector<uint8_t> headerBlockFragment,
|
||||||
|
bool endHeaders)
|
||||||
|
: promisedStreamId(promisedStreamId),
|
||||||
|
headerBlockFragment(std::move(headerBlockFragment)),
|
||||||
|
endHeaders(endHeaders)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t padLength = 0;
|
||||||
|
int32_t promisedStreamId = 0;
|
||||||
|
std::vector<uint8_t> headerBlockFragment;
|
||||||
|
bool endHeaders = false;
|
||||||
|
|
||||||
|
static std::optional<PushPromiseFrame> parse(ByteStream &payload,
|
||||||
|
uint8_t flags);
|
||||||
|
bool serialize(OByteStream &stream, uint8_t &flags) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
using H2Frame = std::variant<SettingsFrame,
|
||||||
|
WindowUpdateFrame,
|
||||||
|
HeadersFrame,
|
||||||
|
GoAwayFrame,
|
||||||
|
DataFrame,
|
||||||
|
PingFrame,
|
||||||
|
ContinuationFrame,
|
||||||
|
PushPromiseFrame,
|
||||||
|
RstStreamFrame>;
|
||||||
|
|
||||||
|
enum class StreamState
|
||||||
|
{
|
||||||
|
SendingBody,
|
||||||
|
ExpectingHeaders,
|
||||||
|
ExpectingContinuation,
|
||||||
|
ExpectingData,
|
||||||
|
Finished,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Virtual stream that holds properties for the HTTP/2 stream
|
||||||
|
// Defaults to stream 0 global properties
|
||||||
|
struct H2Stream
|
||||||
|
{
|
||||||
|
HttpReqCallback callback;
|
||||||
|
HttpResponseImplPtr response;
|
||||||
|
HttpRequestPtr request;
|
||||||
|
std::string body;
|
||||||
|
std::optional<size_t> contentLength;
|
||||||
|
int32_t streamId = 0;
|
||||||
|
size_t avaliableTxWindow = 65535;
|
||||||
|
size_t avaliableRxWindow = 65535;
|
||||||
|
StreamState state = StreamState::ExpectingHeaders;
|
||||||
|
|
||||||
|
trantor::MsgBuffer multipartData;
|
||||||
|
};
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
enum class StreamCloseErrorCode
|
||||||
|
{
|
||||||
|
NoError = 0x0,
|
||||||
|
ProtocolError = 0x1,
|
||||||
|
InternalError = 0x2,
|
||||||
|
FlowControlError = 0x3,
|
||||||
|
SettingsTimeout = 0x4,
|
||||||
|
StreamClosed = 0x5,
|
||||||
|
FrameSizeError = 0x6,
|
||||||
|
RefusedStream = 0x7,
|
||||||
|
Cancel = 0x8,
|
||||||
|
CompressionError = 0x9,
|
||||||
|
ConnectError = 0xa,
|
||||||
|
EnhanceYourCalm = 0xb,
|
||||||
|
InadequateSecurity = 0xc,
|
||||||
|
Http11Required = 0xd,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Http2Transport : public HttpTransport
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// Implementation details, stuff we need to implement HTTP/2
|
||||||
|
trantor::TcpConnectionPtr connPtr;
|
||||||
|
size_t *bytesSent_;
|
||||||
|
size_t *bytesReceived_;
|
||||||
|
Hpack hpackTx;
|
||||||
|
Hpack hpackRx;
|
||||||
|
|
||||||
|
int32_t currentStreamId = 1;
|
||||||
|
std::unordered_map<int32_t, internal::H2Stream> streams;
|
||||||
|
std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> bufferedRequests;
|
||||||
|
trantor::MsgBuffer headerBufferRx;
|
||||||
|
internal::OByteStream batchedSendBuffer;
|
||||||
|
int32_t expectngContinuationStreamId = 0;
|
||||||
|
|
||||||
|
std::map<int32_t, size_t> pendingDataSend;
|
||||||
|
std::optional<decltype(pendingDataSend)::iterator> currentDataSend;
|
||||||
|
bool reconnectionIssued = false;
|
||||||
|
bool firstSettingsReceived = false;
|
||||||
|
|
||||||
|
// HTTP/2 client-wide settings (can be changed by server)
|
||||||
|
size_t maxConcurrentStreams = 100;
|
||||||
|
size_t initialRxWindowSize = 65535;
|
||||||
|
size_t initialTxWindowSize = 65535;
|
||||||
|
size_t maxFrameSize = 16384;
|
||||||
|
size_t maxRxDynamicTableSize = 4096;
|
||||||
|
|
||||||
|
// Configuration settings
|
||||||
|
const uint32_t windowIncreaseThreshold = 16384;
|
||||||
|
const uint32_t windowIncreaseSize = 128 * 1024; // 128KB
|
||||||
|
const uint32_t maxCompressiedHeaderSize = 2048;
|
||||||
|
const int32_t streamIdReconnectThreshold = INT_MAX - 8192;
|
||||||
|
|
||||||
|
// HTTP/2 connection-wide state
|
||||||
|
size_t avaliableTxWindow = 65535;
|
||||||
|
size_t avaliableRxWindow = 65535;
|
||||||
|
|
||||||
|
internal::H2Stream &createStream(int32_t streamId);
|
||||||
|
void responseSuccess(internal::H2Stream &stream);
|
||||||
|
void streamErrored(int32_t streamId, ReqResult result);
|
||||||
|
|
||||||
|
std::optional<int32_t> nextStreamId()
|
||||||
|
{
|
||||||
|
// XXX: Technically UB. But no one actually uses 1's complement
|
||||||
|
if (currentStreamId < 0)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
int32_t streamId = currentStreamId;
|
||||||
|
currentStreamId += 2;
|
||||||
|
return streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true when we SHOULD reconnect due to exhausting stream IDs.
|
||||||
|
// Doesn't mean we will. We will force a reconnect when we actually
|
||||||
|
// run out.
|
||||||
|
inline bool runningOutStreamId()
|
||||||
|
{
|
||||||
|
return currentStreamId > streamIdReconnectThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleFrameForStream(const internal::H2Frame &frame,
|
||||||
|
int32_t streamId,
|
||||||
|
uint8_t flags);
|
||||||
|
void connectionErrored(int32_t lastStreamId,
|
||||||
|
StreamCloseErrorCode errorCode,
|
||||||
|
std::string errorMsg = "");
|
||||||
|
|
||||||
|
bool parseAndApplyHeaders(internal::H2Stream &stream,
|
||||||
|
const void *data,
|
||||||
|
size_t len);
|
||||||
|
std::pair<size_t, bool> sendBodyForStream(internal::H2Stream &stream,
|
||||||
|
const void *data,
|
||||||
|
size_t size);
|
||||||
|
std::pair<size_t, bool> sendBodyForStream(internal::H2Stream &stream,
|
||||||
|
size_t offset);
|
||||||
|
void sendFrame(const internal::H2Frame &frame, int32_t streamId);
|
||||||
|
|
||||||
|
void sendBufferedData();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Http2Transport(trantor::TcpConnectionPtr connPtr,
|
||||||
|
size_t *bytesSent,
|
||||||
|
size_t *bytesReceived);
|
||||||
|
|
||||||
|
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||||
|
HttpReqCallback &&callback) override;
|
||||||
|
void onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||||
|
trantor::MsgBuffer *) override;
|
||||||
|
|
||||||
|
size_t requestsInFlight() const override
|
||||||
|
{
|
||||||
|
return streams.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleConnectionClose() override;
|
||||||
|
|
||||||
|
void onError(ReqResult result) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onServerSettingsReceived(){};
|
||||||
|
};
|
||||||
|
} // namespace drogon
|
@ -184,7 +184,7 @@ static void TERMFunction(int sig)
|
|||||||
HttpAppFrameworkImpl::~HttpAppFrameworkImpl() noexcept
|
HttpAppFrameworkImpl::~HttpAppFrameworkImpl() noexcept
|
||||||
{
|
{
|
||||||
// Destroy the following objects before the loop destruction
|
// Destroy the following objects before the loop destruction
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
sharedLibManagerPtr_.reset();
|
sharedLibManagerPtr_.reset();
|
||||||
#endif
|
#endif
|
||||||
sessionManagerPtr_.reset();
|
sessionManagerPtr_.reset();
|
||||||
@ -236,7 +236,7 @@ const std::string &HttpAppFrameworkImpl::getImplicitPage() const
|
|||||||
{
|
{
|
||||||
return StaticFileRouter::instance().getImplicitPage();
|
return StaticFileRouter::instance().getImplicitPage();
|
||||||
}
|
}
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::enableDynamicViewsLoading(
|
HttpAppFramework &HttpAppFrameworkImpl::enableDynamicViewsLoading(
|
||||||
const std::vector<std::string> &libPaths,
|
const std::vector<std::string> &libPaths,
|
||||||
const std::string &outputPath)
|
const std::string &outputPath)
|
||||||
@ -511,12 +511,6 @@ HttpAppFramework &HttpAppFrameworkImpl::setSSLFiles(const std::string &certPath,
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::reloadSSLFiles()
|
|
||||||
{
|
|
||||||
listenerManagerPtr_->reloadSSLFiles();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpAppFrameworkImpl::run()
|
void HttpAppFrameworkImpl::run()
|
||||||
{
|
{
|
||||||
if (!getLoop()->isInLoopThread())
|
if (!getLoop()->isInLoopThread())
|
||||||
@ -599,7 +593,7 @@ void HttpAppFrameworkImpl::run()
|
|||||||
LOG_INFO << "Start child process";
|
LOG_INFO << "Start child process";
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
if (!libFilePaths_.empty())
|
if (!libFilePaths_.empty())
|
||||||
{
|
{
|
||||||
sharedLibManagerPtr_ =
|
sharedLibManagerPtr_ =
|
||||||
@ -1033,7 +1027,7 @@ HttpAppFramework &HttpAppFrameworkImpl::createRedisClient(
|
|||||||
|
|
||||||
void HttpAppFrameworkImpl::quit()
|
void HttpAppFrameworkImpl::quit()
|
||||||
{
|
{
|
||||||
if (getLoop()->isRunning() && running_.exchange(false))
|
if (getLoop()->isRunning())
|
||||||
{
|
{
|
||||||
getLoop()->queueInLoop([this]() {
|
getLoop()->queueInLoop([this]() {
|
||||||
// Release members in the reverse order of initialization
|
// Release members in the reverse order of initialization
|
||||||
@ -1044,6 +1038,7 @@ void HttpAppFrameworkImpl::quit()
|
|||||||
pluginsManagerPtr_.reset();
|
pluginsManagerPtr_.reset();
|
||||||
redisClientManagerPtr_.reset();
|
redisClientManagerPtr_.reset();
|
||||||
dbClientManagerPtr_.reset();
|
dbClientManagerPtr_.reset();
|
||||||
|
running_ = false;
|
||||||
getLoop()->quit();
|
getLoop()->quit();
|
||||||
for (trantor::EventLoop *loop : ioLoopThreadPool_->getLoops())
|
for (trantor::EventLoop *loop : ioLoopThreadPool_->getLoops())
|
||||||
{
|
{
|
||||||
@ -1251,17 +1246,6 @@ int64_t HttpAppFrameworkImpl::getConnectionCount() const
|
|||||||
return HttpConnectionLimit::instance().getConnectionNum();
|
return HttpConnectionLimit::instance().getConnectionNum();
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::enableRequestStream(bool enable)
|
|
||||||
{
|
|
||||||
enableRequestStream_ = enable;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpAppFrameworkImpl::isRequestStreamEnabled() const
|
|
||||||
{
|
|
||||||
return enableRequestStream_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AOP registration methods
|
// AOP registration methods
|
||||||
|
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::registerNewConnectionAdvice(
|
HttpAppFramework &HttpAppFrameworkImpl::registerNewConnectionAdvice(
|
||||||
@ -1352,24 +1336,3 @@ HttpAppFramework &HttpAppFrameworkImpl::registerPreSendingAdvice(
|
|||||||
AopAdvice::instance().registerPreSendingAdvice(advice);
|
AopAdvice::instance().registerPreSendingAdvice(advice);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::setBeforeListenSockOptCallback(
|
|
||||||
std::function<void(int)> cb)
|
|
||||||
{
|
|
||||||
listenerManagerPtr_->setBeforeListenSockOptCallback(std::move(cb));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::setAfterAcceptSockOptCallback(
|
|
||||||
std::function<void(int)> cb)
|
|
||||||
{
|
|
||||||
listenerManagerPtr_->setAfterAcceptSockOptCallback(std::move(cb));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpAppFramework &HttpAppFrameworkImpl::setConnectionCallback(
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> cb)
|
|
||||||
{
|
|
||||||
listenerManagerPtr_->setConnectionCallback(std::move(cb));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
@ -84,9 +84,6 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
|||||||
override;
|
override;
|
||||||
HttpAppFramework &setSSLFiles(const std::string &certPath,
|
HttpAppFramework &setSSLFiles(const std::string &certPath,
|
||||||
const std::string &keyPath) override;
|
const std::string &keyPath) override;
|
||||||
|
|
||||||
HttpAppFramework &reloadSSLFiles() override;
|
|
||||||
|
|
||||||
void run() override;
|
void run() override;
|
||||||
HttpAppFramework ®isterWebSocketController(
|
HttpAppFramework ®isterWebSocketController(
|
||||||
const std::string &pathName,
|
const std::string &pathName,
|
||||||
@ -267,7 +264,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
|||||||
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
|
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
|
||||||
HttpAppFramework &setFileTypes(
|
HttpAppFramework &setFileTypes(
|
||||||
const std::vector<std::string> &types) override;
|
const std::vector<std::string> &types) override;
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
HttpAppFramework &enableDynamicViewsLoading(
|
HttpAppFramework &enableDynamicViewsLoading(
|
||||||
const std::vector<std::string> &libPaths,
|
const std::vector<std::string> &libPaths,
|
||||||
const std::string &outputPath) override;
|
const std::string &outputPath) override;
|
||||||
@ -661,16 +658,6 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
|||||||
HttpResponsePtr handleSessionForResponse(const HttpRequestImplPtr &req,
|
HttpResponsePtr handleSessionForResponse(const HttpRequestImplPtr &req,
|
||||||
const HttpResponsePtr &resp);
|
const HttpResponsePtr &resp);
|
||||||
|
|
||||||
HttpAppFramework &setBeforeListenSockOptCallback(
|
|
||||||
std::function<void(int)> cb) override;
|
|
||||||
HttpAppFramework &setAfterAcceptSockOptCallback(
|
|
||||||
std::function<void(int)> cb) override;
|
|
||||||
HttpAppFramework &setConnectionCallback(
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> cb) override;
|
|
||||||
|
|
||||||
HttpAppFramework &enableRequestStream(bool enable) override;
|
|
||||||
bool isRequestStreamEnabled() const override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void registerHttpController(const std::string &pathPattern,
|
void registerHttpController(const std::string &pathPattern,
|
||||||
const internal::HttpBinderBasePtr &binder,
|
const internal::HttpBinderBasePtr &binder,
|
||||||
@ -709,7 +696,7 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
|||||||
size_t threadNum_{1};
|
size_t threadNum_{1};
|
||||||
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
|
std::unique_ptr<trantor::EventLoopThreadPool> ioLoopThreadPool_;
|
||||||
|
|
||||||
#if !defined(_WIN32) && !TARGET_OS_IOS
|
#ifndef _WIN32
|
||||||
std::vector<std::string> libFilePaths_;
|
std::vector<std::string> libFilePaths_;
|
||||||
std::string libFileOutputPath_;
|
std::string libFileOutputPath_;
|
||||||
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;
|
std::unique_ptr<SharedLibManager> sharedLibManagerPtr_;
|
||||||
@ -761,8 +748,6 @@ class HttpAppFrameworkImpl final : public HttpAppFramework
|
|||||||
|
|
||||||
ExceptionHandler exceptionHandler_{defaultExceptionHandler};
|
ExceptionHandler exceptionHandler_{defaultExceptionHandler};
|
||||||
bool enableCompressedRequest_{false};
|
bool enableCompressedRequest_{false};
|
||||||
|
|
||||||
bool enableRequestStream_{false};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace drogon
|
} // namespace drogon
|
||||||
|
@ -36,6 +36,7 @@ void HttpClientImpl::createTcpClient()
|
|||||||
LOG_TRACE << "New TcpClient," << serverAddr_.toIpPort();
|
LOG_TRACE << "New TcpClient," << serverAddr_.toIpPort();
|
||||||
tcpClientPtr_ =
|
tcpClientPtr_ =
|
||||||
std::make_shared<trantor::TcpClient>(loop_, serverAddr_, "httpClient");
|
std::make_shared<trantor::TcpClient>(loop_, serverAddr_, "httpClient");
|
||||||
|
Version version = targetHttpVersion_.value_or(Version::kHttp2);
|
||||||
|
|
||||||
if (useSSL_ && utils::supportsTls())
|
if (useSSL_ && utils::supportsTls())
|
||||||
{
|
{
|
||||||
@ -48,6 +49,8 @@ void HttpClientImpl::createTcpClient()
|
|||||||
.setConfCmds(sslConfCmds_)
|
.setConfCmds(sslConfCmds_)
|
||||||
.setCertPath(clientCertPath_)
|
.setCertPath(clientCertPath_)
|
||||||
.setKeyPath(clientKeyPath_);
|
.setKeyPath(clientKeyPath_);
|
||||||
|
if (version == Version::kHttp2)
|
||||||
|
policy->setAlpnProtocols({"h2", "http/1.1"});
|
||||||
tcpClientPtr_->enableSSL(std::move(policy));
|
tcpClientPtr_->enableSSL(std::move(policy));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,55 +63,118 @@ void HttpClientImpl::createTcpClient()
|
|||||||
if (thisPtr->sockOptCallback_)
|
if (thisPtr->sockOptCallback_)
|
||||||
thisPtr->sockOptCallback_(fd);
|
thisPtr->sockOptCallback_(fd);
|
||||||
});
|
});
|
||||||
tcpClientPtr_->setConnectionCallback(
|
tcpClientPtr_->setConnectionCallback([weakPtr](
|
||||||
[weakPtr](const trantor::TcpConnectionPtr &connPtr) {
|
const trantor::TcpConnectionPtr
|
||||||
auto thisPtr = weakPtr.lock();
|
&connPtr) {
|
||||||
if (!thisPtr)
|
auto thisPtr = weakPtr.lock();
|
||||||
return;
|
if (!thisPtr)
|
||||||
if (connPtr->connected())
|
return;
|
||||||
|
if (connPtr->connected())
|
||||||
|
{
|
||||||
|
// send request;
|
||||||
|
LOG_TRACE << "Connection established!";
|
||||||
|
|
||||||
|
auto protocol = connPtr->applicationProtocol();
|
||||||
|
if (protocol == "http/1.1")
|
||||||
{
|
{
|
||||||
connPtr->setContext(
|
LOG_TRACE << "Select http/1.1 protocol";
|
||||||
std::make_shared<HttpResponseParser>(connPtr));
|
thisPtr->transport_ =
|
||||||
// send request;
|
std::make_unique<Http1xTransport>(connPtr,
|
||||||
LOG_TRACE << "Connection established!";
|
Version::kHttp11,
|
||||||
while (thisPtr->pipeliningCallbacks_.size() <=
|
&thisPtr->bytesSent_,
|
||||||
thisPtr->pipeliningDepth_ &&
|
&thisPtr->bytesReceived_);
|
||||||
!thisPtr->requestsBuffer_.empty())
|
thisPtr->httpVersion_ = Version::kHttp11;
|
||||||
{
|
}
|
||||||
thisPtr->sendReq(connPtr,
|
else if (protocol == "h2")
|
||||||
thisPtr->requestsBuffer_.front().first);
|
{
|
||||||
thisPtr->pipeliningCallbacks_.push(
|
LOG_TRACE << "Select http/2 protocol";
|
||||||
std::move(thisPtr->requestsBuffer_.front()));
|
thisPtr->transport_ =
|
||||||
thisPtr->requestsBuffer_.pop_front();
|
std::make_unique<Http2Transport>(connPtr,
|
||||||
}
|
&thisPtr->bytesSent_,
|
||||||
|
&thisPtr->bytesReceived_);
|
||||||
|
thisPtr->httpVersion_ = Version::kHttp2;
|
||||||
|
}
|
||||||
|
else if (protocol.empty())
|
||||||
|
{
|
||||||
|
// Either we are not using TLS or the server does not support
|
||||||
|
// ALPN. Use HTTP/1.1 if not specified otherwise.
|
||||||
|
bool force1_0 = thisPtr->targetHttpVersion_.value_or(
|
||||||
|
Version::kUnknown) == Version::kHttp10;
|
||||||
|
auto version = force1_0 ? Version::kHttp10 : Version::kHttp11;
|
||||||
|
thisPtr->httpVersion_ = version;
|
||||||
|
thisPtr->transport_ =
|
||||||
|
std::make_unique<Http1xTransport>(connPtr,
|
||||||
|
version,
|
||||||
|
&thisPtr->bytesSent_,
|
||||||
|
&thisPtr->bytesReceived_);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_TRACE << "connection disconnect";
|
LOG_ERROR << "Unknown protocol " << protocol
|
||||||
auto responseParser = connPtr->getContext<HttpResponseParser>();
|
<< " selected by server for HTTP";
|
||||||
if (responseParser && responseParser->parseResponseOnClose() &&
|
thisPtr->onError(ReqResult::BadResponse);
|
||||||
responseParser->gotAll())
|
return;
|
||||||
{
|
|
||||||
auto &firstReq = thisPtr->pipeliningCallbacks_.front();
|
|
||||||
if (firstReq.first->method() == Head)
|
|
||||||
{
|
|
||||||
responseParser->setForHeadMethod();
|
|
||||||
}
|
|
||||||
auto resp = responseParser->responseImpl();
|
|
||||||
responseParser->reset();
|
|
||||||
// temporary fix of dead tcpClientPtr_
|
|
||||||
// TODO: fix HttpResponseParser when content-length absence
|
|
||||||
thisPtr->tcpClientPtr_.reset();
|
|
||||||
thisPtr->handleResponse(resp, std::move(firstReq), connPtr);
|
|
||||||
if (!thisPtr->requestsBuffer_.empty())
|
|
||||||
{
|
|
||||||
thisPtr->createTcpClient();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
thisPtr->onError(ReqResult::NetworkFailure);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
assert(thisPtr->httpVersion_.has_value());
|
||||||
|
assert(thisPtr->transport_);
|
||||||
|
thisPtr->transport_->setRespCallback(
|
||||||
|
[weakPtr](const HttpResponseImplPtr &resp,
|
||||||
|
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
||||||
|
const trantor::TcpConnectionPtr &connPtr) {
|
||||||
|
auto thisPtr = weakPtr.lock();
|
||||||
|
if (!thisPtr)
|
||||||
|
return;
|
||||||
|
thisPtr->handleResponse(resp, std::move(reqAndCb), connPtr);
|
||||||
|
});
|
||||||
|
thisPtr->transport_->setErrorCallback([weakPtr](ReqResult result) {
|
||||||
|
auto thisPtr = weakPtr.lock();
|
||||||
|
if (!thisPtr)
|
||||||
|
return;
|
||||||
|
thisPtr->onError(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
size_t maxSendReq = (*(thisPtr->httpVersion_) == Version::kHttp2)
|
||||||
|
? size_t{0xfffffff}
|
||||||
|
: thisPtr->pipeliningDepth_;
|
||||||
|
if (maxSendReq == 0)
|
||||||
|
maxSendReq = 1;
|
||||||
|
while (!thisPtr->requestsBuffer_.empty() &&
|
||||||
|
thisPtr->transport_->requestsInFlight() < maxSendReq)
|
||||||
|
{
|
||||||
|
auto &reqAndCb = thisPtr->requestsBuffer_.front();
|
||||||
|
thisPtr->transport_->sendRequestInLoop(reqAndCb.first,
|
||||||
|
std::move(
|
||||||
|
reqAndCb.second));
|
||||||
|
thisPtr->requestsBuffer_.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_TRACE << "connection disconnect";
|
||||||
|
// TODO: Make sure the sequence of handling is correct
|
||||||
|
bool isUnexpected = false;
|
||||||
|
if (thisPtr->transport_)
|
||||||
|
{
|
||||||
|
isUnexpected = thisPtr->transport_->handleConnectionClose();
|
||||||
|
}
|
||||||
|
if (isUnexpected)
|
||||||
|
{
|
||||||
|
thisPtr->onError(ReqResult::NetworkFailure);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporary fix of dead tcpClientPtr_
|
||||||
|
// TODO: fix HttpResponseParser when content-length absence
|
||||||
|
// TODO: HTTP/2 transport also relies on this behavior to reconnect
|
||||||
|
// when running out of stream IDs
|
||||||
|
thisPtr->tcpClientPtr_.reset();
|
||||||
|
if (!thisPtr->requestsBuffer_.empty())
|
||||||
|
{
|
||||||
|
thisPtr->createTcpClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
tcpClientPtr_->setConnectionErrorCallback([weakPtr]() {
|
tcpClientPtr_->setConnectionErrorCallback([weakPtr]() {
|
||||||
auto thisPtr = weakPtr.lock();
|
auto thisPtr = weakPtr.lock();
|
||||||
if (!thisPtr)
|
if (!thisPtr)
|
||||||
@ -148,20 +214,26 @@ HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
|
|||||||
const trantor::InetAddress &addr,
|
const trantor::InetAddress &addr,
|
||||||
bool useSSL,
|
bool useSSL,
|
||||||
bool useOldTLS,
|
bool useOldTLS,
|
||||||
bool validateCert)
|
bool validateCert,
|
||||||
|
std::optional<Version> targetVersion)
|
||||||
: loop_(loop),
|
: loop_(loop),
|
||||||
serverAddr_(addr),
|
serverAddr_(addr),
|
||||||
useSSL_(useSSL),
|
useSSL_(useSSL),
|
||||||
validateCert_(validateCert),
|
validateCert_(validateCert),
|
||||||
useOldTLS_(useOldTLS)
|
useOldTLS_(useOldTLS),
|
||||||
|
targetHttpVersion_(targetVersion)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
|
HttpClientImpl::HttpClientImpl(trantor::EventLoop *loop,
|
||||||
const std::string &hostString,
|
const std::string &hostString,
|
||||||
bool useOldTLS,
|
bool useOldTLS,
|
||||||
bool validateCert)
|
bool validateCert,
|
||||||
: loop_(loop), validateCert_(validateCert), useOldTLS_(useOldTLS)
|
std::optional<Version> targetVersion)
|
||||||
|
: loop_(loop),
|
||||||
|
validateCert_(validateCert),
|
||||||
|
useOldTLS_(useOldTLS),
|
||||||
|
targetHttpVersion_(targetVersion)
|
||||||
{
|
{
|
||||||
auto lowerHost = hostString;
|
auto lowerHost = hostString;
|
||||||
std::transform(lowerHost.begin(),
|
std::transform(lowerHost.begin(),
|
||||||
@ -319,35 +391,35 @@ void HttpClientImpl::sendRequestInLoop(const HttpRequestPtr &req,
|
|||||||
shared_from_this(),
|
shared_from_this(),
|
||||||
req);
|
req);
|
||||||
|
|
||||||
loop_->runAfter(
|
// TODO: Cancel the timer when the request is finished.
|
||||||
timeout,
|
loop_->runAfter(timeout,
|
||||||
[weakCallbackBackPtr =
|
[weakCallbackBackPtr = std::weak_ptr<RequestCallbackParams>(
|
||||||
std::weak_ptr<RequestCallbackParams>(callbackParamsPtr)] {
|
callbackParamsPtr)] {
|
||||||
auto callbackParamsPtr = weakCallbackBackPtr.lock();
|
auto callbackParamsPtr = weakCallbackBackPtr.lock();
|
||||||
if (callbackParamsPtr != nullptr)
|
if (callbackParamsPtr == nullptr)
|
||||||
{
|
return;
|
||||||
auto &thisPtr = callbackParamsPtr->clientPtr;
|
auto &thisPtr = callbackParamsPtr->clientPtr;
|
||||||
if (callbackParamsPtr->timeoutFlag)
|
if (callbackParamsPtr->timeoutFlag)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackParamsPtr->timeoutFlag = true;
|
callbackParamsPtr->timeoutFlag = true;
|
||||||
|
|
||||||
for (auto iter = thisPtr->requestsBuffer_.begin();
|
for (auto iter = thisPtr->requestsBuffer_.begin();
|
||||||
iter != thisPtr->requestsBuffer_.end();
|
iter != thisPtr->requestsBuffer_.end();
|
||||||
++iter)
|
++iter)
|
||||||
{
|
{
|
||||||
if (iter->first == callbackParamsPtr->requestPtr)
|
if (iter->first == callbackParamsPtr->requestPtr)
|
||||||
{
|
{
|
||||||
thisPtr->requestsBuffer_.erase(iter);
|
thisPtr->requestsBuffer_.erase(iter);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(callbackParamsPtr->callback)(ReqResult::Timeout, nullptr);
|
(callbackParamsPtr->callback)(ReqResult::Timeout,
|
||||||
}
|
nullptr);
|
||||||
});
|
});
|
||||||
sendRequestInLoop(req,
|
sendRequestInLoop(req,
|
||||||
[callbackParamsPtr](ReqResult r,
|
[callbackParamsPtr](ReqResult r,
|
||||||
const HttpResponsePtr &resp) {
|
const HttpResponsePtr &resp) {
|
||||||
@ -355,6 +427,7 @@ void HttpClientImpl::sendRequestInLoop(const HttpRequestPtr &req,
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackParamsPtr->timeoutFlag = true;
|
callbackParamsPtr->timeoutFlag = true;
|
||||||
(callbackParamsPtr->callback)(r, resp);
|
(callbackParamsPtr->callback)(r, resp);
|
||||||
});
|
});
|
||||||
@ -422,10 +495,11 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
|||||||
requestsBuffer_.push_back(
|
requestsBuffer_.push_back(
|
||||||
{req,
|
{req,
|
||||||
[thisPtr = shared_from_this(),
|
[thisPtr = shared_from_this(),
|
||||||
callbackPtr](ReqResult result, const HttpResponsePtr &response) {
|
callbackPtr =
|
||||||
|
std::move(callbackPtr)](ReqResult result,
|
||||||
|
const HttpResponsePtr &response) {
|
||||||
(*callbackPtr)(result, response);
|
(*callbackPtr)(result, response);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
if (domain_.empty() || !isDomainName_)
|
if (domain_.empty() || !isDomainName_)
|
||||||
{
|
{
|
||||||
// Valid ip address, no domain, connect directly
|
// Valid ip address, no domain, connect directly
|
||||||
@ -436,8 +510,7 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
|||||||
// No ip address and no domain, respond with BadServerAddress
|
// No ip address and no domain, respond with BadServerAddress
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
requestsBuffer_.pop_front();
|
callback(ReqResult::BadServerAddress, nullptr);
|
||||||
(*callbackPtr)(ReqResult::BadServerAddress, nullptr);
|
|
||||||
assert(requestsBuffer_.empty());
|
assert(requestsBuffer_.empty());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -504,19 +577,18 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
|||||||
}});
|
}});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
assert(transport_ != nullptr);
|
||||||
|
assert(httpVersion_.has_value());
|
||||||
|
|
||||||
|
size_t maxSendReq = (*httpVersion_ == Version::kHttp2) ? size_t{0xfffffffff}
|
||||||
|
: pipeliningDepth_;
|
||||||
|
if (maxSendReq == 0)
|
||||||
|
maxSendReq = 1;
|
||||||
|
|
||||||
// Connected, send request now
|
// Connected, send request now
|
||||||
if (pipeliningCallbacks_.size() <= pipeliningDepth_ &&
|
if (transport_->requestsInFlight() <= maxSendReq && requestsBuffer_.empty())
|
||||||
requestsBuffer_.empty())
|
|
||||||
{
|
{
|
||||||
sendReq(connPtr, req);
|
transport_->sendRequestInLoop(req, std::move(callback));
|
||||||
pipeliningCallbacks_.push(
|
|
||||||
{req,
|
|
||||||
[thisPtr,
|
|
||||||
callback = std::move(callback)](ReqResult result,
|
|
||||||
const HttpResponsePtr &response) {
|
|
||||||
callback(result, response);
|
|
||||||
}});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -530,25 +602,11 @@ void HttpClientImpl::sendRequestInLoop(const drogon::HttpRequestPtr &req,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr,
|
|
||||||
const HttpRequestPtr &req)
|
|
||||||
{
|
|
||||||
trantor::MsgBuffer buffer;
|
|
||||||
assert(req);
|
|
||||||
auto implPtr = static_cast<HttpRequestImpl *>(req.get());
|
|
||||||
implPtr->appendToBuffer(&buffer);
|
|
||||||
LOG_TRACE << "Send request:"
|
|
||||||
<< std::string(buffer.peek(), buffer.readableBytes());
|
|
||||||
bytesSent_ += buffer.readableBytes();
|
|
||||||
connPtr->send(std::move(buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpClientImpl::handleResponse(
|
void HttpClientImpl::handleResponse(
|
||||||
const HttpResponseImplPtr &resp,
|
const HttpResponseImplPtr &resp,
|
||||||
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
||||||
const trantor::TcpConnectionPtr &connPtr)
|
const trantor::TcpConnectionPtr &connPtr)
|
||||||
{
|
{
|
||||||
assert(!pipeliningCallbacks_.empty());
|
|
||||||
auto &type = resp->getHeaderBy("content-type");
|
auto &type = resp->getHeaderBy("content-type");
|
||||||
auto &coding = resp->getHeaderBy("content-encoding");
|
auto &coding = resp->getHeaderBy("content-encoding");
|
||||||
if (coding == "gzip")
|
if (coding == "gzip")
|
||||||
@ -561,8 +619,12 @@ void HttpClientImpl::handleResponse(
|
|||||||
resp->brDecompress();
|
resp->brDecompress();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (type.find("application/json") != std::string::npos)
|
||||||
|
{
|
||||||
|
resp->parseJson();
|
||||||
|
}
|
||||||
|
resp->setPeerCertificate(connPtr->peerCertificate());
|
||||||
auto cb = std::move(reqAndCb);
|
auto cb = std::move(reqAndCb);
|
||||||
pipeliningCallbacks_.pop();
|
|
||||||
handleCookies(resp);
|
handleCookies(resp);
|
||||||
cb.second(ReqResult::Ok, resp);
|
cb.second(ReqResult::Ok, resp);
|
||||||
|
|
||||||
@ -575,13 +637,16 @@ void HttpClientImpl::handleResponse(
|
|||||||
if (!requestsBuffer_.empty())
|
if (!requestsBuffer_.empty())
|
||||||
{
|
{
|
||||||
auto &reqAndCallback = requestsBuffer_.front();
|
auto &reqAndCallback = requestsBuffer_.front();
|
||||||
sendReq(connPtr, reqAndCallback.first);
|
transport_->sendRequestInLoop(reqAndCallback.first,
|
||||||
pipeliningCallbacks_.push(std::move(reqAndCallback));
|
std::move(reqAndCallback.second));
|
||||||
requestsBuffer_.pop_front();
|
requestsBuffer_.pop_front();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (resp->ifCloseConnection() && pipeliningCallbacks_.empty())
|
assert(httpVersion_.has_value());
|
||||||
|
if (resp->ifCloseConnection() &&
|
||||||
|
transport_->requestsInFlight() == 0 &&
|
||||||
|
*httpVersion_ != Version::kHttp2)
|
||||||
{
|
{
|
||||||
tcpClientPtr_.reset();
|
tcpClientPtr_.reset();
|
||||||
}
|
}
|
||||||
@ -589,56 +654,15 @@ void HttpClientImpl::handleResponse(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
while (!pipeliningCallbacks_.empty())
|
transport_->onError(ReqResult::NetworkFailure);
|
||||||
{
|
|
||||||
auto cb = std::move(pipeliningCallbacks_.front());
|
|
||||||
pipeliningCallbacks_.pop();
|
|
||||||
cb.second(ReqResult::NetworkFailure, nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
|
void HttpClientImpl::onRecvMessage(const trantor::TcpConnectionPtr &connPtr,
|
||||||
trantor::MsgBuffer *msg)
|
trantor::MsgBuffer *msg)
|
||||||
{
|
{
|
||||||
auto responseParser = connPtr->getContext<HttpResponseParser>();
|
assert(transport_ != nullptr);
|
||||||
|
transport_->onRecvMessage(connPtr, msg);
|
||||||
// LOG_TRACE << "###:" << msg->readableBytes();
|
|
||||||
auto msgSize = msg->readableBytes();
|
|
||||||
while (msg->readableBytes() > 0)
|
|
||||||
{
|
|
||||||
if (pipeliningCallbacks_.empty())
|
|
||||||
{
|
|
||||||
LOG_ERROR << "More responses than expected!";
|
|
||||||
connPtr->shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto &firstReq = pipeliningCallbacks_.front();
|
|
||||||
if (firstReq.first->method() == Head)
|
|
||||||
{
|
|
||||||
responseParser->setForHeadMethod();
|
|
||||||
}
|
|
||||||
if (!responseParser->parseResponse(msg))
|
|
||||||
{
|
|
||||||
onError(ReqResult::BadResponse);
|
|
||||||
bytesReceived_ += (msgSize - msg->readableBytes());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (responseParser->gotAll())
|
|
||||||
{
|
|
||||||
auto resp = responseParser->responseImpl();
|
|
||||||
resp->setPeerCertificate(connPtr->peerCertificate());
|
|
||||||
responseParser->reset();
|
|
||||||
bytesReceived_ += (msgSize - msg->readableBytes());
|
|
||||||
msgSize = msg->readableBytes();
|
|
||||||
handleResponse(resp, std::move(firstReq), connPtr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bytesReceived_ += (msgSize - msg->readableBytes());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
|
HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
|
||||||
@ -646,7 +670,8 @@ HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
|
|||||||
bool useSSL,
|
bool useSSL,
|
||||||
trantor::EventLoop *loop,
|
trantor::EventLoop *loop,
|
||||||
bool useOldTLS,
|
bool useOldTLS,
|
||||||
bool validateCert)
|
bool validateCert,
|
||||||
|
std::optional<Version> targetVersion)
|
||||||
{
|
{
|
||||||
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
|
bool isIpv6 = ip.find(':') == std::string::npos ? false : true;
|
||||||
return std::make_shared<HttpClientImpl>(
|
return std::make_shared<HttpClientImpl>(
|
||||||
@ -654,29 +679,28 @@ HttpClientPtr HttpClient::newHttpClient(const std::string &ip,
|
|||||||
trantor::InetAddress(ip, port, isIpv6),
|
trantor::InetAddress(ip, port, isIpv6),
|
||||||
useSSL,
|
useSSL,
|
||||||
useOldTLS,
|
useOldTLS,
|
||||||
validateCert);
|
validateCert,
|
||||||
|
targetVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClientPtr HttpClient::newHttpClient(const std::string &hostString,
|
HttpClientPtr HttpClient::newHttpClient(const std::string &hostString,
|
||||||
trantor::EventLoop *loop,
|
trantor::EventLoop *loop,
|
||||||
bool useOldTLS,
|
bool useOldTLS,
|
||||||
bool validateCert)
|
bool validateCert,
|
||||||
|
std::optional<Version> targetVersion)
|
||||||
{
|
{
|
||||||
return std::make_shared<HttpClientImpl>(
|
return std::make_shared<HttpClientImpl>(
|
||||||
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
|
loop == nullptr ? HttpAppFrameworkImpl::instance().getLoop() : loop,
|
||||||
hostString,
|
hostString,
|
||||||
useOldTLS,
|
useOldTLS,
|
||||||
validateCert);
|
validateCert,
|
||||||
|
targetVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpClientImpl::onError(ReqResult result)
|
void HttpClientImpl::onError(ReqResult result)
|
||||||
{
|
{
|
||||||
while (!pipeliningCallbacks_.empty())
|
if (transport_)
|
||||||
{
|
transport_->onError(result);
|
||||||
auto cb = std::move(pipeliningCallbacks_.front());
|
|
||||||
pipeliningCallbacks_.pop();
|
|
||||||
cb.second(result, nullptr);
|
|
||||||
}
|
|
||||||
while (!requestsBuffer_.empty())
|
while (!requestsBuffer_.empty())
|
||||||
{
|
{
|
||||||
auto cb = std::move(requestsBuffer_.front().second);
|
auto cb = std::move(requestsBuffer_.front().second);
|
||||||
|
@ -19,17 +19,18 @@
|
|||||||
#include <trantor/net/EventLoop.h>
|
#include <trantor/net/EventLoop.h>
|
||||||
#include <trantor/net/Resolver.h>
|
#include <trantor/net/Resolver.h>
|
||||||
#include <trantor/net/TcpClient.h>
|
#include <trantor/net/TcpClient.h>
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
|
||||||
#include <future>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "impl_forwards.h"
|
#include "impl_forwards.h"
|
||||||
|
#include "Http2Transport.h"
|
||||||
|
#include "HttpTransport.h"
|
||||||
|
#include "Http1xTransport.h"
|
||||||
|
|
||||||
namespace drogon
|
namespace drogon
|
||||||
{
|
{
|
||||||
|
|
||||||
class HttpClientImpl final : public HttpClient,
|
class HttpClientImpl final : public HttpClient,
|
||||||
public std::enable_shared_from_this<HttpClientImpl>
|
public std::enable_shared_from_this<HttpClientImpl>
|
||||||
{
|
{
|
||||||
@ -38,11 +39,13 @@ class HttpClientImpl final : public HttpClient,
|
|||||||
const trantor::InetAddress &addr,
|
const trantor::InetAddress &addr,
|
||||||
bool useSSL = false,
|
bool useSSL = false,
|
||||||
bool useOldTLS = false,
|
bool useOldTLS = false,
|
||||||
bool validateCert = true);
|
bool validateCert = true,
|
||||||
|
std::optional<Version> httpVersion = std::nullopt);
|
||||||
HttpClientImpl(trantor::EventLoop *loop,
|
HttpClientImpl(trantor::EventLoop *loop,
|
||||||
const std::string &hostString,
|
const std::string &hostString,
|
||||||
bool useOldTLS = false,
|
bool useOldTLS = false,
|
||||||
bool validateCert = true);
|
bool validateCert = true,
|
||||||
|
std::optional<Version> httpVersion = std::nullopt);
|
||||||
void sendRequest(const HttpRequestPtr &req,
|
void sendRequest(const HttpRequestPtr &req,
|
||||||
const HttpReqCallback &callback,
|
const HttpReqCallback &callback,
|
||||||
double timeout = 0) override;
|
double timeout = 0) override;
|
||||||
@ -118,19 +121,9 @@ class HttpClientImpl final : public HttpClient,
|
|||||||
sockOptCallback_ = std::move(cb);
|
sockOptCallback_ = std::move(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t requestsBufferSize() override
|
std::optional<Version> protocolVersion() const override
|
||||||
{
|
{
|
||||||
if (loop_->isInLoopThread())
|
return httpVersion_;
|
||||||
{
|
|
||||||
return requestsBuffer_.size();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::promise<std::size_t> bufferSize;
|
|
||||||
loop_->queueInLoop(
|
|
||||||
[&] { bufferSize.set_value(requestsBuffer_.size()); });
|
|
||||||
return bufferSize.get_future().get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -139,8 +132,6 @@ class HttpClientImpl final : public HttpClient,
|
|||||||
trantor::InetAddress serverAddr_;
|
trantor::InetAddress serverAddr_;
|
||||||
bool useSSL_;
|
bool useSSL_;
|
||||||
bool validateCert_;
|
bool validateCert_;
|
||||||
void sendReq(const trantor::TcpConnectionPtr &connPtr,
|
|
||||||
const HttpRequestPtr &req);
|
|
||||||
void sendRequestInLoop(const HttpRequestPtr &req,
|
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||||
HttpReqCallback &&callback);
|
HttpReqCallback &&callback);
|
||||||
void sendRequestInLoop(const HttpRequestPtr &req,
|
void sendRequestInLoop(const HttpRequestPtr &req,
|
||||||
@ -151,7 +142,6 @@ class HttpClientImpl final : public HttpClient,
|
|||||||
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
std::pair<HttpRequestPtr, HttpReqCallback> &&reqAndCb,
|
||||||
const trantor::TcpConnectionPtr &connPtr);
|
const trantor::TcpConnectionPtr &connPtr);
|
||||||
void createTcpClient();
|
void createTcpClient();
|
||||||
std::queue<std::pair<HttpRequestPtr, HttpReqCallback>> pipeliningCallbacks_;
|
|
||||||
std::list<std::pair<HttpRequestPtr, HttpReqCallback>> requestsBuffer_;
|
std::list<std::pair<HttpRequestPtr, HttpReqCallback>> requestsBuffer_;
|
||||||
void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
|
void onRecvMessage(const trantor::TcpConnectionPtr &, trantor::MsgBuffer *);
|
||||||
void onError(ReqResult result);
|
void onError(ReqResult result);
|
||||||
@ -170,6 +160,9 @@ class HttpClientImpl final : public HttpClient,
|
|||||||
std::string clientCertPath_;
|
std::string clientCertPath_;
|
||||||
std::string clientKeyPath_;
|
std::string clientKeyPath_;
|
||||||
std::function<void(int)> sockOptCallback_;
|
std::function<void(int)> sockOptCallback_;
|
||||||
|
std::unique_ptr<HttpTransport> transport_;
|
||||||
|
std::optional<Version> targetHttpVersion_;
|
||||||
|
std::optional<Version> httpVersion_;
|
||||||
};
|
};
|
||||||
|
|
||||||
using HttpClientImplPtr = std::shared_ptr<HttpClientImpl>;
|
using HttpClientImplPtr = std::shared_ptr<HttpClientImpl>;
|
||||||
|
@ -39,12 +39,6 @@ class HttpControllerBinder : public ControllerBinderBase
|
|||||||
const HttpRequestImplPtr &req,
|
const HttpRequestImplPtr &req,
|
||||||
std::function<void(const HttpResponsePtr &)> &&callback) const override;
|
std::function<void(const HttpResponsePtr &)> &&callback) const override;
|
||||||
|
|
||||||
bool isStreamHandler() const override
|
|
||||||
{
|
|
||||||
assert(binderPtr_);
|
|
||||||
return binderPtr_->isStreamHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal::HttpBinderBasePtr binderPtr_;
|
internal::HttpBinderBasePtr binderPtr_;
|
||||||
std::vector<size_t> parameterPlaces_;
|
std::vector<size_t> parameterPlaces_;
|
||||||
std::vector<std::pair<std::string, size_t>> queryParametersPlaces_;
|
std::vector<std::pair<std::string, size_t>> queryParametersPlaces_;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
#include <drogon/UploadFile.h>
|
#include <drogon/UploadFile.h>
|
||||||
#include <drogon/utils/Utilities.h>
|
#include <drogon/utils/Utilities.h>
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
using namespace drogon;
|
using namespace drogon;
|
||||||
|
|
||||||
HttpFileUploadRequest::HttpFileUploadRequest(
|
HttpFileUploadRequest::HttpFileUploadRequest(
|
||||||
@ -25,7 +27,74 @@ HttpFileUploadRequest::HttpFileUploadRequest(
|
|||||||
files_(files)
|
files_(files)
|
||||||
{
|
{
|
||||||
setMethod(drogon::Post);
|
setMethod(drogon::Post);
|
||||||
setVersion(drogon::Version::kHttp11);
|
|
||||||
setContentType("multipart/form-data; boundary=" + boundary_);
|
setContentType("multipart/form-data; boundary=" + boundary_);
|
||||||
contentType_ = CT_MULTIPART_FORM_DATA;
|
contentType_ = CT_MULTIPART_FORM_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void renderMultipart(const HttpFileUploadRequest *mPtr, T &content)
|
||||||
|
{
|
||||||
|
auto &boundary = mPtr->boundary();
|
||||||
|
for (auto ¶m : mPtr->getParameters())
|
||||||
|
{
|
||||||
|
content.append("--");
|
||||||
|
content.append(boundary);
|
||||||
|
content.append("\r\n");
|
||||||
|
content.append("content-disposition: form-data; name=\"");
|
||||||
|
content.append(param.first);
|
||||||
|
content.append("\"\r\n\r\n");
|
||||||
|
content.append(param.second);
|
||||||
|
content.append("\r\n");
|
||||||
|
}
|
||||||
|
for (auto &file : mPtr->files())
|
||||||
|
{
|
||||||
|
content.append("--");
|
||||||
|
content.append(boundary);
|
||||||
|
content.append("\r\n");
|
||||||
|
content.append("content-disposition: form-data; name=\"");
|
||||||
|
content.append(file.itemName());
|
||||||
|
content.append("\"; filename=\"");
|
||||||
|
content.append(file.fileName());
|
||||||
|
content.append("\"");
|
||||||
|
if (file.contentType() != CT_NONE)
|
||||||
|
{
|
||||||
|
content.append("\r\n");
|
||||||
|
|
||||||
|
auto &type = contentTypeToMime(file.contentType());
|
||||||
|
content.append("content-type: ");
|
||||||
|
content.append(type.data(), type.length());
|
||||||
|
}
|
||||||
|
content.append("\r\n\r\n");
|
||||||
|
std::ifstream infile(utils::toNativePath(file.path()),
|
||||||
|
std::ifstream::binary);
|
||||||
|
if (!infile)
|
||||||
|
{
|
||||||
|
LOG_ERROR << file.path() << " not found";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::streambuf *pbuf = infile.rdbuf();
|
||||||
|
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
||||||
|
pbuf->pubseekoff(0, infile.beg); // rewind
|
||||||
|
std::string str;
|
||||||
|
str.resize(filesize);
|
||||||
|
pbuf->sgetn(&str[0], filesize);
|
||||||
|
content.append(std::move(str));
|
||||||
|
}
|
||||||
|
content.append("\r\n");
|
||||||
|
}
|
||||||
|
content.append("--");
|
||||||
|
content.append(boundary);
|
||||||
|
content.append("--");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpFileUploadRequest::renderMultipartFormData(
|
||||||
|
trantor::MsgBuffer &buffer) const
|
||||||
|
{
|
||||||
|
renderMultipart(this, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpFileUploadRequest::renderMultipartFormData(std::string &buffer) const
|
||||||
|
{
|
||||||
|
renderMultipart(this, buffer);
|
||||||
|
}
|
||||||
|
@ -34,6 +34,9 @@ class HttpFileUploadRequest : public HttpRequestImpl
|
|||||||
|
|
||||||
explicit HttpFileUploadRequest(const std::vector<UploadFile> &files);
|
explicit HttpFileUploadRequest(const std::vector<UploadFile> &files);
|
||||||
|
|
||||||
|
void renderMultipartFormData(trantor::MsgBuffer &buffer) const;
|
||||||
|
void renderMultipartFormData(std::string &buffer) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string boundary_;
|
std::string boundary_;
|
||||||
std::vector<UploadFile> files_;
|
std::vector<UploadFile> files_;
|
||||||
|
@ -96,15 +96,13 @@ void HttpRequestImpl::parseParameters() const
|
|||||||
while (cpos < key.length() &&
|
while (cpos < key.length() &&
|
||||||
isspace(static_cast<unsigned char>(key[cpos])))
|
isspace(static_cast<unsigned char>(key[cpos])))
|
||||||
++cpos;
|
++cpos;
|
||||||
key.remove_prefix(cpos);
|
key = key.substr(cpos);
|
||||||
auto pvalue = coo.substr(epos + 1);
|
auto pvalue = coo.substr(epos + 1);
|
||||||
parameters_[utils::urlDecode(key)] = utils::urlDecode(pvalue);
|
std::string pdecode = utils::urlDecode(pvalue);
|
||||||
|
std::string keydecode = utils::urlDecode(key);
|
||||||
|
parameters_[keydecode] = pdecode;
|
||||||
}
|
}
|
||||||
else
|
value = value.substr(pos + 1);
|
||||||
{
|
|
||||||
parameters_[utils::urlDecode(coo)];
|
|
||||||
}
|
|
||||||
value.remove_prefix(pos + 1);
|
|
||||||
}
|
}
|
||||||
if (value.length() > 0)
|
if (value.length() > 0)
|
||||||
{
|
{
|
||||||
@ -117,13 +115,11 @@ void HttpRequestImpl::parseParameters() const
|
|||||||
while (cpos < key.length() &&
|
while (cpos < key.length() &&
|
||||||
isspace(static_cast<unsigned char>(key[cpos])))
|
isspace(static_cast<unsigned char>(key[cpos])))
|
||||||
++cpos;
|
++cpos;
|
||||||
key.remove_prefix(cpos);
|
key = key.substr(cpos);
|
||||||
auto pvalue = coo.substr(epos + 1);
|
auto pvalue = coo.substr(epos + 1);
|
||||||
parameters_[utils::urlDecode(key)] = utils::urlDecode(pvalue);
|
std::string pdecode = utils::urlDecode(pvalue);
|
||||||
}
|
std::string keydecode = utils::urlDecode(key);
|
||||||
else
|
parameters_[keydecode] = pdecode;
|
||||||
{
|
|
||||||
parameters_[utils::urlDecode(coo)];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,15 +153,13 @@ void HttpRequestImpl::parseParameters() const
|
|||||||
while (cpos < key.length() &&
|
while (cpos < key.length() &&
|
||||||
isspace(static_cast<unsigned char>(key[cpos])))
|
isspace(static_cast<unsigned char>(key[cpos])))
|
||||||
++cpos;
|
++cpos;
|
||||||
key.remove_prefix(cpos);
|
key = key.substr(cpos);
|
||||||
auto pvalue = coo.substr(epos + 1);
|
auto pvalue = coo.substr(epos + 1);
|
||||||
parameters_[utils::urlDecode(key)] = utils::urlDecode(pvalue);
|
std::string pdecode = utils::urlDecode(pvalue);
|
||||||
|
std::string keydecode = utils::urlDecode(key);
|
||||||
|
parameters_[keydecode] = pdecode;
|
||||||
}
|
}
|
||||||
else
|
value = value.substr(pos + 1);
|
||||||
{
|
|
||||||
parameters_[utils::urlDecode(coo)];
|
|
||||||
}
|
|
||||||
value.remove_prefix(pos + 1);
|
|
||||||
}
|
}
|
||||||
if (value.length() > 0)
|
if (value.length() > 0)
|
||||||
{
|
{
|
||||||
@ -178,19 +172,18 @@ void HttpRequestImpl::parseParameters() const
|
|||||||
while (cpos < key.length() &&
|
while (cpos < key.length() &&
|
||||||
isspace(static_cast<unsigned char>(key[cpos])))
|
isspace(static_cast<unsigned char>(key[cpos])))
|
||||||
++cpos;
|
++cpos;
|
||||||
key.remove_prefix(cpos);
|
key = key.substr(cpos);
|
||||||
auto pvalue = coo.substr(epos + 1);
|
auto pvalue = coo.substr(epos + 1);
|
||||||
parameters_[utils::urlDecode(key)] = utils::urlDecode(pvalue);
|
std::string pdecode = utils::urlDecode(pvalue);
|
||||||
}
|
std::string keydecode = utils::urlDecode(key);
|
||||||
else
|
parameters_[keydecode] = pdecode;
|
||||||
{
|
|
||||||
parameters_[utils::urlDecode(coo)];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestImpl::appendToBuffer(trantor::MsgBuffer *output) const
|
void HttpRequestImpl::appendToBuffer(trantor::MsgBuffer *output,
|
||||||
|
Version protoVer) const
|
||||||
{
|
{
|
||||||
switch (method_)
|
switch (method_)
|
||||||
{
|
{
|
||||||
@ -274,11 +267,11 @@ void HttpRequestImpl::appendToBuffer(trantor::MsgBuffer *output) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
output->append(" ");
|
output->append(" ");
|
||||||
if (version_ == Version::kHttp11)
|
if (protoVer == Version::kHttp11)
|
||||||
{
|
{
|
||||||
output->append("HTTP/1.1");
|
output->append("HTTP/1.1");
|
||||||
}
|
}
|
||||||
else if (version_ == Version::kHttp10)
|
else if (protoVer == Version::kHttp10)
|
||||||
{
|
{
|
||||||
output->append("HTTP/1.0");
|
output->append("HTTP/1.0");
|
||||||
}
|
}
|
||||||
@ -293,57 +286,7 @@ void HttpRequestImpl::appendToBuffer(trantor::MsgBuffer *output) const
|
|||||||
auto mReq = dynamic_cast<const HttpFileUploadRequest *>(this);
|
auto mReq = dynamic_cast<const HttpFileUploadRequest *>(this);
|
||||||
if (mReq)
|
if (mReq)
|
||||||
{
|
{
|
||||||
for (auto ¶m : mReq->getParameters())
|
mReq->renderMultipartFormData(content);
|
||||||
{
|
|
||||||
content.append("--");
|
|
||||||
content.append(mReq->boundary());
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append("content-disposition: form-data; name=\"");
|
|
||||||
content.append(param.first);
|
|
||||||
content.append("\"\r\n\r\n");
|
|
||||||
content.append(param.second);
|
|
||||||
content.append("\r\n");
|
|
||||||
}
|
|
||||||
for (auto &file : mReq->files())
|
|
||||||
{
|
|
||||||
content.append("--");
|
|
||||||
content.append(mReq->boundary());
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append("content-disposition: form-data; name=\"");
|
|
||||||
content.append(file.itemName());
|
|
||||||
content.append("\"; filename=\"");
|
|
||||||
content.append(file.fileName());
|
|
||||||
content.append("\"");
|
|
||||||
if (file.contentType() != CT_NONE)
|
|
||||||
{
|
|
||||||
content.append("\r\n");
|
|
||||||
|
|
||||||
auto &type = contentTypeToMime(file.contentType());
|
|
||||||
content.append("content-type: ");
|
|
||||||
content.append(type.data(), type.length());
|
|
||||||
}
|
|
||||||
content.append("\r\n\r\n");
|
|
||||||
std::ifstream infile(utils::toNativePath(file.path()),
|
|
||||||
std::ifstream::binary);
|
|
||||||
if (!infile)
|
|
||||||
{
|
|
||||||
LOG_ERROR << file.path() << " not found";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::streambuf *pbuf = infile.rdbuf();
|
|
||||||
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
|
||||||
pbuf->pubseekoff(0, infile.beg); // rewind
|
|
||||||
std::string str;
|
|
||||||
str.resize(filesize);
|
|
||||||
pbuf->sgetn(&str[0], filesize);
|
|
||||||
content.append(std::move(str));
|
|
||||||
}
|
|
||||||
content.append("\r\n");
|
|
||||||
}
|
|
||||||
content.append("--");
|
|
||||||
content.append(mReq->boundary());
|
|
||||||
content.append("--");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert(!(!content.empty() && !content_.empty()));
|
assert(!(!content.empty() && !content_.empty()));
|
||||||
@ -514,7 +457,6 @@ HttpRequestPtr HttpRequest::newHttpRequest()
|
|||||||
{
|
{
|
||||||
auto req = std::make_shared<HttpRequestImpl>(nullptr);
|
auto req = std::make_shared<HttpRequestImpl>(nullptr);
|
||||||
req->setMethod(drogon::Get);
|
req->setMethod(drogon::Get);
|
||||||
req->setVersion(drogon::Version::kHttp11);
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,7 +464,6 @@ HttpRequestPtr HttpRequest::newHttpFormPostRequest()
|
|||||||
{
|
{
|
||||||
auto req = std::make_shared<HttpRequestImpl>(nullptr);
|
auto req = std::make_shared<HttpRequestImpl>(nullptr);
|
||||||
req->setMethod(drogon::Post);
|
req->setMethod(drogon::Post);
|
||||||
req->setVersion(drogon::Version::kHttp11);
|
|
||||||
req->contentType_ = CT_APPLICATION_X_FORM;
|
req->contentType_ = CT_APPLICATION_X_FORM;
|
||||||
req->flagForParsingContentType_ = true;
|
req->flagForParsingContentType_ = true;
|
||||||
return req;
|
return req;
|
||||||
@ -548,7 +489,6 @@ HttpRequestPtr HttpRequest::newHttpJsonRequest(const Json::Value &data)
|
|||||||
});
|
});
|
||||||
auto req = std::make_shared<HttpRequestImpl>(nullptr);
|
auto req = std::make_shared<HttpRequestImpl>(nullptr);
|
||||||
req->setMethod(drogon::Get);
|
req->setMethod(drogon::Get);
|
||||||
req->setVersion(drogon::Version::kHttp11);
|
|
||||||
req->contentType_ = CT_APPLICATION_JSON;
|
req->contentType_ = CT_APPLICATION_JSON;
|
||||||
req->setContent(writeString(builder, data));
|
req->setContent(writeString(builder, data));
|
||||||
req->flagForParsingContentType_ = true;
|
req->flagForParsingContentType_ = true;
|
||||||
@ -575,8 +515,6 @@ void HttpRequestImpl::swap(HttpRequestImpl &that) noexcept
|
|||||||
swap(query_, that.query_);
|
swap(query_, that.query_);
|
||||||
swap(headers_, that.headers_);
|
swap(headers_, that.headers_);
|
||||||
swap(cookies_, that.cookies_);
|
swap(cookies_, that.cookies_);
|
||||||
swap(contentLengthHeaderValue_, that.contentLengthHeaderValue_);
|
|
||||||
swap(realContentLength_, that.realContentLength_);
|
|
||||||
swap(parameters_, that.parameters_);
|
swap(parameters_, that.parameters_);
|
||||||
swap(jsonPtr_, that.jsonPtr_);
|
swap(jsonPtr_, that.jsonPtr_);
|
||||||
swap(sessionPtr_, that.sessionPtr_);
|
swap(sessionPtr_, that.sessionPtr_);
|
||||||
@ -594,13 +532,6 @@ void HttpRequestImpl::swap(HttpRequestImpl &that) noexcept
|
|||||||
swap(flagForParsingContentType_, that.flagForParsingContentType_);
|
swap(flagForParsingContentType_, that.flagForParsingContentType_);
|
||||||
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
|
swap(jsonParsingErrorPtr_, that.jsonParsingErrorPtr_);
|
||||||
swap(routingParams_, that.routingParams_);
|
swap(routingParams_, that.routingParams_);
|
||||||
// stream
|
|
||||||
swap(streamStatus_, that.streamStatus_);
|
|
||||||
swap(streamReaderPtr_, that.streamReaderPtr_);
|
|
||||||
swap(streamFinishCb_, that.streamFinishCb_);
|
|
||||||
swap(streamExceptionPtr_, that.streamExceptionPtr_);
|
|
||||||
swap(startProcessing_, that.startProcessing_);
|
|
||||||
swap(connPtr_, that.connPtr_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *HttpRequestImpl::versionString() const
|
const char *HttpRequestImpl::versionString() const
|
||||||
@ -616,6 +547,10 @@ const char *HttpRequestImpl::versionString() const
|
|||||||
result = "HTTP/1.1";
|
result = "HTTP/1.1";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Version::kHttp2:
|
||||||
|
result = "HTTP/2";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -740,11 +675,6 @@ HttpRequestImpl::~HttpRequestImpl()
|
|||||||
|
|
||||||
void HttpRequestImpl::reserveBodySize(size_t length)
|
void HttpRequestImpl::reserveBodySize(size_t length)
|
||||||
{
|
{
|
||||||
assert(loop_->isInLoopThread());
|
|
||||||
if (cacheFilePtr_)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (length <= HttpAppFrameworkImpl::instance().getClientMaxMemoryBodySize())
|
if (length <= HttpAppFrameworkImpl::instance().getClientMaxMemoryBodySize())
|
||||||
{
|
{
|
||||||
content_.reserve(length);
|
content_.reserve(length);
|
||||||
@ -758,14 +688,7 @@ void HttpRequestImpl::reserveBodySize(size_t length)
|
|||||||
|
|
||||||
void HttpRequestImpl::appendToBody(const char *data, size_t length)
|
void HttpRequestImpl::appendToBody(const char *data, size_t length)
|
||||||
{
|
{
|
||||||
assert(loop_->isInLoopThread());
|
if (cacheFilePtr_)
|
||||||
realContentLength_ += length;
|
|
||||||
if (streamReaderPtr_)
|
|
||||||
{
|
|
||||||
assert(streamStatus_ == ReqStreamStatus::Open);
|
|
||||||
streamReaderPtr_->onStreamData(data, length);
|
|
||||||
}
|
|
||||||
else if (cacheFilePtr_)
|
|
||||||
{
|
{
|
||||||
cacheFilePtr_->append(data, length);
|
cacheFilePtr_->append(data, length);
|
||||||
}
|
}
|
||||||
@ -1003,114 +926,3 @@ StreamDecompressStatus HttpRequestImpl::decompressBodyGzip() noexcept
|
|||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestImpl::setStreamReader(RequestStreamReaderPtr reader)
|
|
||||||
{
|
|
||||||
assert(loop_->isInLoopThread());
|
|
||||||
assert(!streamReaderPtr_);
|
|
||||||
assert(streamStatus_ > ReqStreamStatus::None);
|
|
||||||
|
|
||||||
if (streamExceptionPtr_)
|
|
||||||
{
|
|
||||||
assert(streamStatus_ == ReqStreamStatus::Error);
|
|
||||||
reader->onStreamFinish(std::move(streamExceptionPtr_));
|
|
||||||
streamExceptionPtr_ = nullptr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume already received body
|
|
||||||
if (cacheFilePtr_)
|
|
||||||
{
|
|
||||||
auto bodyPieceView = cacheFilePtr_->getStringView();
|
|
||||||
if (!bodyPieceView.empty())
|
|
||||||
reader->onStreamData(bodyPieceView.data(), bodyPieceView.length());
|
|
||||||
cacheFilePtr_.reset();
|
|
||||||
}
|
|
||||||
else if (!content_.empty())
|
|
||||||
{
|
|
||||||
reader->onStreamData(content_.data(), content_.length());
|
|
||||||
content_.clear();
|
|
||||||
}
|
|
||||||
if (streamStatus_ == ReqStreamStatus::Finish)
|
|
||||||
{
|
|
||||||
reader->onStreamFinish({});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
streamReaderPtr_ = std::move(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequestImpl::streamStart()
|
|
||||||
{
|
|
||||||
assert(streamStatus_ == ReqStreamStatus::None);
|
|
||||||
streamStatus_ = ReqStreamStatus::Open;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequestImpl::streamFinish()
|
|
||||||
{
|
|
||||||
assert(loop_->isInLoopThread());
|
|
||||||
assert(streamStatus_ == ReqStreamStatus::Open);
|
|
||||||
streamStatus_ = ReqStreamStatus::Finish;
|
|
||||||
if (streamFinishCb_)
|
|
||||||
{
|
|
||||||
auto cb = std::move(streamFinishCb_);
|
|
||||||
streamFinishCb_ = nullptr;
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
if (streamReaderPtr_)
|
|
||||||
{
|
|
||||||
streamReaderPtr_->onStreamFinish({});
|
|
||||||
streamReaderPtr_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequestImpl::streamError(std::exception_ptr ex)
|
|
||||||
{
|
|
||||||
// TODO: can we be sure that streamError() only be called once?
|
|
||||||
// If not, we could allow it to be called multiple times, and
|
|
||||||
// only handle the first one.
|
|
||||||
assert(loop_->isInLoopThread());
|
|
||||||
assert(streamStatus_ == ReqStreamStatus::Open);
|
|
||||||
streamStatus_ = ReqStreamStatus::Error;
|
|
||||||
if (streamReaderPtr_)
|
|
||||||
{
|
|
||||||
streamReaderPtr_->onStreamFinish(std::move(ex));
|
|
||||||
streamReaderPtr_ = nullptr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
streamExceptionPtr_ = std::move(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamFinishCb_)
|
|
||||||
{
|
|
||||||
auto cb = std::move(streamFinishCb_);
|
|
||||||
streamFinishCb_ = nullptr;
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequestImpl::waitForStreamFinish(std::function<void()> &&cb)
|
|
||||||
{
|
|
||||||
assert(loop_->isInLoopThread());
|
|
||||||
assert(streamStatus_ > ReqStreamStatus::None);
|
|
||||||
|
|
||||||
if (streamStatus_ <= ReqStreamStatus::Open)
|
|
||||||
{
|
|
||||||
assert(!streamFinishCb_); // should only be called once
|
|
||||||
streamFinishCb_ = std::move(cb);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpRequestImpl::quitStreamMode()
|
|
||||||
{
|
|
||||||
assert(loop_->isInLoopThread());
|
|
||||||
assert(streamStatus_ >= ReqStreamStatus::Finish);
|
|
||||||
assert(!streamReaderPtr_);
|
|
||||||
streamStatus_ = ReqStreamStatus::None;
|
|
||||||
}
|
|
||||||
|
@ -16,10 +16,8 @@
|
|||||||
|
|
||||||
#include "HttpUtils.h"
|
#include "HttpUtils.h"
|
||||||
#include "CacheFile.h"
|
#include "CacheFile.h"
|
||||||
#include "impl_forwards.h"
|
|
||||||
#include <drogon/utils/Utilities.h>
|
#include <drogon/utils/Utilities.h>
|
||||||
#include <drogon/HttpRequest.h>
|
#include <drogon/HttpRequest.h>
|
||||||
#include <drogon/RequestStream.h>
|
|
||||||
#include <drogon/utils/Utilities.h>
|
#include <drogon/utils/Utilities.h>
|
||||||
#include <trantor/net/EventLoop.h>
|
#include <trantor/net/EventLoop.h>
|
||||||
#include <trantor/net/InetAddress.h>
|
#include <trantor/net/InetAddress.h>
|
||||||
@ -27,12 +25,9 @@
|
|||||||
#include <trantor/utils/Logger.h>
|
#include <trantor/utils/Logger.h>
|
||||||
#include <trantor/utils/MsgBuffer.h>
|
#include <trantor/utils/MsgBuffer.h>
|
||||||
#include <trantor/utils/NonCopyable.h>
|
#include <trantor/utils/NonCopyable.h>
|
||||||
#include <trantor/net/TcpConnection.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <future>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -47,14 +42,6 @@ enum class StreamDecompressStatus
|
|||||||
Ok
|
Ok
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ReqStreamStatus
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
Open = 1,
|
|
||||||
Finish = 2,
|
|
||||||
Error = 3
|
|
||||||
};
|
|
||||||
|
|
||||||
class HttpRequestImpl : public HttpRequest
|
class HttpRequestImpl : public HttpRequest
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -73,8 +60,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
flagForParsingJson_ = false;
|
flagForParsingJson_ = false;
|
||||||
headers_.clear();
|
headers_.clear();
|
||||||
cookies_.clear();
|
cookies_.clear();
|
||||||
contentLengthHeaderValue_.reset();
|
|
||||||
realContentLength_ = 0;
|
|
||||||
flagForParsingParameters_ = false;
|
flagForParsingParameters_ = false;
|
||||||
path_.clear();
|
path_.clear();
|
||||||
originalPath_.clear();
|
originalPath_.clear();
|
||||||
@ -95,13 +80,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
jsonParsingErrorPtr_.reset();
|
jsonParsingErrorPtr_.reset();
|
||||||
peerCertificate_.reset();
|
peerCertificate_.reset();
|
||||||
routingParams_.clear();
|
routingParams_.clear();
|
||||||
// stream
|
|
||||||
streamStatus_ = ReqStreamStatus::None;
|
|
||||||
streamReaderPtr_.reset();
|
|
||||||
streamFinishCb_ = nullptr;
|
|
||||||
streamExceptionPtr_ = nullptr;
|
|
||||||
startProcessing_ = false;
|
|
||||||
connPtr_.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trantor::EventLoop *getLoop()
|
trantor::EventLoop *getLoop()
|
||||||
@ -229,10 +207,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
|
|
||||||
std::string_view bodyView() const
|
std::string_view bodyView() const
|
||||||
{
|
{
|
||||||
if (isStreamMode())
|
|
||||||
{
|
|
||||||
return emptySv_;
|
|
||||||
}
|
|
||||||
if (cacheFilePtr_)
|
if (cacheFilePtr_)
|
||||||
{
|
{
|
||||||
return cacheFilePtr_->getStringView();
|
return cacheFilePtr_->getStringView();
|
||||||
@ -242,10 +216,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
|
|
||||||
const char *bodyData() const override
|
const char *bodyData() const override
|
||||||
{
|
{
|
||||||
if (isStreamMode())
|
|
||||||
{
|
|
||||||
return emptySv_.data();
|
|
||||||
}
|
|
||||||
if (cacheFilePtr_)
|
if (cacheFilePtr_)
|
||||||
{
|
{
|
||||||
return cacheFilePtr_->getStringView().data();
|
return cacheFilePtr_->getStringView().data();
|
||||||
@ -255,10 +225,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
|
|
||||||
size_t bodyLength() const override
|
size_t bodyLength() const override
|
||||||
{
|
{
|
||||||
if (isStreamMode())
|
|
||||||
{
|
|
||||||
return emptySv_.length();
|
|
||||||
}
|
|
||||||
if (cacheFilePtr_)
|
if (cacheFilePtr_)
|
||||||
{
|
{
|
||||||
return cacheFilePtr_->getStringView().length();
|
return cacheFilePtr_->getStringView().length();
|
||||||
@ -277,10 +243,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
|
|
||||||
std::string_view contentView() const
|
std::string_view contentView() const
|
||||||
{
|
{
|
||||||
if (isStreamMode())
|
|
||||||
{
|
|
||||||
return emptySv_;
|
|
||||||
}
|
|
||||||
if (cacheFilePtr_)
|
if (cacheFilePtr_)
|
||||||
return cacheFilePtr_->getStringView();
|
return cacheFilePtr_->getStringView();
|
||||||
return content_;
|
return content_;
|
||||||
@ -331,11 +293,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
peerCertificate_ = cert;
|
peerCertificate_ = cert;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setConnectionPtr(const std::shared_ptr<trantor::TcpConnection> &ptr)
|
|
||||||
{
|
|
||||||
connPtr_ = ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addHeader(const char *start, const char *colon, const char *end);
|
void addHeader(const char *start, const char *colon, const char *end);
|
||||||
|
|
||||||
void removeHeader(std::string key) override
|
void removeHeader(std::string key) override
|
||||||
@ -392,16 +349,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
return cookies_;
|
return cookies_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<size_t> getContentLengthHeaderValue() const
|
|
||||||
{
|
|
||||||
return contentLengthHeaderValue_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t realContentLength() const override
|
|
||||||
{
|
|
||||||
return realContentLength_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setParameter(const std::string &key, const std::string &value) override
|
void setParameter(const std::string &key, const std::string &value) override
|
||||||
{
|
{
|
||||||
flagForParsingParameters_ = true;
|
flagForParsingParameters_ = true;
|
||||||
@ -448,9 +395,9 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
headers_[std::move(field)] = std::move(value);
|
headers_[std::move(field)] = std::move(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addCookie(std::string key, std::string value) override
|
void addCookie(const std::string &key, const std::string &value) override
|
||||||
{
|
{
|
||||||
cookies_[std::move(key)] = std::move(value);
|
cookies_[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPassThrough(bool flag) override
|
void setPassThrough(bool flag) override
|
||||||
@ -463,7 +410,7 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
return passThrough_;
|
return passThrough_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void appendToBuffer(trantor::MsgBuffer *output) const;
|
void appendToBuffer(trantor::MsgBuffer *output, Version protoVer) const;
|
||||||
|
|
||||||
const SessionPtr &session() const override
|
const SessionPtr &session() const override
|
||||||
{
|
{
|
||||||
@ -564,21 +511,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
return keepAlive_;
|
return keepAlive_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool connected() const noexcept override
|
|
||||||
{
|
|
||||||
if (auto conn = connPtr_.lock())
|
|
||||||
{
|
|
||||||
return conn->connected();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::weak_ptr<trantor::TcpConnection> &getConnectionPtr()
|
|
||||||
const noexcept override
|
|
||||||
{
|
|
||||||
return connPtr_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isOnSecureConnection() const noexcept override
|
bool isOnSecureConnection() const noexcept override
|
||||||
{
|
{
|
||||||
return isOnSecureConnection_;
|
return isOnSecureConnection_;
|
||||||
@ -594,36 +526,7 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
|
|
||||||
StreamDecompressStatus decompressBody();
|
StreamDecompressStatus decompressBody();
|
||||||
|
|
||||||
// Stream mode api
|
~HttpRequestImpl();
|
||||||
ReqStreamStatus streamStatus() const
|
|
||||||
{
|
|
||||||
return streamStatus_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isStreamMode() const
|
|
||||||
{
|
|
||||||
return streamStatus_ > ReqStreamStatus::None;
|
|
||||||
}
|
|
||||||
|
|
||||||
void streamStart();
|
|
||||||
void streamFinish();
|
|
||||||
void streamError(std::exception_ptr ex);
|
|
||||||
|
|
||||||
void setStreamReader(RequestStreamReaderPtr reader);
|
|
||||||
void waitForStreamFinish(std::function<void()> &&cb);
|
|
||||||
void quitStreamMode();
|
|
||||||
|
|
||||||
void startProcessing()
|
|
||||||
{
|
|
||||||
startProcessing_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isProcessingStarted() const
|
|
||||||
{
|
|
||||||
return startProcessing_;
|
|
||||||
}
|
|
||||||
|
|
||||||
~HttpRequestImpl() override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class HttpRequest;
|
friend class HttpRequest;
|
||||||
@ -689,26 +592,18 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
StreamDecompressStatus decompressBodyBrotli() noexcept;
|
StreamDecompressStatus decompressBodyBrotli() noexcept;
|
||||||
#endif
|
#endif
|
||||||
StreamDecompressStatus decompressBodyGzip() noexcept;
|
StreamDecompressStatus decompressBodyGzip() noexcept;
|
||||||
|
|
||||||
static constexpr const std::string_view emptySv_{""};
|
|
||||||
|
|
||||||
mutable bool flagForParsingParameters_{false};
|
mutable bool flagForParsingParameters_{false};
|
||||||
mutable bool flagForParsingJson_{false};
|
mutable bool flagForParsingJson_{false};
|
||||||
HttpMethod method_{Invalid};
|
HttpMethod method_{Invalid};
|
||||||
HttpMethod previousMethod_{Invalid};
|
HttpMethod previousMethod_{Invalid};
|
||||||
Version version_{Version::kUnknown};
|
Version version_{Version::kUnknown};
|
||||||
std::string path_;
|
std::string path_;
|
||||||
/// Contains the encoded `path_` if and only if `path_` is set in encoded
|
|
||||||
/// form. If path is in a normal form and needed no decoding, then this will
|
|
||||||
/// be empty, as we do not need to store a duplicate.
|
|
||||||
std::string originalPath_;
|
std::string originalPath_;
|
||||||
bool pathEncode_{true};
|
bool pathEncode_{true};
|
||||||
std::string_view matchedPathPattern_{""};
|
std::string_view matchedPathPattern_{""};
|
||||||
std::string query_;
|
std::string query_;
|
||||||
SafeStringMap<std::string> headers_;
|
SafeStringMap<std::string> headers_;
|
||||||
SafeStringMap<std::string> cookies_;
|
SafeStringMap<std::string> cookies_;
|
||||||
std::optional<size_t> contentLengthHeaderValue_;
|
|
||||||
size_t realContentLength_{0};
|
|
||||||
mutable SafeStringMap<std::string> parameters_;
|
mutable SafeStringMap<std::string> parameters_;
|
||||||
mutable std::shared_ptr<Json::Value> jsonPtr_;
|
mutable std::shared_ptr<Json::Value> jsonPtr_;
|
||||||
SessionPtr sessionPtr_;
|
SessionPtr sessionPtr_;
|
||||||
@ -725,13 +620,6 @@ class HttpRequestImpl : public HttpRequest
|
|||||||
bool passThrough_{false};
|
bool passThrough_{false};
|
||||||
std::vector<std::string> routingParams_;
|
std::vector<std::string> routingParams_;
|
||||||
|
|
||||||
ReqStreamStatus streamStatus_{ReqStreamStatus::None};
|
|
||||||
std::function<void()> streamFinishCb_;
|
|
||||||
RequestStreamReaderPtr streamReaderPtr_;
|
|
||||||
std::exception_ptr streamExceptionPtr_;
|
|
||||||
bool startProcessing_{false};
|
|
||||||
std::weak_ptr<trantor::TcpConnection> connPtr_;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string content_;
|
std::string content_;
|
||||||
trantor::EventLoop *loop_;
|
trantor::EventLoop *loop_;
|
||||||
|
@ -36,6 +36,19 @@ HttpRequestParser::HttpRequestParser(const trantor::TcpConnectionPtr &connPtr)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpRequestParser::shutdownConnection(HttpStatusCode code)
|
||||||
|
{
|
||||||
|
auto connPtr = conn_.lock();
|
||||||
|
if (connPtr)
|
||||||
|
{
|
||||||
|
connPtr->send(utils::formattedString(
|
||||||
|
"HTTP/1.1 %d %s\r\nConnection: close\r\n\r\n",
|
||||||
|
code,
|
||||||
|
statusCodeToString(code).data()));
|
||||||
|
connPtr->shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool HttpRequestParser::processRequestLine(const char *begin, const char *end)
|
bool HttpRequestParser::processRequestLine(const char *begin, const char *end)
|
||||||
{
|
{
|
||||||
bool succeed = false;
|
bool succeed = false;
|
||||||
@ -117,7 +130,7 @@ HttpRequestImplPtr HttpRequestParser::makeRequestForPool(HttpRequestImpl *ptr)
|
|||||||
void HttpRequestParser::reset()
|
void HttpRequestParser::reset()
|
||||||
{
|
{
|
||||||
assert(loop_->isInLoopThread());
|
assert(loop_->isInLoopThread());
|
||||||
remainContentLength_ = 0;
|
currentContentLength_ = 0;
|
||||||
status_ = HttpRequestParseStatus::kExpectMethod;
|
status_ = HttpRequestParseStatus::kExpectMethod;
|
||||||
if (requestsPool_.empty())
|
if (requestsPool_.empty())
|
||||||
{
|
{
|
||||||
@ -133,12 +146,9 @@ void HttpRequestParser::reset()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return return -HttpStatusCode if encounters any http errors in request
|
* @return return -1 if encounters any error in request
|
||||||
* @return return -1 if encounters any other errors in request
|
|
||||||
* @return return 0 if request is not ready
|
* @return return 0 if request is not ready
|
||||||
* @return return 1 if request is ready
|
* @return return 1 if request is ready
|
||||||
* @return return 2 if request is ready and entering stream mode
|
|
||||||
* @return return 3 if request header is ready and entering stream mode
|
|
||||||
*/
|
*/
|
||||||
int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
||||||
{
|
{
|
||||||
@ -156,14 +166,18 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
{
|
{
|
||||||
if (buf->readableBytes() > METHOD_MAX_LEN)
|
if (buf->readableBytes() > METHOD_MAX_LEN)
|
||||||
{
|
{
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// try read method
|
// try read method
|
||||||
if (!request_->setMethod(buf->peek(), space))
|
if (!request_->setMethod(buf->peek(), space))
|
||||||
{
|
{
|
||||||
return -k405MethodNotAllowed;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k405MethodNotAllowed);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
status_ = HttpRequestParseStatus::kExpectRequestLine;
|
status_ = HttpRequestParseStatus::kExpectRequestLine;
|
||||||
buf->retrieveUntil(space + 1);
|
buf->retrieveUntil(space + 1);
|
||||||
@ -179,14 +193,18 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
/// The limit for request line is 64K bytes. response
|
/// The limit for request line is 64K bytes. response
|
||||||
/// k414RequestURITooLarge
|
/// k414RequestURITooLarge
|
||||||
/// TODO: Make this configurable?
|
/// TODO: Make this configurable?
|
||||||
return -k414RequestURITooLarge;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k414RequestURITooLarge);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!processRequestLine(buf->peek(), crlf))
|
if (!processRequestLine(buf->peek(), crlf))
|
||||||
{
|
{
|
||||||
// error
|
// error
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
buf->retrieveUntil(crlf + CRLF_LEN);
|
buf->retrieveUntil(crlf + CRLF_LEN);
|
||||||
status_ = HttpRequestParseStatus::kExpectHeaders;
|
status_ = HttpRequestParseStatus::kExpectHeaders;
|
||||||
@ -201,7 +219,9 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
{
|
{
|
||||||
/// The limit for every request header is 64K bytes;
|
/// The limit for every request header is 64K bytes;
|
||||||
/// TODO: Make this configurable?
|
/// TODO: Make this configurable?
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -226,18 +246,21 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
remainContentLength_ =
|
currentContentLength_ =
|
||||||
static_cast<size_t>(std::stoull(len));
|
static_cast<size_t>(std::stoull(len));
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
request_->contentLengthHeaderValue_ = remainContentLength_;
|
if (currentContentLength_ == 0)
|
||||||
if (remainContentLength_ == 0)
|
|
||||||
{
|
{
|
||||||
// content-length = 0, request is over.
|
// content-length = 0, request is over.
|
||||||
status_ = HttpRequestParseStatus::kGotAll;
|
status_ = HttpRequestParseStatus::kGotAll;
|
||||||
|
++requestsCounter_;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -253,6 +276,8 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
// no content-length and no transfer-encoding,
|
// no content-length and no transfer-encoding,
|
||||||
// request is over.
|
// request is over.
|
||||||
status_ = HttpRequestParseStatus::kGotAll;
|
status_ = HttpRequestParseStatus::kGotAll;
|
||||||
|
++requestsCounter_;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
else if (encode == "chunked")
|
else if (encode == "chunked")
|
||||||
{
|
{
|
||||||
@ -260,37 +285,43 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return -k501NotImplemented;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k501NotImplemented);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check max body size
|
|
||||||
if (remainContentLength_ >
|
|
||||||
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
|
|
||||||
{
|
|
||||||
return -k413RequestEntityTooLarge;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check expect:100-continue
|
|
||||||
auto &expect = request_->expect();
|
auto &expect = request_->expect();
|
||||||
if (expect == "100-continue" &&
|
if (expect == "100-continue" &&
|
||||||
request_->getVersion() >= Version::kHttp11)
|
request_->getVersion() >= Version::kHttp11)
|
||||||
{
|
{
|
||||||
if (remainContentLength_ == 0)
|
if (currentContentLength_ == 0)
|
||||||
{
|
{
|
||||||
// error
|
// error
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// rfc2616-8.2.3
|
||||||
|
auto connPtr = conn_.lock();
|
||||||
|
if (!connPtr)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
auto resp = HttpResponse::newHttpResponse();
|
||||||
|
if (currentContentLength_ >
|
||||||
|
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
|
||||||
|
{
|
||||||
|
resp->setStatusCode(k413RequestEntityTooLarge);
|
||||||
|
auto httpString =
|
||||||
|
static_cast<HttpResponseImpl *>(resp.get())
|
||||||
|
->renderToBuffer();
|
||||||
|
reset();
|
||||||
|
connPtr->send(std::move(*httpString));
|
||||||
|
// TODO: missing logic here
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// rfc2616-8.2.3
|
|
||||||
// TODO: consider adding an AOP for expect header
|
|
||||||
auto connPtr = conn_.lock(); // ugly
|
|
||||||
if (!connPtr)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
|
||||||
resp->setStatusCode(k100Continue);
|
resp->setStatusCode(k100Continue);
|
||||||
auto httpString =
|
auto httpString =
|
||||||
static_cast<HttpResponseImpl *>(resp.get())
|
static_cast<HttpResponseImpl *>(resp.get())
|
||||||
@ -301,50 +332,35 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
else if (!expect.empty())
|
else if (!expect.empty())
|
||||||
{
|
{
|
||||||
LOG_WARN << "417ExpectationFailed for \"" << expect << "\"";
|
LOG_WARN << "417ExpectationFailed for \"" << expect << "\"";
|
||||||
return -k417ExpectationFailed;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k417ExpectationFailed);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
else if (currentContentLength_ >
|
||||||
assert(status_ == HttpRequestParseStatus::kGotAll ||
|
HttpAppFrameworkImpl::instance()
|
||||||
status_ == HttpRequestParseStatus::kExpectBody ||
|
.getClientMaxBodySize())
|
||||||
status_ == HttpRequestParseStatus::kExpectChunkLen);
|
|
||||||
|
|
||||||
if (app().isRequestStreamEnabled())
|
|
||||||
{
|
{
|
||||||
request_->streamStart();
|
buf->retrieveAll();
|
||||||
if (status_ == HttpRequestParseStatus::kGotAll)
|
shutdownConnection(k413RequestEntityTooLarge);
|
||||||
{
|
return -1;
|
||||||
++requestsCounter_;
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserve space for full body in non-stream mode.
|
|
||||||
// For stream mode requests that match a non-stream handler,
|
|
||||||
// we will reserve full body before waitForStreamFinish().
|
|
||||||
if (remainContentLength_)
|
|
||||||
{
|
|
||||||
request_->reserveBodySize(remainContentLength_);
|
|
||||||
}
|
}
|
||||||
|
request_->reserveBodySize(currentContentLength_);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case HttpRequestParseStatus::kExpectBody:
|
case HttpRequestParseStatus::kExpectBody:
|
||||||
{
|
{
|
||||||
size_t bytesToConsume =
|
size_t bytesToConsume =
|
||||||
remainContentLength_ <= buf->readableBytes()
|
currentContentLength_ <= buf->readableBytes()
|
||||||
? remainContentLength_
|
? currentContentLength_
|
||||||
: buf->readableBytes();
|
: buf->readableBytes();
|
||||||
if (bytesToConsume)
|
if (bytesToConsume)
|
||||||
{
|
{
|
||||||
request_->appendToBody(buf->peek(), bytesToConsume);
|
request_->appendToBody(buf->peek(), bytesToConsume);
|
||||||
buf->retrieve(bytesToConsume);
|
buf->retrieve(bytesToConsume);
|
||||||
remainContentLength_ -= bytesToConsume;
|
currentContentLength_ -= bytesToConsume;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainContentLength_ == 0)
|
if (currentContentLength_ == 0)
|
||||||
{
|
{
|
||||||
status_ = HttpRequestParseStatus::kGotAll;
|
status_ = HttpRequestParseStatus::kGotAll;
|
||||||
++requestsCounter_;
|
++requestsCounter_;
|
||||||
@ -360,7 +376,9 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
{
|
{
|
||||||
if (buf->readableBytes() > TRUNK_LEN_MAX_LEN + CRLF_LEN)
|
if (buf->readableBytes() > TRUNK_LEN_MAX_LEN + CRLF_LEN)
|
||||||
{
|
{
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -370,10 +388,12 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
currentChunkLength_ = strtol(len.c_str(), &end, 16);
|
currentChunkLength_ = strtol(len.c_str(), &end, 16);
|
||||||
if (currentChunkLength_ != 0)
|
if (currentChunkLength_ != 0)
|
||||||
{
|
{
|
||||||
if (currentChunkLength_ + remainContentLength_ >
|
if (currentChunkLength_ + currentContentLength_ >
|
||||||
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
|
HttpAppFrameworkImpl::instance().getClientMaxBodySize())
|
||||||
{
|
{
|
||||||
return -k413RequestEntityTooLarge;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k413RequestEntityTooLarge);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
status_ = HttpRequestParseStatus::kExpectChunkBody;
|
status_ = HttpRequestParseStatus::kExpectChunkBody;
|
||||||
}
|
}
|
||||||
@ -394,11 +414,13 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
*(buf->peek() + currentChunkLength_ + 1) != '\n')
|
*(buf->peek() + currentChunkLength_ + 1) != '\n')
|
||||||
{
|
{
|
||||||
// error!
|
// error!
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
request_->appendToBody(buf->peek(), currentChunkLength_);
|
request_->appendToBody(buf->peek(), currentChunkLength_);
|
||||||
buf->retrieve(currentChunkLength_ + CRLF_LEN);
|
buf->retrieve(currentChunkLength_ + CRLF_LEN);
|
||||||
remainContentLength_ += currentChunkLength_;
|
currentContentLength_ += currentChunkLength_;
|
||||||
currentChunkLength_ = 0;
|
currentChunkLength_ = 0;
|
||||||
status_ = HttpRequestParseStatus::kExpectChunkLen;
|
status_ = HttpRequestParseStatus::kExpectChunkLen;
|
||||||
continue;
|
continue;
|
||||||
@ -413,44 +435,25 @@ int HttpRequestParser::parseRequest(MsgBuffer *buf)
|
|||||||
if (*(buf->peek()) != '\r' || *(buf->peek() + 1) != '\n')
|
if (*(buf->peek()) != '\r' || *(buf->peek() + 1) != '\n')
|
||||||
{
|
{
|
||||||
// error!
|
// error!
|
||||||
return -k400BadRequest;
|
buf->retrieveAll();
|
||||||
|
shutdownConnection(k400BadRequest);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
buf->retrieve(CRLF_LEN);
|
buf->retrieve(CRLF_LEN);
|
||||||
|
|
||||||
if (!request_->isStreamMode())
|
|
||||||
{
|
|
||||||
// Previously we only have non-stream mode, drogon handled
|
|
||||||
// chunked encoding internally, and give user a regular
|
|
||||||
// request as if it has a content-length header.
|
|
||||||
//
|
|
||||||
// We have to keep compatibility for non-stream mode.
|
|
||||||
//
|
|
||||||
// But I don't think it's a good implementation. We should
|
|
||||||
// instead add an api to access real content-length of
|
|
||||||
// requests.
|
|
||||||
// Now HttpRequest::realContentLength() is added, and user
|
|
||||||
// should no longer parse content-length header by
|
|
||||||
// themselves.
|
|
||||||
//
|
|
||||||
// NOTE: request forward behavior may be infected in stream
|
|
||||||
// mode, we should check it out.
|
|
||||||
request_->addHeader("content-length",
|
|
||||||
std::to_string(
|
|
||||||
request_->realContentLength()));
|
|
||||||
request_->removeHeaderBy("transfer-encoding");
|
|
||||||
}
|
|
||||||
status_ = HttpRequestParseStatus::kGotAll;
|
status_ = HttpRequestParseStatus::kGotAll;
|
||||||
|
request_->addHeader("content-length",
|
||||||
|
std::to_string(request_->bodyLength()));
|
||||||
|
request_->removeHeaderBy("transfer-encoding");
|
||||||
++requestsCounter_;
|
++requestsCounter_;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
case HttpRequestParseStatus::kGotAll:
|
case HttpRequestParseStatus::kGotAll:
|
||||||
{
|
{
|
||||||
++requestsCounter_;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1; // won't reach here, just to make compiler happy
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req,
|
void HttpRequestParser::pushRequestToPipelining(const HttpRequestPtr &req,
|
||||||
|
@ -43,6 +43,7 @@ class HttpRequestParser : public trantor::NonCopyable,
|
|||||||
|
|
||||||
explicit HttpRequestParser(const trantor::TcpConnectionPtr &connPtr);
|
explicit HttpRequestParser(const trantor::TcpConnectionPtr &connPtr);
|
||||||
|
|
||||||
|
// return false if any error
|
||||||
int parseRequest(trantor::MsgBuffer *buf);
|
int parseRequest(trantor::MsgBuffer *buf);
|
||||||
|
|
||||||
bool gotAll() const
|
bool gotAll() const
|
||||||
@ -137,6 +138,7 @@ class HttpRequestParser : public trantor::NonCopyable,
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
HttpRequestImplPtr makeRequestForPool(HttpRequestImpl *p);
|
HttpRequestImplPtr makeRequestForPool(HttpRequestImpl *p);
|
||||||
|
void shutdownConnection(HttpStatusCode code);
|
||||||
bool processRequestLine(const char *begin, const char *end);
|
bool processRequestLine(const char *begin, const char *end);
|
||||||
HttpRequestParseStatus status_;
|
HttpRequestParseStatus status_;
|
||||||
trantor::EventLoop *loop_;
|
trantor::EventLoop *loop_;
|
||||||
@ -154,7 +156,7 @@ class HttpRequestParser : public trantor::NonCopyable,
|
|||||||
std::unique_ptr<std::vector<HttpRequestImplPtr>> requestBuffer_;
|
std::unique_ptr<std::vector<HttpRequestImplPtr>> requestBuffer_;
|
||||||
std::vector<HttpRequestImplPtr> requestsPool_;
|
std::vector<HttpRequestImplPtr> requestsPool_;
|
||||||
size_t currentChunkLength_{0};
|
size_t currentChunkLength_{0};
|
||||||
size_t remainContentLength_{0};
|
size_t currentContentLength_{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace drogon
|
} // namespace drogon
|
||||||
|
@ -97,6 +97,9 @@ const char *HttpResponseImpl::versionString() const
|
|||||||
case Version::kHttp11:
|
case Version::kHttp11:
|
||||||
result = "HTTP/1.1";
|
result = "HTTP/1.1";
|
||||||
break;
|
break;
|
||||||
|
case Version::kHttp2:
|
||||||
|
result = "HTTP/2";
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -516,7 +519,6 @@ void HttpResponseImpl::makeHeaderString(trantor::MsgBuffer &buffer)
|
|||||||
statusCode_);
|
statusCode_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.hasWritten(len);
|
buffer.hasWritten(len);
|
||||||
|
|
||||||
if (!statusMessage_.empty())
|
if (!statusMessage_.empty())
|
||||||
@ -526,18 +528,7 @@ void HttpResponseImpl::makeHeaderString(trantor::MsgBuffer &buffer)
|
|||||||
if (!passThrough_)
|
if (!passThrough_)
|
||||||
{
|
{
|
||||||
buffer.ensureWritableBytes(64);
|
buffer.ensureWritableBytes(64);
|
||||||
if (!contentLengthIsAllowed())
|
if (streamCallback_ || asyncStreamCallback_)
|
||||||
{
|
|
||||||
len = 0;
|
|
||||||
if ((bodyPtr_ && bodyPtr_->length() > 0) ||
|
|
||||||
!sendfileName_.empty() || streamCallback_ ||
|
|
||||||
asyncStreamCallback_)
|
|
||||||
{
|
|
||||||
LOG_ERROR << "The body should be empty when the content-length "
|
|
||||||
"is not allowed!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (streamCallback_ || asyncStreamCallback_)
|
|
||||||
{
|
{
|
||||||
// When the headers are created, it is time to set the transfer
|
// When the headers are created, it is time to set the transfer
|
||||||
// encoding to chunked if the contents size is not specified
|
// encoding to chunked if the contents size is not specified
|
||||||
@ -632,14 +623,15 @@ void HttpResponseImpl::renderToBuffer(trantor::MsgBuffer &buffer)
|
|||||||
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
drogon::HttpAppFrameworkImpl::instance().sendDateHeader())
|
||||||
{
|
{
|
||||||
buffer.append("date: ");
|
buffer.append("date: ");
|
||||||
buffer.append(utils::getHttpFullDateStr(trantor::Date::date()));
|
buffer.append(utils::getHttpFullDate(trantor::Date::date()),
|
||||||
|
httpFullDateStringLength);
|
||||||
buffer.append("\r\n\r\n");
|
buffer.append("\r\n\r\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
buffer.append("\r\n");
|
buffer.append("\r\n");
|
||||||
}
|
}
|
||||||
if (bodyPtr_ && contentLengthIsAllowed())
|
if (bodyPtr_)
|
||||||
buffer.append(bodyPtr_->data(), bodyPtr_->length());
|
buffer.append(bodyPtr_->data(), bodyPtr_->length());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -705,7 +697,8 @@ std::shared_ptr<trantor::MsgBuffer> HttpResponseImpl::renderToBuffer()
|
|||||||
{
|
{
|
||||||
httpString->append("date: ");
|
httpString->append("date: ");
|
||||||
auto datePos = httpString->readableBytes();
|
auto datePos = httpString->readableBytes();
|
||||||
httpString->append(utils::getHttpFullDateStr(trantor::Date::date()));
|
httpString->append(utils::getHttpFullDate(trantor::Date::date()),
|
||||||
|
httpFullDateStringLength);
|
||||||
httpString->append("\r\n\r\n");
|
httpString->append("\r\n\r\n");
|
||||||
datePos_ = datePos;
|
datePos_ = datePos;
|
||||||
}
|
}
|
||||||
@ -864,7 +857,7 @@ void HttpResponseImpl::addHeader(const char *start,
|
|||||||
}
|
}
|
||||||
if (!cookie.key().empty())
|
if (!cookie.key().empty())
|
||||||
{
|
{
|
||||||
cookies_[cookie.key()] = std::move(cookie);
|
cookies_[cookie.key()] = cookie;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -967,8 +960,7 @@ bool HttpResponseImpl::shouldBeCompressed() const
|
|||||||
{
|
{
|
||||||
if (streamCallback_ || asyncStreamCallback_ || !sendfileName_.empty() ||
|
if (streamCallback_ || asyncStreamCallback_ || !sendfileName_.empty() ||
|
||||||
contentType() >= CT_APPLICATION_OCTET_STREAM ||
|
contentType() >= CT_APPLICATION_OCTET_STREAM ||
|
||||||
getBody().length() < 1024 ||
|
getBody().length() < 1024 || !(getHeaderBy("content-encoding").empty()))
|
||||||
!(getHeaderBy("content-encoding").empty()) || !contentLengthIsAllowed())
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -402,16 +402,6 @@ class DROGON_EXPORT HttpResponseImpl : public HttpResponse
|
|||||||
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
addHeader("content-length", std::to_string(bodyPtr_->length()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contentLengthIsAllowed() const
|
|
||||||
{
|
|
||||||
int statusCode =
|
|
||||||
customStatusCode_ >= 0 ? customStatusCode_ : statusCode_;
|
|
||||||
|
|
||||||
// return false if status code is 1xx or 204
|
|
||||||
return (statusCode >= k200OK || statusCode < k100Continue) &&
|
|
||||||
statusCode != k204NoContent;
|
|
||||||
}
|
|
||||||
#ifdef USE_BROTLI
|
#ifdef USE_BROTLI
|
||||||
void brDecompress()
|
void brDecompress()
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
#include "HttpControllersRouter.h"
|
#include "HttpControllersRouter.h"
|
||||||
#include "StaticFileRouter.h"
|
#include "StaticFileRouter.h"
|
||||||
#include "WebSocketConnectionImpl.h"
|
#include "WebSocketConnectionImpl.h"
|
||||||
#include "impl_forwards.h"
|
|
||||||
|
|
||||||
#if COZ_PROFILING
|
#if COZ_PROFILING
|
||||||
#include <coz.h>
|
#include <coz.h>
|
||||||
@ -76,12 +75,7 @@ HttpServer::HttpServer(EventLoop *loop,
|
|||||||
: server_(loop, listenAddr, std::move(name), true, app().reusePort())
|
: server_(loop, listenAddr, std::move(name), true, app().reusePort())
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
server_.setConnectionCallback(
|
server_.setConnectionCallback(onConnection);
|
||||||
[this](const trantor::TcpConnectionPtr &conn) {
|
|
||||||
onConnection(conn);
|
|
||||||
if (connectionCallback_)
|
|
||||||
connectionCallback_(conn);
|
|
||||||
});
|
|
||||||
server_.setRecvMessageCallback(onMessage);
|
server_.setRecvMessageCallback(onMessage);
|
||||||
server_.kickoffIdleConnections(
|
server_.kickoffIdleConnections(
|
||||||
HttpAppFrameworkImpl::instance().getIdleConnectionTimeout());
|
HttpAppFrameworkImpl::instance().getIdleConnectionTimeout());
|
||||||
@ -91,14 +85,6 @@ HttpServer::~HttpServer() = default;
|
|||||||
|
|
||||||
void HttpServer::start()
|
void HttpServer::start()
|
||||||
{
|
{
|
||||||
if (beforeListenSetSockOptCallback_)
|
|
||||||
{
|
|
||||||
server_.setBeforeListenSockOptCallback(beforeListenSetSockOptCallback_);
|
|
||||||
}
|
|
||||||
if (afterAcceptSetSockOptCallback_)
|
|
||||||
{
|
|
||||||
server_.setAfterAcceptSockOptCallback(afterAcceptSetSockOptCallback_);
|
|
||||||
}
|
|
||||||
LOG_TRACE << "HttpServer[" << server_.name() << "] starts listening on "
|
LOG_TRACE << "HttpServer[" << server_.name() << "] starts listening on "
|
||||||
<< server_.ipPort();
|
<< server_.ipPort();
|
||||||
server_.start();
|
server_.start();
|
||||||
@ -138,13 +124,6 @@ void HttpServer::onConnection(const TcpConnectionPtr &conn)
|
|||||||
{
|
{
|
||||||
requestParser->webSocketConn()->onClose();
|
requestParser->webSocketConn()->onClose();
|
||||||
}
|
}
|
||||||
else if (requestParser->requestImpl()->isStreamMode())
|
|
||||||
{
|
|
||||||
requestParser->requestImpl()->streamError(
|
|
||||||
std::make_exception_ptr(
|
|
||||||
StreamError(StreamErrorCode::kConnectionBroken,
|
|
||||||
"Connection closed")));
|
|
||||||
}
|
|
||||||
conn->clearContext();
|
conn->clearContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,76 +154,28 @@ void HttpServer::onMessage(const TcpConnectionPtr &conn, MsgBuffer *buf)
|
|||||||
buf->retrieveAll();
|
buf->retrieveAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &req = requestParser->requestImpl();
|
|
||||||
// if stream mode enabled, parseRequest() may return >0 multiple times
|
|
||||||
// for the same request
|
|
||||||
int parseRes = requestParser->parseRequest(buf);
|
int parseRes = requestParser->parseRequest(buf);
|
||||||
if (parseRes < 0)
|
if (parseRes < 0)
|
||||||
{
|
{
|
||||||
if (req->isStreamMode() && req->isProcessingStarted())
|
|
||||||
{
|
|
||||||
// After entering stream mode, if request matches a non-stream
|
|
||||||
// handler, stream error would be intercepted by the
|
|
||||||
// `waitForStreamFinish()` call.
|
|
||||||
// If request matches a stream handler, stream error should be
|
|
||||||
// captured by user provided StreamReader, and response should
|
|
||||||
// also be sent by user.
|
|
||||||
req->streamError(std::make_exception_ptr(
|
|
||||||
StreamError(StreamErrorCode::kBadRequest, "Bad request")));
|
|
||||||
}
|
|
||||||
else if (parseRes != -1)
|
|
||||||
{
|
|
||||||
// In non-stream mode, request won't be process until it's fully
|
|
||||||
// parsed. To keep the old behavior, we send response directly
|
|
||||||
// through conn. (This response won't go through pre-sending
|
|
||||||
// aop, maybe we should change this behavior).
|
|
||||||
auto code = static_cast<HttpStatusCode>(-parseRes);
|
|
||||||
conn->send(utils::formattedString(
|
|
||||||
"HTTP/1.1 %d %s\r\nConnection: close\r\n\r\n",
|
|
||||||
code,
|
|
||||||
statusCodeToString(code).data()));
|
|
||||||
}
|
|
||||||
buf->retrieveAll();
|
|
||||||
// NOTE: should we call conn->forceClose() instead?
|
|
||||||
// Calling shutdown() handles socket more elegantly.
|
|
||||||
conn->shutdown();
|
|
||||||
// We have to call clearContext() here in order to ignore following
|
|
||||||
// illegal data from client
|
|
||||||
conn->clearContext();
|
|
||||||
requestParser->reset();
|
requestParser->reset();
|
||||||
|
conn->forceClose();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parseRes == 0)
|
if (parseRes == 0)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (parseRes >= 2 || parseRes == 1 && !req->isStreamMode())
|
auto &req = requestParser->requestImpl();
|
||||||
{
|
req->setPeerAddr(conn->peerAddr());
|
||||||
req->setPeerAddr(conn->peerAddr());
|
req->setLocalAddr(conn->localAddr());
|
||||||
req->setLocalAddr(conn->localAddr());
|
req->setCreationDate(trantor::Date::date());
|
||||||
req->setCreationDate(trantor::Date::date());
|
req->setSecure(conn->isSSLConnection());
|
||||||
req->setSecure(conn->isSSLConnection());
|
req->setPeerCertificate(conn->peerCertificate());
|
||||||
req->setPeerCertificate(conn->peerCertificate());
|
requests.push_back(req);
|
||||||
req->setConnectionPtr(conn);
|
requestParser->reset();
|
||||||
// TODO: maybe call onRequests() directly in stream mode
|
|
||||||
requests.push_back(req);
|
|
||||||
}
|
|
||||||
if (parseRes == 1 || parseRes == 2)
|
|
||||||
{
|
|
||||||
assert(requestParser->gotAll());
|
|
||||||
if (req->isStreamMode())
|
|
||||||
{
|
|
||||||
req->streamFinish();
|
|
||||||
}
|
|
||||||
requestParser->reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!requests.empty())
|
|
||||||
{
|
|
||||||
onRequests(conn, requests, requestParser);
|
|
||||||
requests.clear();
|
|
||||||
}
|
}
|
||||||
|
onRequests(conn, requests, requestParser);
|
||||||
|
requests.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CallbackParamPack
|
struct CallbackParamPack
|
||||||
@ -275,14 +206,14 @@ void HttpServer::onRequests(
|
|||||||
const std::vector<HttpRequestImplPtr> &requests,
|
const std::vector<HttpRequestImplPtr> &requests,
|
||||||
const std::shared_ptr<HttpRequestParser> &requestParser)
|
const std::shared_ptr<HttpRequestParser> &requestParser)
|
||||||
{
|
{
|
||||||
assert(!requests.empty());
|
if (requests.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
// will only be checked for the first request
|
// will only be checked for the first request
|
||||||
if (requestParser->firstReq() && requests.size() == 1 &&
|
if (requestParser->firstReq() && requests.size() == 1 &&
|
||||||
isWebSocket(requests[0]))
|
isWebSocket(requests[0]))
|
||||||
{
|
{
|
||||||
auto &req = requests[0];
|
auto &req = requests[0];
|
||||||
req->startProcessing();
|
|
||||||
if (passSyncAdvices(req,
|
if (passSyncAdvices(req,
|
||||||
requestParser,
|
requestParser,
|
||||||
false /* Not pipelined */,
|
false /* Not pipelined */,
|
||||||
@ -313,7 +244,7 @@ void HttpServer::onRequests(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush response for not passing sync advice
|
// flush response for not passing sync advices
|
||||||
if (conn->connected() && !requestParser->getResponseBuffer().empty())
|
if (conn->connected() && !requestParser->getResponseBuffer().empty())
|
||||||
{
|
{
|
||||||
sendResponses(conn,
|
sendResponses(conn,
|
||||||
@ -348,7 +279,6 @@ void HttpServer::onRequests(
|
|||||||
|
|
||||||
for (auto &req : requests)
|
for (auto &req : requests)
|
||||||
{
|
{
|
||||||
req->startProcessing();
|
|
||||||
bool isHeadMethod = (req->method() == Head);
|
bool isHeadMethod = (req->method() == Head);
|
||||||
if (isHeadMethod)
|
if (isHeadMethod)
|
||||||
{
|
{
|
||||||
@ -483,47 +413,6 @@ void HttpServer::httpRequestRouting(
|
|||||||
template <typename Pack>
|
template <typename Pack>
|
||||||
void HttpServer::requestPostRouting(const HttpRequestImplPtr &req, Pack &&pack)
|
void HttpServer::requestPostRouting(const HttpRequestImplPtr &req, Pack &&pack)
|
||||||
{
|
{
|
||||||
// Handle stream mode for non-stream handlers
|
|
||||||
if (req->streamStatus() >= ReqStreamStatus::Open &&
|
|
||||||
!pack.binderPtr->isStreamHandler())
|
|
||||||
{
|
|
||||||
LOG_TRACE << "Wait for request stream finish";
|
|
||||||
if (req->streamStatus() == ReqStreamStatus::Finish)
|
|
||||||
{
|
|
||||||
req->quitStreamMode();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto contentLength = req->getContentLengthHeaderValue();
|
|
||||||
if (contentLength.has_value())
|
|
||||||
{
|
|
||||||
req->reserveBodySize(contentLength.value());
|
|
||||||
}
|
|
||||||
req->waitForStreamFinish([weakReq = std::weak_ptr(req),
|
|
||||||
pack =
|
|
||||||
std::forward<Pack>(pack)]() mutable {
|
|
||||||
auto req = weakReq.lock();
|
|
||||||
if (!req)
|
|
||||||
return;
|
|
||||||
if (req->streamStatus() == ReqStreamStatus::Finish)
|
|
||||||
{
|
|
||||||
req->quitStreamMode();
|
|
||||||
// call requestPostRouting again
|
|
||||||
requestPostRouting(req, std::forward<Pack>(pack));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
req->quitStreamMode();
|
|
||||||
LOG_ERROR << "Stop processing request due to stream error";
|
|
||||||
pack.callback(
|
|
||||||
app().getCustomErrorHandler()(k400BadRequest, req));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// post-routing aop
|
// post-routing aop
|
||||||
auto &aop = AopAdvice::instance();
|
auto &aop = AopAdvice::instance();
|
||||||
aop.passPostRoutingObservers(req);
|
aop.passPostRoutingObservers(req);
|
||||||
@ -973,8 +862,6 @@ void HttpServer::sendResponse(const TcpConnectionPtr &conn,
|
|||||||
{
|
{
|
||||||
auto httpString = respImplPtr->renderToBuffer();
|
auto httpString = respImplPtr->renderToBuffer();
|
||||||
conn->send(httpString);
|
conn->send(httpString);
|
||||||
if (!respImplPtr->contentLengthIsAllowed())
|
|
||||||
return;
|
|
||||||
auto &asyncStreamCallback = respImplPtr->asyncStreamCallback();
|
auto &asyncStreamCallback = respImplPtr->asyncStreamCallback();
|
||||||
if (asyncStreamCallback)
|
if (asyncStreamCallback)
|
||||||
{
|
{
|
||||||
@ -1055,8 +942,6 @@ void HttpServer::sendResponses(
|
|||||||
{
|
{
|
||||||
// Not HEAD method
|
// Not HEAD method
|
||||||
respImplPtr->renderToBuffer(buffer);
|
respImplPtr->renderToBuffer(buffer);
|
||||||
if (!respImplPtr->contentLengthIsAllowed())
|
|
||||||
continue;
|
|
||||||
auto &asyncStreamCallback = respImplPtr->asyncStreamCallback();
|
auto &asyncStreamCallback = respImplPtr->asyncStreamCallback();
|
||||||
if (asyncStreamCallback)
|
if (asyncStreamCallback)
|
||||||
{
|
{
|
||||||
@ -1202,7 +1087,7 @@ static inline HttpResponsePtr tryDecompressRequest(
|
|||||||
* @brief Check request against each sync advice, generate response if request
|
* @brief Check request against each sync advice, generate response if request
|
||||||
* is rejected by any one of them.
|
* is rejected by any one of them.
|
||||||
*
|
*
|
||||||
* @return true if all sync advice are passed.
|
* @return true if all sync advices are passed.
|
||||||
* @return false if rejected by any sync advice.
|
* @return false if rejected by any sync advice.
|
||||||
*/
|
*/
|
||||||
static inline bool passSyncAdvices(
|
static inline bool passSyncAdvices(
|
||||||
|
@ -49,32 +49,11 @@ class HttpServer : trantor::NonCopyable
|
|||||||
server_.enableSSL(std::move(policy));
|
server_.enableSSL(std::move(policy));
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadSSL()
|
|
||||||
{
|
|
||||||
server_.reloadSSL();
|
|
||||||
}
|
|
||||||
|
|
||||||
const trantor::InetAddress &address() const
|
const trantor::InetAddress &address() const
|
||||||
{
|
{
|
||||||
return server_.address();
|
return server_.address();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBeforeListenSockOptCallback(std::function<void(int)> cb)
|
|
||||||
{
|
|
||||||
beforeListenSetSockOptCallback_ = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAfterAcceptSockOptCallback(std::function<void(int)> cb)
|
|
||||||
{
|
|
||||||
afterAcceptSetSockOptCallback_ = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setConnectionCallback(
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> cb)
|
|
||||||
{
|
|
||||||
connectionCallback_ = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class HttpInternalForwardHelper;
|
friend class HttpInternalForwardHelper;
|
||||||
|
|
||||||
@ -147,10 +126,6 @@ class HttpServer : trantor::NonCopyable
|
|||||||
trantor::MsgBuffer &buffer);
|
trantor::MsgBuffer &buffer);
|
||||||
|
|
||||||
trantor::TcpServer server_;
|
trantor::TcpServer server_;
|
||||||
|
|
||||||
std::function<void(int)> beforeListenSetSockOptCallback_;
|
|
||||||
std::function<void(int)> afterAcceptSetSockOptCallback_;
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HttpInternalForwardHelper
|
class HttpInternalForwardHelper
|
||||||
|
51
lib/src/HttpTransport.h
Normal file
51
lib/src/HttpTransport.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HttpRequestImpl.h"
|
||||||
|
#include "HttpResponseImpl.h"
|
||||||
|
#include <drogon/drogon_callbacks.h>
|
||||||
|
|
||||||
|
#include <trantor/net/TcpConnection.h>
|
||||||
|
#include <trantor/utils/NonCopyable.h>
|
||||||
|
|
||||||
|
namespace drogon
|
||||||
|
{
|
||||||
|
|
||||||
|
class HttpTransport : public trantor::NonCopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HttpTransport() = default;
|
||||||
|
virtual ~HttpTransport() = default;
|
||||||
|
virtual void sendRequestInLoop(const HttpRequestPtr &req,
|
||||||
|
HttpReqCallback &&callback) = 0;
|
||||||
|
virtual void onRecvMessage(const trantor::TcpConnectionPtr &,
|
||||||
|
trantor::MsgBuffer *) = 0;
|
||||||
|
virtual bool handleConnectionClose() = 0;
|
||||||
|
|
||||||
|
// XXX: Footgun: This MUST be called by the HttpClient. DO NOT
|
||||||
|
// call this within Transport. Client needs to know that error
|
||||||
|
// happened. When in doubt, call error callback.
|
||||||
|
virtual void onError(ReqResult result) = 0;
|
||||||
|
|
||||||
|
virtual size_t requestsInFlight() const = 0;
|
||||||
|
|
||||||
|
using RespCallback =
|
||||||
|
std::function<void(const HttpResponseImplPtr &,
|
||||||
|
std::pair<HttpRequestPtr, HttpReqCallback> &&,
|
||||||
|
const trantor::TcpConnectionPtr)>;
|
||||||
|
using ErrorCallback = std::function<void(ReqResult)>;
|
||||||
|
|
||||||
|
void setRespCallback(RespCallback cb)
|
||||||
|
{
|
||||||
|
respCallback = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setErrorCallback(ErrorCallback cb)
|
||||||
|
{
|
||||||
|
errorCallback = std::move(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
RespCallback respCallback;
|
||||||
|
ErrorCallback errorCallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace drogon
|
@ -111,20 +111,6 @@ void ListenerManager::createListeners(
|
|||||||
std::make_shared<HttpServer>(ioLoops[i],
|
std::make_shared<HttpServer>(ioLoops[i],
|
||||||
listenAddress,
|
listenAddress,
|
||||||
"drogon");
|
"drogon");
|
||||||
if (beforeListenSetSockOptCallback_)
|
|
||||||
{
|
|
||||||
serverPtr->setBeforeListenSockOptCallback(
|
|
||||||
beforeListenSetSockOptCallback_);
|
|
||||||
}
|
|
||||||
if (afterAcceptSetSockOptCallback_)
|
|
||||||
{
|
|
||||||
serverPtr->setAfterAcceptSockOptCallback(
|
|
||||||
afterAcceptSetSockOptCallback_);
|
|
||||||
}
|
|
||||||
if (connectionCallback_)
|
|
||||||
{
|
|
||||||
serverPtr->setConnectionCallback(connectionCallback_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener.useSSL_ && utils::supportsTls())
|
if (listener.useSSL_ && utils::supportsTls())
|
||||||
{
|
{
|
||||||
@ -218,11 +204,3 @@ void ListenerManager::stopListening()
|
|||||||
listeningThread_->wait();
|
listeningThread_->wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListenerManager::reloadSSLFiles()
|
|
||||||
{
|
|
||||||
for (auto &server : servers_)
|
|
||||||
{
|
|
||||||
server->reloadSSL();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -51,24 +51,6 @@ class ListenerManager : public trantor::NonCopyable
|
|||||||
void startListening();
|
void startListening();
|
||||||
void stopListening();
|
void stopListening();
|
||||||
|
|
||||||
void setBeforeListenSockOptCallback(std::function<void(int)> cb)
|
|
||||||
{
|
|
||||||
beforeListenSetSockOptCallback_ = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAfterAcceptSockOptCallback(std::function<void(int)> cb)
|
|
||||||
{
|
|
||||||
afterAcceptSetSockOptCallback_ = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setConnectionCallback(
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> cb)
|
|
||||||
{
|
|
||||||
connectionCallback_ = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reloadSSLFiles();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ListenerInfo
|
struct ListenerInfo
|
||||||
{
|
{
|
||||||
@ -105,9 +87,6 @@ class ListenerManager : public trantor::NonCopyable
|
|||||||
// should have value when and only when on OS that one port can only be
|
// should have value when and only when on OS that one port can only be
|
||||||
// listened by one thread
|
// listened by one thread
|
||||||
std::unique_ptr<trantor::EventLoopThread> listeningThread_;
|
std::unique_ptr<trantor::EventLoopThread> listeningThread_;
|
||||||
std::function<void(int)> beforeListenSetSockOptCallback_;
|
|
||||||
std::function<void(int)> afterAcceptSetSockOptCallback_;
|
|
||||||
std::function<void(const trantor::TcpConnectionPtr &)> connectionCallback_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace drogon
|
} // namespace drogon
|
||||||
|
@ -1,356 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @file MultipartStreamParser.h
|
|
||||||
* @author Nitromelon
|
|
||||||
*
|
|
||||||
* Copyright 2024, Nitromelon. All rights reserved.
|
|
||||||
* https://github.com/drogonframework/drogon
|
|
||||||
* Use of this source code is governed by a MIT license
|
|
||||||
* that can be found in the License file.
|
|
||||||
*
|
|
||||||
* Drogon
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "MultipartStreamParser.h"
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
using namespace drogon;
|
|
||||||
|
|
||||||
static bool startsWith(const std::string_view &a, const std::string_view &b)
|
|
||||||
{
|
|
||||||
if (a.size() < b.size())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < b.size(); i++)
|
|
||||||
{
|
|
||||||
if (a[i] != b[i])
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool startsWithIgnoreCase(const std::string_view &a,
|
|
||||||
const std::string_view &b)
|
|
||||||
{
|
|
||||||
if (a.size() < b.size())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < b.size(); i++)
|
|
||||||
{
|
|
||||||
if (::tolower(a[i]) != ::tolower(b[i]))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
MultipartStreamParser::MultipartStreamParser(const std::string &contentType)
|
|
||||||
{
|
|
||||||
static const std::string_view multipart = "multipart/form-data";
|
|
||||||
static const std::string_view boundaryEq = "boundary=";
|
|
||||||
|
|
||||||
if (!startsWithIgnoreCase(contentType, multipart))
|
|
||||||
{
|
|
||||||
isValid_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto pos = contentType.find(boundaryEq, multipart.size());
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
{
|
|
||||||
isValid_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += boundaryEq.size();
|
|
||||||
size_t pos2;
|
|
||||||
if (contentType[pos] == '"')
|
|
||||||
{
|
|
||||||
++pos;
|
|
||||||
pos2 = contentType.find('"', pos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pos2 = contentType.find(';', pos);
|
|
||||||
}
|
|
||||||
if (pos2 == std::string::npos)
|
|
||||||
pos2 = contentType.size();
|
|
||||||
|
|
||||||
boundary_ = contentType.substr(pos, pos2 - pos);
|
|
||||||
dashBoundaryCrlf_ = dash_ + boundary_ + crlf_;
|
|
||||||
crlfDashBoundary_ = crlf_ + dash_ + boundary_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: same function in HttpRequestParser.cc
|
|
||||||
static std::pair<std::string_view, std::string_view> parseLine(
|
|
||||||
const char *begin,
|
|
||||||
const char *end)
|
|
||||||
{
|
|
||||||
auto p = begin;
|
|
||||||
while (p != end)
|
|
||||||
{
|
|
||||||
if (*p == ':')
|
|
||||||
{
|
|
||||||
if (p + 1 != end && *(p + 1) == ' ')
|
|
||||||
{
|
|
||||||
return std::make_pair(std::string_view(begin, p - begin),
|
|
||||||
std::string_view(p + 2, end - p - 2));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return std::make_pair(std::string_view(begin, p - begin),
|
|
||||||
std::string_view(p + 1, end - p - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++p;
|
|
||||||
}
|
|
||||||
return std::make_pair(std::string_view(), std::string_view());
|
|
||||||
}
|
|
||||||
|
|
||||||
void drogon::MultipartStreamParser::parse(
|
|
||||||
const char *data,
|
|
||||||
size_t length,
|
|
||||||
const drogon::RequestStreamReader::MultipartHeaderCallback &headerCb,
|
|
||||||
const drogon::RequestStreamReader::StreamDataCallback &dataCb)
|
|
||||||
{
|
|
||||||
buffer_.append(data, length);
|
|
||||||
|
|
||||||
while (buffer_.size() > 0)
|
|
||||||
{
|
|
||||||
switch (status_)
|
|
||||||
{
|
|
||||||
case Status::kExpectFirstBoundary:
|
|
||||||
{
|
|
||||||
if (buffer_.size() < dashBoundaryCrlf_.size())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string_view v = buffer_.view();
|
|
||||||
auto pos = v.find(dashBoundaryCrlf_);
|
|
||||||
// ignore everything before the first boundary
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
{
|
|
||||||
buffer_.eraseFront(buffer_.size() -
|
|
||||||
dashBoundaryCrlf_.size());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// found
|
|
||||||
buffer_.eraseFront(pos + dashBoundaryCrlf_.size());
|
|
||||||
status_ = Status::kExpectNewEntry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case Status::kExpectNewEntry:
|
|
||||||
{
|
|
||||||
currentHeader_.name.clear();
|
|
||||||
currentHeader_.filename.clear();
|
|
||||||
currentHeader_.contentType.clear();
|
|
||||||
status_ = Status::kExpectHeader;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case Status::kExpectHeader:
|
|
||||||
{
|
|
||||||
std::string_view v = buffer_.view();
|
|
||||||
auto pos = v.find(crlf_);
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
{
|
|
||||||
// same magic number in HttpRequestParser::parseRequest()
|
|
||||||
if (buffer_.size() > 60 * 1024)
|
|
||||||
{
|
|
||||||
isValid_ = false;
|
|
||||||
}
|
|
||||||
return; // header incomplete, wait for more data
|
|
||||||
}
|
|
||||||
// empty line
|
|
||||||
if (pos == 0)
|
|
||||||
{
|
|
||||||
buffer_.eraseFront(crlf_.size());
|
|
||||||
status_ = Status::kExpectBody;
|
|
||||||
headerCb(currentHeader_);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// found header line
|
|
||||||
auto [keyView, valueView] = parseLine(v.data(), v.data() + pos);
|
|
||||||
if (keyView.empty() || valueView.empty())
|
|
||||||
{
|
|
||||||
// Bad header
|
|
||||||
isValid_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startsWithIgnoreCase(keyView, "content-type"))
|
|
||||||
{
|
|
||||||
currentHeader_.contentType = valueView;
|
|
||||||
}
|
|
||||||
else if (startsWithIgnoreCase(keyView, "content-disposition"))
|
|
||||||
{
|
|
||||||
static const std::string_view nameKey = "name=";
|
|
||||||
static const std::string_view fileNameKey = "filename=";
|
|
||||||
|
|
||||||
// Extract name
|
|
||||||
auto namePos = valueView.find(nameKey);
|
|
||||||
if (namePos == std::string::npos)
|
|
||||||
{
|
|
||||||
// name absent
|
|
||||||
isValid_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
namePos += nameKey.size();
|
|
||||||
size_t nameEnd;
|
|
||||||
if (valueView[namePos] == '"')
|
|
||||||
{
|
|
||||||
++namePos;
|
|
||||||
nameEnd = valueView.find('"', namePos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nameEnd = valueView.find(';', namePos);
|
|
||||||
}
|
|
||||||
if (nameEnd == std::string::npos)
|
|
||||||
{
|
|
||||||
// name end not found
|
|
||||||
isValid_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentHeader_.name =
|
|
||||||
valueView.substr(namePos, nameEnd - namePos);
|
|
||||||
|
|
||||||
// Extract filename
|
|
||||||
auto fileNamePos = valueView.find(fileNameKey, nameEnd);
|
|
||||||
if (fileNamePos != std::string::npos)
|
|
||||||
{
|
|
||||||
fileNamePos += fileNameKey.size();
|
|
||||||
size_t fileNameEnd;
|
|
||||||
if (valueView[fileNamePos] == '"')
|
|
||||||
{
|
|
||||||
++fileNamePos;
|
|
||||||
fileNameEnd = valueView.find('"', fileNamePos);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fileNameEnd = valueView.find(';', fileNamePos);
|
|
||||||
}
|
|
||||||
currentHeader_.filename =
|
|
||||||
valueView.substr(fileNamePos,
|
|
||||||
fileNameEnd - fileNamePos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ignore other headers
|
|
||||||
buffer_.eraseFront(pos + crlf_.size());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case Status::kExpectBody:
|
|
||||||
{
|
|
||||||
if (buffer_.size() < crlfDashBoundary_.size())
|
|
||||||
{
|
|
||||||
return; // not enough data to check boundary
|
|
||||||
}
|
|
||||||
std::string_view v = buffer_.view();
|
|
||||||
auto pos = v.find(crlfDashBoundary_);
|
|
||||||
if (pos == std::string::npos)
|
|
||||||
{
|
|
||||||
// boundary not found, leave potential partial boundary
|
|
||||||
size_t len = v.size() - crlfDashBoundary_.size();
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
dataCb(v.data(), len);
|
|
||||||
buffer_.eraseFront(len);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// found boundary
|
|
||||||
dataCb(v.data(), pos);
|
|
||||||
if (pos > 0)
|
|
||||||
{
|
|
||||||
dataCb(v.data() + pos, 0); // notify end of file
|
|
||||||
}
|
|
||||||
buffer_.eraseFront(pos + crlfDashBoundary_.size());
|
|
||||||
status_ = Status::kExpectEndOrNewEntry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case Status::kExpectEndOrNewEntry:
|
|
||||||
{
|
|
||||||
std::string_view v = buffer_.view();
|
|
||||||
// Check new entry
|
|
||||||
if (v.size() < crlf_.size())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (startsWith(v, crlf_))
|
|
||||||
{
|
|
||||||
buffer_.eraseFront(crlf_.size());
|
|
||||||
status_ = Status::kExpectNewEntry;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check end
|
|
||||||
if (v.size() < dash_.size())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (startsWith(v, dash_))
|
|
||||||
{
|
|
||||||
isFinished_ = true;
|
|
||||||
buffer_.clear(); // ignore epilogue
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isValid_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view MultipartStreamParser::Buffer::view() const
|
|
||||||
{
|
|
||||||
return {buffer_.data() + bufHead_, size()};
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultipartStreamParser::Buffer::append(const char *data, size_t length)
|
|
||||||
{
|
|
||||||
size_t remainSize = size();
|
|
||||||
// Move existing data to the front
|
|
||||||
if (remainSize > 0 && bufHead_ > 0)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < remainSize; i++)
|
|
||||||
{
|
|
||||||
buffer_[i] = buffer_[bufHead_ + i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bufHead_ = 0;
|
|
||||||
bufTail_ = remainSize;
|
|
||||||
|
|
||||||
if (remainSize + length > buffer_.size())
|
|
||||||
{
|
|
||||||
buffer_.resize(remainSize + length);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < length; ++i)
|
|
||||||
{
|
|
||||||
buffer_[bufTail_ + i] = data[i];
|
|
||||||
}
|
|
||||||
bufTail_ += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t MultipartStreamParser::Buffer::size() const
|
|
||||||
{
|
|
||||||
return bufTail_ - bufHead_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultipartStreamParser::Buffer::eraseFront(size_t length)
|
|
||||||
{
|
|
||||||
assert(length <= size());
|
|
||||||
bufHead_ += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MultipartStreamParser::Buffer::clear()
|
|
||||||
{
|
|
||||||
buffer_.clear();
|
|
||||||
bufHead_ = 0;
|
|
||||||
bufTail_ = 0;
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @file MultipartStreamParser.h
|
|
||||||
* @author Nitromelon
|
|
||||||
*
|
|
||||||
* Copyright 2024, Nitromelon. All rights reserved.
|
|
||||||
* https://github.com/drogonframework/drogon
|
|
||||||
* Use of this source code is governed by a MIT license
|
|
||||||
* that can be found in the License file.
|
|
||||||
*
|
|
||||||
* Drogon
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <drogon/exports.h>
|
|
||||||
#include <drogon/RequestStream.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace drogon
|
|
||||||
{
|
|
||||||
class DROGON_EXPORT MultipartStreamParser
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MultipartStreamParser(const std::string &contentType);
|
|
||||||
|
|
||||||
void parse(const char *data,
|
|
||||||
size_t length,
|
|
||||||
const RequestStreamReader::MultipartHeaderCallback &headerCb,
|
|
||||||
const RequestStreamReader::StreamDataCallback &dataCb);
|
|
||||||
|
|
||||||
bool isFinished() const
|
|
||||||
{
|
|
||||||
return isFinished_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const
|
|
||||||
{
|
|
||||||
return isValid_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const std::string dash_ = "--";
|
|
||||||
const std::string crlf_ = "\r\n";
|
|
||||||
std::string boundary_;
|
|
||||||
std::string dashBoundaryCrlf_;
|
|
||||||
std::string crlfDashBoundary_;
|
|
||||||
|
|
||||||
struct Buffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
std::string_view view() const;
|
|
||||||
void append(const char *data, size_t length);
|
|
||||||
size_t size() const;
|
|
||||||
void eraseFront(size_t length);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string buffer_;
|
|
||||||
size_t bufHead_{0};
|
|
||||||
size_t bufTail_{0};
|
|
||||||
} buffer_;
|
|
||||||
|
|
||||||
enum class Status
|
|
||||||
{
|
|
||||||
kExpectFirstBoundary = 0,
|
|
||||||
kExpectNewEntry = 1,
|
|
||||||
kExpectHeader = 2,
|
|
||||||
kExpectBody = 3,
|
|
||||||
kExpectEndOrNewEntry = 4,
|
|
||||||
} status_{Status::kExpectFirstBoundary};
|
|
||||||
|
|
||||||
MultipartHeader currentHeader_;
|
|
||||||
bool isValid_{true};
|
|
||||||
bool isFinished_{false};
|
|
||||||
};
|
|
||||||
} // namespace drogon
|
|
@ -12,7 +12,7 @@ using namespace drogon::plugin;
|
|||||||
void PromExporter::initAndStart(const Json::Value &config)
|
void PromExporter::initAndStart(const Json::Value &config)
|
||||||
{
|
{
|
||||||
path_ = config.get("path", path_).asString();
|
path_ = config.get("path", path_).asString();
|
||||||
LOG_TRACE << path_;
|
LOG_ERROR << path_;
|
||||||
auto &app = drogon::app();
|
auto &app = drogon::app();
|
||||||
std::weak_ptr<PromExporter> weakPtr = shared_from_this();
|
std::weak_ptr<PromExporter> weakPtr = shared_from_this();
|
||||||
app.registerHandler(
|
app.registerHandler(
|
||||||
@ -28,7 +28,6 @@ void PromExporter::initAndStart(const Json::Value &config)
|
|||||||
}
|
}
|
||||||
auto resp = HttpResponse::newHttpResponse();
|
auto resp = HttpResponse::newHttpResponse();
|
||||||
resp->setBody(thisPtr->exportMetrics());
|
resp->setBody(thisPtr->exportMetrics());
|
||||||
resp->setContentTypeCode(CT_TEXT_PLAIN);
|
|
||||||
resp->setExpiredTime(5);
|
resp->setExpiredTime(5);
|
||||||
callback(resp);
|
callback(resp);
|
||||||
},
|
},
|
||||||
@ -119,19 +118,19 @@ static std::string exportCollector(
|
|||||||
.append(collector->name())
|
.append(collector->name())
|
||||||
.append(" ")
|
.append(" ")
|
||||||
.append(collector->help())
|
.append(collector->help())
|
||||||
.append("\n");
|
.append("\r\n");
|
||||||
res.append("# TYPE ")
|
res.append("# TYPE ")
|
||||||
.append(collector->name())
|
.append(collector->name())
|
||||||
.append(" ")
|
.append(" ")
|
||||||
.append(collector->type())
|
.append(collector->type())
|
||||||
.append("\n");
|
.append("\r\n");
|
||||||
for (auto const &sampleGroup : sampleGroups)
|
for (auto const &sampleGroup : sampleGroups)
|
||||||
{
|
{
|
||||||
auto const &metricPtr = sampleGroup.metric;
|
auto const &metricPtr = sampleGroup.metric;
|
||||||
auto const &samples = sampleGroup.samples;
|
auto const &samples = sampleGroup.samples;
|
||||||
for (auto &sample : samples)
|
for (auto &sample : samples)
|
||||||
{
|
{
|
||||||
res.append(sample.name);
|
res.append(metricPtr->name());
|
||||||
if (!sample.exLabels.empty() || !metricPtr->labels().empty())
|
if (!sample.exLabels.empty() || !metricPtr->labels().empty())
|
||||||
{
|
{
|
||||||
res.append("{");
|
res.append("{");
|
||||||
@ -158,11 +157,11 @@ static std::string exportCollector(
|
|||||||
res.append(" ")
|
res.append(" ")
|
||||||
.append(std::to_string(
|
.append(std::to_string(
|
||||||
sample.timestamp.microSecondsSinceEpoch() / 1000))
|
sample.timestamp.microSecondsSinceEpoch() / 1000))
|
||||||
.append("\n");
|
.append("\r\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
res.append("\n");
|
res.append("\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,20 +96,21 @@ void RealIpResolver::initAndStart(const Json::Value &config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Json::Value &trustIps = config["trust_ips"];
|
const Json::Value &trustIps = config["trust_ips"];
|
||||||
if (!trustIps.isNull() && !trustIps.isArray())
|
if (!trustIps.isArray())
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Invalid trusted_ips. Should be array.");
|
throw std::runtime_error("Invalid trusted_ips. Should be array.");
|
||||||
}
|
}
|
||||||
for (const auto &ipOrCidr : trustIps)
|
for (const auto &elem : trustIps)
|
||||||
{
|
{
|
||||||
trustCIDRs_.emplace_back(ipOrCidr.asString());
|
std::string ipOrCidr = elem.asString();
|
||||||
|
trustCIDRs_.emplace_back(ipOrCidr);
|
||||||
}
|
}
|
||||||
|
|
||||||
drogon::app().registerPreRoutingAdvice([this](const HttpRequestPtr &req) {
|
drogon::app().registerPreRoutingAdvice([this](const HttpRequestPtr &req) {
|
||||||
const auto &headers = req->headers();
|
const auto &headers = req->headers();
|
||||||
auto ipHeaderFind = headers.find(fromHeader_);
|
auto ipHeaderFind = headers.find(fromHeader_);
|
||||||
const trantor::InetAddress &peerAddr = req->getPeerAddr();
|
const trantor::InetAddress &peerAddr = req->getPeerAddr();
|
||||||
if (ipHeaderFind == headers.end() || !matchCidr(peerAddr, trustCIDRs_))
|
if (ipHeaderFind == headers.end() || !matchCidr(peerAddr))
|
||||||
{
|
{
|
||||||
// Target header is empty, or
|
// Target header is empty, or
|
||||||
// direct peer is already a non-proxy
|
// direct peer is already a non-proxy
|
||||||
@ -138,7 +139,7 @@ void RealIpResolver::initAndStart(const Json::Value &config)
|
|||||||
while (!(ip = parser.getNext()).empty())
|
while (!(ip = parser.getNext()).empty())
|
||||||
{
|
{
|
||||||
trantor::InetAddress addr = parseAddress(ip);
|
trantor::InetAddress addr = parseAddress(ip);
|
||||||
if (addr.isUnspecified() || matchCidr(addr, trustCIDRs_))
|
if (addr.isUnspecified() || matchCidr(addr))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -176,10 +177,9 @@ const trantor::InetAddress &RealIpResolver::getRealAddr(
|
|||||||
return attributesPtr->get<trantor::InetAddress>(attributeKey_);
|
return attributesPtr->get<trantor::InetAddress>(attributeKey_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RealIpResolver::matchCidr(const trantor::InetAddress &addr,
|
bool RealIpResolver::matchCidr(const trantor::InetAddress &addr) const
|
||||||
const CIDRs &trustCIDRs)
|
|
||||||
{
|
{
|
||||||
for (const auto &cidr : trustCIDRs)
|
for (auto &cidr : trustCIDRs_)
|
||||||
{
|
{
|
||||||
if ((addr.ipNetEndian() & cidr.mask_) == cidr.addr_)
|
if ((addr.ipNetEndian() & cidr.mask_) == cidr.addr_)
|
||||||
{
|
{
|
||||||
|
@ -30,10 +30,6 @@ void Redirector::initAndStart(const Json::Value &config)
|
|||||||
}
|
}
|
||||||
std::string protocol, host;
|
std::string protocol, host;
|
||||||
bool pathChanged{false};
|
bool pathChanged{false};
|
||||||
for (auto &handler : thisPtr->pathRewriteHandlers_)
|
|
||||||
{
|
|
||||||
pathChanged |= handler(req);
|
|
||||||
}
|
|
||||||
for (auto &handler : thisPtr->handlers_)
|
for (auto &handler : thisPtr->handlers_)
|
||||||
{
|
{
|
||||||
if (!handler(req, protocol, host, pathChanged))
|
if (!handler(req, protocol, host, pathChanged))
|
||||||
@ -41,6 +37,10 @@ void Redirector::initAndStart(const Json::Value &config)
|
|||||||
return HttpResponse::newNotFoundResponse(req);
|
return HttpResponse::newNotFoundResponse(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto &handler : thisPtr->pathRewriteHandlers_)
|
||||||
|
{
|
||||||
|
pathChanged |= handler(req);
|
||||||
|
}
|
||||||
if (!protocol.empty() || !host.empty() || pathChanged)
|
if (!protocol.empty() || !host.empty() || pathChanged)
|
||||||
{
|
{
|
||||||
std::string url;
|
std::string url;
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @file RequestStream.cc
|
|
||||||
* @author Nitromelon
|
|
||||||
*
|
|
||||||
* Copyright 2024, Nitromelon. All rights reserved.
|
|
||||||
* https://github.com/drogonframework/drogon
|
|
||||||
* Use of this source code is governed by a MIT license
|
|
||||||
* that can be found in the License file.
|
|
||||||
*
|
|
||||||
* Drogon
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "MultipartStreamParser.h"
|
|
||||||
#include "HttpRequestImpl.h"
|
|
||||||
|
|
||||||
#include <drogon/RequestStream.h>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
namespace drogon
|
|
||||||
{
|
|
||||||
class RequestStreamImpl : public RequestStream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RequestStreamImpl(const HttpRequestImplPtr &req) : weakReq_(req)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~RequestStreamImpl() override
|
|
||||||
{
|
|
||||||
if (isSet_.exchange(true))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop all data if no reader is set
|
|
||||||
if (auto req = weakReq_.lock())
|
|
||||||
{
|
|
||||||
setHandlerInLoop(req, RequestStreamReader::newNullReader());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStreamReader(RequestStreamReaderPtr reader) override
|
|
||||||
{
|
|
||||||
if (isSet_.exchange(true))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto req = weakReq_.lock())
|
|
||||||
{
|
|
||||||
setHandlerInLoop(req, std::move(reader));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHandlerInLoop(const HttpRequestImplPtr &req,
|
|
||||||
RequestStreamReaderPtr reader)
|
|
||||||
{
|
|
||||||
if (!req->isStreamMode())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto loop = req->getLoop();
|
|
||||||
if (loop->isInLoopThread())
|
|
||||||
{
|
|
||||||
req->setStreamReader(std::move(reader));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
loop->queueInLoop([req, reader = std::move(reader)]() mutable {
|
|
||||||
req->setStreamReader(std::move(reader));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::weak_ptr<HttpRequestImpl> weakReq_;
|
|
||||||
std::atomic_bool isSet_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace internal
|
|
||||||
{
|
|
||||||
RequestStreamPtr createRequestStream(const HttpRequestPtr &req)
|
|
||||||
{
|
|
||||||
auto reqImpl = std::static_pointer_cast<HttpRequestImpl>(req);
|
|
||||||
if (!reqImpl->isStreamMode())
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return std::make_shared<RequestStreamImpl>(
|
|
||||||
std::static_pointer_cast<HttpRequestImpl>(req));
|
|
||||||
}
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A default implementation for convenience
|
|
||||||
*/
|
|
||||||
class DefaultStreamReader : public RequestStreamReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DefaultStreamReader(StreamDataCallback dataCb,
|
|
||||||
StreamFinishCallback finishCb)
|
|
||||||
: dataCb_(std::move(dataCb)), finishCb_(std::move(finishCb))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamData(const char *data, size_t length) override
|
|
||||||
{
|
|
||||||
dataCb_(data, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamFinish(std::exception_ptr ex) override
|
|
||||||
{
|
|
||||||
finishCb_(std::move(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
StreamDataCallback dataCb_;
|
|
||||||
StreamFinishCallback finishCb_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drops all data
|
|
||||||
*/
|
|
||||||
class NullStreamReader : public RequestStreamReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void onStreamData(const char *, size_t length) override
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamFinish(std::exception_ptr) override
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse multipart data and return actual content
|
|
||||||
*/
|
|
||||||
class MultipartStreamReader : public RequestStreamReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MultipartStreamReader(const std::string &contentType,
|
|
||||||
MultipartHeaderCallback headerCb,
|
|
||||||
StreamDataCallback dataCb,
|
|
||||||
StreamFinishCallback finishCb)
|
|
||||||
: parser_(contentType),
|
|
||||||
headerCb_(std::move(headerCb)),
|
|
||||||
dataCb_(std::move(dataCb)),
|
|
||||||
finishCb_(std::move(finishCb))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamData(const char *data, size_t length) override
|
|
||||||
{
|
|
||||||
if (!parser_.isValid() || parser_.isFinished())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
parser_.parse(data, length, headerCb_, dataCb_);
|
|
||||||
if (!parser_.isValid())
|
|
||||||
{
|
|
||||||
// TODO: should we mix stream error and user error?
|
|
||||||
finishCb_(std::make_exception_ptr(
|
|
||||||
std::runtime_error("invalid multipart data")));
|
|
||||||
}
|
|
||||||
else if (parser_.isFinished())
|
|
||||||
{
|
|
||||||
finishCb_({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStreamFinish(std::exception_ptr ex) override
|
|
||||||
{
|
|
||||||
if (!parser_.isValid() || parser_.isFinished())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ex)
|
|
||||||
{
|
|
||||||
finishCb_(std::make_exception_ptr(
|
|
||||||
std::runtime_error("incomplete multipart data")));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
finishCb_(std::move(ex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
MultipartStreamParser parser_;
|
|
||||||
MultipartHeaderCallback headerCb_;
|
|
||||||
StreamDataCallback dataCb_;
|
|
||||||
StreamFinishCallback finishCb_;
|
|
||||||
};
|
|
||||||
|
|
||||||
RequestStreamReaderPtr RequestStreamReader::newReader(
|
|
||||||
StreamDataCallback dataCb,
|
|
||||||
StreamFinishCallback finishCb)
|
|
||||||
{
|
|
||||||
return std::make_shared<DefaultStreamReader>(std::move(dataCb),
|
|
||||||
std::move(finishCb));
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestStreamReaderPtr RequestStreamReader::newNullReader()
|
|
||||||
{
|
|
||||||
return std::make_shared<NullStreamReader>();
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestStreamReaderPtr RequestStreamReader::newMultipartReader(
|
|
||||||
const HttpRequestPtr &req,
|
|
||||||
MultipartHeaderCallback headerCb,
|
|
||||||
StreamDataCallback dataCb,
|
|
||||||
StreamFinishCallback finishCb)
|
|
||||||
{
|
|
||||||
return std::make_shared<MultipartStreamReader>(req->getHeader(
|
|
||||||
"content-type"),
|
|
||||||
std::move(headerCb),
|
|
||||||
std::move(dataCb),
|
|
||||||
std::move(finishCb));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace drogon
|
|
@ -44,7 +44,7 @@ void StaticFileRouter::init(const std::vector<trantor::EventLoop *> &ioLoops)
|
|||||||
size_t i) {
|
size_t i) {
|
||||||
assert(i == ioLoops[i]->index());
|
assert(i == ioLoops[i]->index());
|
||||||
mapPtr = std::make_unique<CacheMap<std::string, char>>(ioLoops[i],
|
mapPtr = std::make_unique<CacheMap<std::string, char>>(ioLoops[i],
|
||||||
1.0f,
|
1.0,
|
||||||
4,
|
4,
|
||||||
50);
|
50);
|
||||||
});
|
});
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <random>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
@ -84,12 +83,12 @@ namespace drogon
|
|||||||
{
|
{
|
||||||
namespace utils
|
namespace utils
|
||||||
{
|
{
|
||||||
static constexpr std::string_view base64Chars =
|
static const std::string base64Chars =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
"0123456789+/";
|
"0123456789+/";
|
||||||
|
|
||||||
static constexpr std::string_view urlBase64Chars =
|
static const std::string urlBase64Chars =
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
"0123456789-_";
|
"0123456789-_";
|
||||||
@ -163,16 +162,28 @@ bool isBase64(std::string_view str)
|
|||||||
|
|
||||||
std::string genRandomString(int length)
|
std::string genRandomString(int length)
|
||||||
{
|
{
|
||||||
static const std::string_view char_space =
|
static const char char_space[] =
|
||||||
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
std::uniform_int_distribution<size_t> dist(0, char_space.size() - 1);
|
static std::once_flag once;
|
||||||
thread_local std::mt19937 rng(std::random_device{}());
|
static const size_t len = strlen(char_space);
|
||||||
|
static const int randMax = RAND_MAX - (RAND_MAX % len);
|
||||||
|
std::call_once(once, []() {
|
||||||
|
std::srand(static_cast<unsigned int>(time(nullptr)));
|
||||||
|
});
|
||||||
|
|
||||||
|
int i;
|
||||||
std::string str;
|
std::string str;
|
||||||
str.resize(length);
|
str.resize(length);
|
||||||
for (char &ch : str)
|
|
||||||
|
for (i = 0; i < length; ++i)
|
||||||
{
|
{
|
||||||
ch = char_space[dist(rng)];
|
int x = std::rand();
|
||||||
|
while (x >= randMax)
|
||||||
|
{
|
||||||
|
x = std::rand();
|
||||||
|
}
|
||||||
|
x = (x % len);
|
||||||
|
str[i] = char_space[x];
|
||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
@ -429,33 +440,33 @@ std::string getUuid(bool lowercase)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void base64Encode(const unsigned char *bytesToEncode,
|
std::string base64Encode(const unsigned char *bytes_to_encode,
|
||||||
size_t inLen,
|
size_t in_len,
|
||||||
unsigned char *outputBuffer,
|
bool url_safe,
|
||||||
bool urlSafe,
|
bool padded)
|
||||||
bool padded)
|
|
||||||
{
|
{
|
||||||
|
std::string ret;
|
||||||
|
ret.reserve(base64EncodedLength(in_len, padded));
|
||||||
int i = 0;
|
int i = 0;
|
||||||
unsigned char charArray3[3];
|
unsigned char char_array_3[3];
|
||||||
unsigned char charArray4[4];
|
unsigned char char_array_4[4];
|
||||||
|
|
||||||
const std::string_view charSet = urlSafe ? urlBase64Chars : base64Chars;
|
const std::string &charSet = url_safe ? urlBase64Chars : base64Chars;
|
||||||
|
|
||||||
size_t a = 0;
|
while (in_len--)
|
||||||
while (inLen--)
|
|
||||||
{
|
{
|
||||||
charArray3[i++] = *(bytesToEncode++);
|
char_array_3[i++] = *(bytes_to_encode++);
|
||||||
if (i == 3)
|
if (i == 3)
|
||||||
{
|
{
|
||||||
charArray4[0] = (charArray3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
charArray4[1] =
|
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
|
||||||
((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4);
|
((char_array_3[1] & 0xf0) >> 4);
|
||||||
charArray4[2] =
|
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
|
||||||
((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6);
|
((char_array_3[2] & 0xc0) >> 6);
|
||||||
charArray4[3] = charArray3[2] & 0x3f;
|
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||||
|
|
||||||
for (i = 0; (i < 4); ++i, ++a)
|
for (i = 0; (i < 4); ++i)
|
||||||
outputBuffer[a] = charSet[charArray4[i]];
|
ret += charSet[char_array_4[i]];
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,61 +474,59 @@ void base64Encode(const unsigned char *bytesToEncode,
|
|||||||
if (i)
|
if (i)
|
||||||
{
|
{
|
||||||
for (int j = i; j < 3; ++j)
|
for (int j = i; j < 3; ++j)
|
||||||
charArray3[j] = '\0';
|
char_array_3[j] = '\0';
|
||||||
|
|
||||||
charArray4[0] = (charArray3[0] & 0xfc) >> 2;
|
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||||
charArray4[1] =
|
char_array_4[1] =
|
||||||
((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4);
|
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||||
charArray4[2] =
|
char_array_4[2] =
|
||||||
((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6);
|
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||||
charArray4[3] = charArray3[2] & 0x3f;
|
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||||
|
|
||||||
for (int j = 0; (j <= i); ++j, ++a)
|
for (int j = 0; (j <= i); ++j)
|
||||||
outputBuffer[a] = charSet[charArray4[j]];
|
ret += charSet[char_array_4[j]];
|
||||||
|
|
||||||
if (padded)
|
if (padded)
|
||||||
while ((++i < 4))
|
while ((++i < 4))
|
||||||
{
|
ret += '=';
|
||||||
outputBuffer[a] = '=';
|
|
||||||
++a;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<char> base64DecodeToVector(std::string_view encodedString)
|
std::vector<char> base64DecodeToVector(std::string_view encoded_string)
|
||||||
{
|
{
|
||||||
auto inLen = encodedString.size();
|
auto in_len = encoded_string.size();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int in_{0};
|
int in_{0};
|
||||||
char charArray4[4], charArray3[3];
|
char char_array_4[4], char_array_3[3];
|
||||||
std::vector<char> ret;
|
std::vector<char> ret;
|
||||||
ret.reserve(base64DecodedLength(inLen));
|
ret.reserve(base64DecodedLength(in_len));
|
||||||
|
|
||||||
while (inLen-- && (encodedString[in_] != '='))
|
while (in_len-- && (encoded_string[in_] != '='))
|
||||||
{
|
{
|
||||||
if (!isBase64(encodedString[in_]))
|
if (!isBase64(encoded_string[in_]))
|
||||||
{
|
{
|
||||||
++in_;
|
++in_;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
charArray4[i++] = encodedString[in_];
|
char_array_4[i++] = encoded_string[in_];
|
||||||
++in_;
|
++in_;
|
||||||
if (i == 4)
|
if (i == 4)
|
||||||
{
|
{
|
||||||
for (i = 0; i < 4; ++i)
|
for (i = 0; i < 4; ++i)
|
||||||
{
|
{
|
||||||
charArray4[i] = base64CharMap.getIndex(charArray4[i]);
|
char_array_4[i] = base64CharMap.getIndex(char_array_4[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
charArray3[0] =
|
char_array_3[0] =
|
||||||
(charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4);
|
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
charArray3[1] =
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
|
||||||
((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2);
|
((char_array_4[2] & 0x3c) >> 2);
|
||||||
charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3];
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
for (i = 0; (i < 3); ++i)
|
for (i = 0; (i < 3); ++i)
|
||||||
ret.push_back(charArray3[i]);
|
ret.push_back(char_array_3[i]);
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -525,59 +534,60 @@ std::vector<char> base64DecodeToVector(std::string_view encodedString)
|
|||||||
if (i)
|
if (i)
|
||||||
{
|
{
|
||||||
for (int j = i; j < 4; ++j)
|
for (int j = i; j < 4; ++j)
|
||||||
charArray4[j] = 0;
|
char_array_4[j] = 0;
|
||||||
|
|
||||||
for (int j = 0; j < 4; ++j)
|
for (int j = 0; j < 4; ++j)
|
||||||
{
|
{
|
||||||
charArray4[j] = base64CharMap.getIndex(charArray4[j]);
|
char_array_4[j] = base64CharMap.getIndex(char_array_4[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4);
|
char_array_3[0] =
|
||||||
charArray3[1] =
|
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2);
|
char_array_3[1] =
|
||||||
charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3];
|
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
--i;
|
--i;
|
||||||
for (int j = 0; (j < i); ++j)
|
for (int j = 0; (j < i); ++j)
|
||||||
ret.push_back(charArray3[j]);
|
ret.push_back(char_array_3[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t base64Decode(const char *encodedString,
|
std::string base64Decode(std::string_view encoded_string)
|
||||||
size_t inLen,
|
|
||||||
unsigned char *outputBuffer)
|
|
||||||
{
|
{
|
||||||
|
auto in_len = encoded_string.size();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int in_{0};
|
int in_{0};
|
||||||
unsigned char charArray4[4], charArray3[3];
|
unsigned char char_array_4[4], char_array_3[3];
|
||||||
|
std::string ret;
|
||||||
|
ret.reserve(base64DecodedLength(in_len));
|
||||||
|
|
||||||
size_t a = 0;
|
while (in_len-- && (encoded_string[in_] != '='))
|
||||||
while (inLen-- && (encodedString[in_] != '='))
|
|
||||||
{
|
{
|
||||||
if (!isBase64(encodedString[in_]))
|
if (!isBase64(encoded_string[in_]))
|
||||||
{
|
{
|
||||||
++in_;
|
++in_;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
charArray4[i++] = encodedString[in_];
|
char_array_4[i++] = encoded_string[in_];
|
||||||
++in_;
|
++in_;
|
||||||
if (i == 4)
|
if (i == 4)
|
||||||
{
|
{
|
||||||
for (i = 0; i < 4; ++i)
|
for (i = 0; i < 4; ++i)
|
||||||
{
|
{
|
||||||
charArray4[i] = base64CharMap.getIndex(charArray4[i]);
|
char_array_4[i] = base64CharMap.getIndex(char_array_4[i]);
|
||||||
}
|
}
|
||||||
charArray3[0] =
|
char_array_3[0] =
|
||||||
(charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4);
|
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
charArray3[1] =
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) +
|
||||||
((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2);
|
((char_array_4[2] & 0x3c) >> 2);
|
||||||
charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3];
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
for (i = 0; (i < 3); ++i, ++a)
|
for (i = 0; (i < 3); ++i)
|
||||||
outputBuffer[a] = charArray3[i];
|
ret += char_array_3[i];
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -585,24 +595,25 @@ size_t base64Decode(const char *encodedString,
|
|||||||
if (i)
|
if (i)
|
||||||
{
|
{
|
||||||
for (int j = i; j < 4; ++j)
|
for (int j = i; j < 4; ++j)
|
||||||
charArray4[j] = 0;
|
char_array_4[j] = 0;
|
||||||
|
|
||||||
for (int j = 0; j < 4; ++j)
|
for (int j = 0; j < 4; ++j)
|
||||||
{
|
{
|
||||||
charArray4[j] = base64CharMap.getIndex(charArray4[j]);
|
char_array_4[j] = base64CharMap.getIndex(char_array_4[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4);
|
char_array_3[0] =
|
||||||
charArray3[1] =
|
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
||||||
((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2);
|
char_array_3[1] =
|
||||||
charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3];
|
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
||||||
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
||||||
|
|
||||||
--i;
|
--i;
|
||||||
for (int j = 0; (j < i); ++j, ++a)
|
for (int j = 0; (j < i); ++j)
|
||||||
outputBuffer[a] = charArray3[j];
|
ret += char_array_3[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
return a;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string charToHex(char c)
|
static std::string charToHex(char c)
|
||||||
@ -1034,35 +1045,6 @@ char *getHttpFullDate(const trantor::Date &date)
|
|||||||
return lastTimeString;
|
return lastTimeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dateToCustomFormattedString(const std::string &fmtStr,
|
|
||||||
std::string &str,
|
|
||||||
const trantor::Date &date)
|
|
||||||
{
|
|
||||||
auto nowSecond = date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
|
|
||||||
time_t seconds = static_cast<time_t>(nowSecond);
|
|
||||||
struct tm tm_LValue = date.tmStruct();
|
|
||||||
std::stringstream Out;
|
|
||||||
Out.imbue(std::locale{"C"});
|
|
||||||
Out << std::put_time(&tm_LValue, fmtStr.c_str());
|
|
||||||
str = Out.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &getHttpFullDateStr(const trantor::Date &date)
|
|
||||||
{
|
|
||||||
static thread_local int64_t lastSecond = 0;
|
|
||||||
static thread_local std::string lastTimeString(128, 0);
|
|
||||||
auto nowSecond = date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC;
|
|
||||||
if (nowSecond == lastSecond)
|
|
||||||
{
|
|
||||||
return lastTimeString;
|
|
||||||
}
|
|
||||||
lastSecond = nowSecond;
|
|
||||||
dateToCustomFormattedString("%a, %d %b %Y %H:%M:%S GMT",
|
|
||||||
lastTimeString,
|
|
||||||
date);
|
|
||||||
return lastTimeString;
|
|
||||||
}
|
|
||||||
|
|
||||||
trantor::Date getHttpDate(const std::string &httpFullDateString)
|
trantor::Date getHttpDate(const std::string &httpFullDateString)
|
||||||
{
|
{
|
||||||
static const std::array<const char *, 4> formats = {
|
static const std::array<const char *, 4> formats = {
|
||||||
|
@ -427,7 +427,7 @@ void WebSocketClientImpl::sendReq(const trantor::TcpConnectionPtr &connPtr)
|
|||||||
trantor::MsgBuffer buffer;
|
trantor::MsgBuffer buffer;
|
||||||
assert(upgradeRequest_);
|
assert(upgradeRequest_);
|
||||||
auto implPtr = static_cast<HttpRequestImpl *>(upgradeRequest_.get());
|
auto implPtr = static_cast<HttpRequestImpl *>(upgradeRequest_.get());
|
||||||
implPtr->appendToBuffer(&buffer);
|
implPtr->appendToBuffer(&buffer, Version::kHttp11);
|
||||||
LOG_TRACE << "Send request:"
|
LOG_TRACE << "Send request:"
|
||||||
<< std::string(buffer.peek(), buffer.readableBytes());
|
<< std::string(buffer.peek(), buffer.readableBytes());
|
||||||
connPtr->send(std::move(buffer));
|
connPtr->send(std::move(buffer));
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
#include <json/writer.h>
|
#include <json/writer.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
using namespace drogon;
|
using namespace drogon;
|
||||||
|
|
||||||
@ -269,14 +268,14 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
|||||||
{
|
{
|
||||||
// According to the rfc6455
|
// According to the rfc6455
|
||||||
gotAll_ = false;
|
gotAll_ = false;
|
||||||
while (buffer->readableBytes() >= 2)
|
if (buffer->readableBytes() >= 2)
|
||||||
{
|
{
|
||||||
unsigned char opcode = (*buffer)[0] & 0x0f;
|
unsigned char opcode = (*buffer)[0] & 0x0f;
|
||||||
bool isControlFrame = false;
|
bool isControlFrame = false;
|
||||||
switch (opcode)
|
switch (opcode)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
LOG_TRACE << "continuation frame";
|
// continuation frame
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
type_ = WebSocketMessageType::Text;
|
type_ = WebSocketMessageType::Text;
|
||||||
@ -328,13 +327,8 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
|||||||
{
|
{
|
||||||
indexFirstMask = 10;
|
indexFirstMask = 10;
|
||||||
}
|
}
|
||||||
if (indexFirstMask > 2)
|
if (indexFirstMask > 2 && buffer->readableBytes() >= indexFirstMask)
|
||||||
{
|
{
|
||||||
if (buffer->readableBytes() < indexFirstMask)
|
|
||||||
{
|
|
||||||
// Not enough data yet, wait for more.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isControlFrame)
|
if (isControlFrame)
|
||||||
{
|
{
|
||||||
// rfc6455-5.5
|
// rfc6455-5.5
|
||||||
@ -350,17 +344,14 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
|||||||
}
|
}
|
||||||
else if (indexFirstMask == 10)
|
else if (indexFirstMask == 10)
|
||||||
{
|
{
|
||||||
length = 0;
|
length = (unsigned char)(*buffer)[2];
|
||||||
for (int i = 2; i <= 9; ++i)
|
length = (length << 8) + (unsigned char)(*buffer)[3];
|
||||||
{
|
length = (length << 8) + (unsigned char)(*buffer)[4];
|
||||||
if (length > ((std::numeric_limits<size_t>::max)() >> 8))
|
length = (length << 8) + (unsigned char)(*buffer)[5];
|
||||||
{
|
length = (length << 8) + (unsigned char)(*buffer)[6];
|
||||||
LOG_ERROR
|
length = (length << 8) + (unsigned char)(*buffer)[7];
|
||||||
<< "Payload length too large to handle safely";
|
length = (length << 8) + (unsigned char)(*buffer)[8];
|
||||||
return false;
|
length = (length << 8) + (unsigned char)(*buffer)[9];
|
||||||
}
|
|
||||||
length = (length << 8) + (unsigned char)(*buffer)[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -389,16 +380,9 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
|||||||
{
|
{
|
||||||
message_[oldLen + i] = (rawData[i] ^ masks[i % 4]);
|
message_[oldLen + i] = (rawData[i] ^ masks[i % 4]);
|
||||||
}
|
}
|
||||||
buffer->retrieve(indexFirstMask + 4 + length);
|
|
||||||
if (isFin)
|
if (isFin)
|
||||||
{
|
|
||||||
gotAll_ = true;
|
gotAll_ = true;
|
||||||
return true;
|
buffer->retrieve(indexFirstMask + 4 + length);
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not enough data yet, wait for more.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,16 +392,9 @@ bool WebSocketMessageParser::parse(trantor::MsgBuffer *buffer)
|
|||||||
{
|
{
|
||||||
auto rawData = buffer->peek() + indexFirstMask;
|
auto rawData = buffer->peek() + indexFirstMask;
|
||||||
message_.append(rawData, length);
|
message_.append(rawData, length);
|
||||||
buffer->retrieve(indexFirstMask + length);
|
|
||||||
if (isFin)
|
if (isFin)
|
||||||
{
|
|
||||||
gotAll_ = true;
|
gotAll_ = true;
|
||||||
return true;
|
buffer->retrieve(indexFirstMask + length);
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Not enough data yet, wait for more.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user