Add redis support (#719)

This commit is contained in:
An Tao 2021-03-13 19:13:54 +08:00 committed by GitHub
parent df04c47f74
commit 6d9aa3b44c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 2866 additions and 558 deletions

View File

@ -45,7 +45,8 @@ jobs:
if: runner.os == 'macOS'
run: |
brew install jsoncpp ossp-uuid brotli zlib
brew install openssl lz4 mariadb sqlite3 postgresql
brew install openssl lz4 mariadb sqlite3 postgresql hiredis
brew install redis
- name: (Linux) Install dependencies
if: runner.os == 'Linux'
@ -112,6 +113,7 @@ jobs:
brew tap homebrew/services;
brew services restart postgresql;
brew services start mariadb;
brew services start redis;
sleep 4;
mariadb -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('')";
mariadb -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost'";
@ -135,7 +137,7 @@ jobs:
run: ./test.sh -t
- name: Lint
if: runner.os == 'Linux'
if: matrix.os == 'ubuntu-20.04'
working-directory: ${{env.GITHUB_WORKSPACE}}
shell: bash
run: ./format.sh && git diff --exit-code

View File

@ -4,19 +4,18 @@ project(drogon)
message(STATUS "compiler: " ${CMAKE_CXX_COMPILER_ID})
include(CheckCXXSourceRuns)
check_cxx_source_runs(
"int main(){return 0;}"
not_cross_compiling)
check_cxx_source_runs("int main(){return 0;}" not_cross_compiling)
if(not_cross_compiling)
set(BUILD_PROGRAMS ON)
else()
set(BUILD_PROGRAMS OFF)
endif()
if (not_cross_compiling)
set(BUILD_PROGRAMS ON)
else ()
set(BUILD_PROGRAMS OFF)
endif ()
option(BUILD_CTL "Build drogon_ctl" ${BUILD_PROGRAMS})
option(BUILD_EXAMPLES "Build examples" ${BUILD_PROGRAMS})
option(BUILD_ORM "Build orm" ON)
option(BUILD_REDIS "Build redis client" ON)
option(COZ_PROFILING "Use coz for profiling" OFF)
option(LIBPQ_BATCH_MODE "Use batch mode for libpq" ON)
option(BUILD_DROGON_SHARED "Build drogon as a shared lib" OFF)
@ -25,39 +24,39 @@ set(DROGON_MAJOR_VERSION 1)
set(DROGON_MINOR_VERSION 4)
set(DROGON_PATCH_VERSION 1)
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}")
# Offer the user the choice of overriding the installation directories
set(INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries")
set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDE_DIR
include
CACHE PATH "Installation directory for header files")
include
CACHE PATH "Installation directory for header files")
set(DEF_INSTALL_DROGON_CMAKE_DIR lib/cmake/Drogon)
set(INSTALL_DROGON_CMAKE_DIR
${DEF_INSTALL_DROGON_CMAKE_DIR}
CACHE PATH "Installation directory for cmake files")
${DEF_INSTALL_DROGON_CMAKE_DIR}
CACHE PATH "Installation directory for cmake files")
if(BUILD_DROGON_SHARED)
set(BUILD_TRANTOR_SHARED TRUE)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
set(CMAKE_THREAD_LIBS_INIT "-lpthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(CMAKE_HAVE_THREADS_LIBRARY 1)
set(CMAKE_USE_WIN32_THREADS_INIT 0)
set(CMAKE_USE_PTHREADS_INIT 1)
set(THREADS_PREFER_PTHREAD_FLAG ON)
# set(BUILD_EXAMPLES FALSE)
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES
if (BUILD_DROGON_SHARED)
set(BUILD_TRANTOR_SHARED TRUE)
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
set(CMAKE_THREAD_LIBS_INIT "-lpthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(CMAKE_HAVE_THREADS_LIBRARY 1)
set(CMAKE_USE_WIN32_THREADS_INIT 0)
set(CMAKE_USE_PTHREADS_INIT 1)
set(THREADS_PREFER_PTHREAD_FLAG ON)
# set(BUILD_EXAMPLES FALSE)
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES
"${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}" isSystemDir)
if("${isSystemDir}" STREQUAL "-1")
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}")
endif("${isSystemDir}" STREQUAL "-1")
add_library(${PROJECT_NAME} SHARED)
else(BUILD_DROGON_SHARED)
add_library(${PROJECT_NAME} STATIC)
endif(BUILD_DROGON_SHARED)
if ("${isSystemDir}" STREQUAL "-1")
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}")
endif ("${isSystemDir}" STREQUAL "-1")
add_library(${PROJECT_NAME} SHARED)
else (BUILD_DROGON_SHARED)
add_library(${PROJECT_NAME} STATIC)
endif (BUILD_DROGON_SHARED)
include(cmake/DrogonUtilities.cmake)
include(CheckIncludeFileCXX)
@ -65,52 +64,53 @@ 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(HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
set(DROGON_CXX_STANDARD 20)
elseif(HAS_ANY AND HAS_STRING_VIEW)
set(DROGON_CXX_STANDARD 17)
else()
set(DROGON_CXX_STANDARD 14)
endif()
if (HAS_ANY AND HAS_STRING_VIEW AND HAS_COROUTINE)
set(DROGON_CXX_STANDARD 20)
elseif (HAS_ANY AND HAS_STRING_VIEW)
set(DROGON_CXX_STANDARD 17)
else ()
set(DROGON_CXX_STANDARD 14)
endif ()
target_include_directories(
${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/orm_lib/inc>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/trantor>
$<INSTALL_INTERFACE:${INSTALL_INCLUDE_DIR}>)
${PROJECT_NAME}
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib/inc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/orm_lib/inc>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/nosql_lib/redis/inc>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/trantor>
$<INSTALL_INTERFACE:${INSTALL_INCLUDE_DIR}>)
if(WIN32)
target_include_directories(
${PROJECT_NAME}
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/third_party/mman-win32>)
endif(WIN32)
if (WIN32)
target_include_directories(
${PROJECT_NAME}
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/third_party/mman-win32>)
endif (WIN32)
add_subdirectory(trantor)
target_link_libraries(${PROJECT_NAME} PUBLIC trantor)
if(NOT WIN32)
if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
target_link_libraries(${PROJECT_NAME} PRIVATE dl)
endif(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
else(NOT WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi)
endif(NOT WIN32)
if (NOT WIN32)
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
target_link_libraries(${PROJECT_NAME} PRIVATE dl)
endif (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
else (NOT WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi)
endif (NOT WIN32)
if(DROGON_CXX_STANDARD EQUAL 14)
# With C++14, use boost to support any and string_view
message(STATUS "use c++14")
find_package(Boost 1.61.0 REQUIRED)
message(STATUS "boost include dir:" ${Boost_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost)
list(APPEND INCLUDE_DIRS_FOR_DYNAMIC_VIEW ${Boost_INCLUDE_DIR})
elseif(DROGON_CXX_STANDARD EQUAL 17)
message(STATUS "use c++17")
else()
message(STATUS "use c++20")
endif()
if (DROGON_CXX_STANDARD EQUAL 14)
# With C++14, use boost to support any and string_view
message(STATUS "use c++14")
find_package(Boost 1.61.0 REQUIRED)
message(STATUS "boost include dir:" ${Boost_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC Boost::boost)
list(APPEND INCLUDE_DIRS_FOR_DYNAMIC_VIEW ${Boost_INCLUDE_DIR})
elseif (DROGON_CXX_STANDARD EQUAL 17)
message(STATUS "use c++17")
else ()
message(STATUS "use c++20")
endif ()
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
@ -119,261 +119,293 @@ find_package(Jsoncpp REQUIRED)
target_link_libraries(${PROJECT_NAME} PUBLIC Jsoncpp_lib)
list(APPEND INCLUDE_DIRS_FOR_DYNAMIC_VIEW ${JSONCPP_INCLUDE_DIRS})
if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND NOT WIN32)
find_package(UUID REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE UUID_lib)
if (NOT
${CMAKE_SYSTEM_NAME}
STREQUAL
"FreeBSD"
AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD"
AND NOT WIN32)
find_package(UUID REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE UUID_lib)
try_compile(normal_uuid ${CMAKE_BINARY_DIR}/cmaketest
${PROJECT_SOURCE_DIR}/cmake/tests/normal_uuid_lib_test.cc
LINK_LIBRARIES UUID_lib)
try_compile(ossp_uuid ${CMAKE_BINARY_DIR}/cmaketest
${PROJECT_SOURCE_DIR}/cmake/tests/ossp_uuid_lib_test.cc
LINK_LIBRARIES UUID_lib)
if(normal_uuid)
add_definitions(-DUSE_OSSP_UUID=0)
elseif(ossp_uuid)
add_definitions(-DUSE_OSSP_UUID=1)
else()
message(FATAL_ERROR "uuid lib error")
endif()
endif(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND NOT WIN32)
try_compile(normal_uuid ${CMAKE_BINARY_DIR}/cmaketest
${PROJECT_SOURCE_DIR}/cmake/tests/normal_uuid_lib_test.cc
LINK_LIBRARIES UUID_lib)
try_compile(ossp_uuid ${CMAKE_BINARY_DIR}/cmaketest
${PROJECT_SOURCE_DIR}/cmake/tests/ossp_uuid_lib_test.cc
LINK_LIBRARIES UUID_lib)
if (normal_uuid)
add_definitions(-DUSE_OSSP_UUID=0)
elseif (ossp_uuid)
add_definitions(-DUSE_OSSP_UUID=1)
else ()
message(FATAL_ERROR "uuid lib error")
endif ()
endif (NOT
${CMAKE_SYSTEM_NAME}
STREQUAL
"FreeBSD"
AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD"
AND NOT WIN32)
find_package(Brotli)
if(Brotli_FOUND)
message(STATUS "Brotli found")
add_definitions(-DUSE_BROTLI)
target_link_libraries(${PROJECT_NAME} PRIVATE Brotli_lib)
endif(Brotli_FOUND)
if (Brotli_FOUND)
message(STATUS "Brotli found")
add_definitions(-DUSE_BROTLI)
target_link_libraries(${PROJECT_NAME} PRIVATE Brotli_lib)
endif (Brotli_FOUND)
set(DROGON_SOURCES
lib/src/AOPAdvice.cc
lib/src/CacheFile.cc
lib/src/ConfigLoader.cc
lib/src/Cookie.cc
lib/src/DrClassMap.cc
lib/src/DrTemplateBase.cc
lib/src/FiltersFunction.cc
lib/src/HttpAppFrameworkImpl.cc
lib/src/HttpClientImpl.cc
lib/src/HttpControllersRouter.cc
lib/src/HttpFileImpl.cc
lib/src/HttpFileUploadRequest.cc
lib/src/HttpRequestImpl.cc
lib/src/HttpRequestParser.cc
lib/src/HttpResponseImpl.cc
lib/src/HttpResponseParser.cc
lib/src/HttpServer.cc
lib/src/HttpSimpleControllersRouter.cc
lib/src/HttpUtils.cc
lib/src/HttpViewData.cc
lib/src/IntranetIpFilter.cc
lib/src/ListenerManager.cc
lib/src/LocalHostFilter.cc
lib/src/MultiPart.cc
lib/src/NotFound.cc
lib/src/PluginsManager.cc
lib/src/SecureSSLRedirector.cc
lib/src/SessionManager.cc
lib/src/StaticFileRouter.cc
lib/src/Utilities.cc
lib/src/WebSocketClientImpl.cc
lib/src/WebSocketConnectionImpl.cc
lib/src/WebsocketControllersRouter.cc)
lib/src/AOPAdvice.cc
lib/src/CacheFile.cc
lib/src/ConfigLoader.cc
lib/src/Cookie.cc
lib/src/DrClassMap.cc
lib/src/DrTemplateBase.cc
lib/src/FiltersFunction.cc
lib/src/HttpAppFrameworkImpl.cc
lib/src/HttpClientImpl.cc
lib/src/HttpControllersRouter.cc
lib/src/HttpFileImpl.cc
lib/src/HttpFileUploadRequest.cc
lib/src/HttpRequestImpl.cc
lib/src/HttpRequestParser.cc
lib/src/HttpResponseImpl.cc
lib/src/HttpResponseParser.cc
lib/src/HttpServer.cc
lib/src/HttpSimpleControllersRouter.cc
lib/src/HttpUtils.cc
lib/src/HttpViewData.cc
lib/src/IntranetIpFilter.cc
lib/src/ListenerManager.cc
lib/src/LocalHostFilter.cc
lib/src/MultiPart.cc
lib/src/NotFound.cc
lib/src/PluginsManager.cc
lib/src/SecureSSLRedirector.cc
lib/src/SessionManager.cc
lib/src/StaticFileRouter.cc
lib/src/Utilities.cc
lib/src/WebSocketClientImpl.cc
lib/src/WebSocketConnectionImpl.cc
lib/src/WebsocketControllersRouter.cc)
if(NOT WIN32)
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/SharedLibManager.cc)
else(NOT WIN32)
set(DROGON_SOURCES ${DROGON_SOURCES} third_party/mman-win32/mman.c)
endif(NOT WIN32)
if (NOT WIN32)
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/SharedLibManager.cc)
else (NOT WIN32)
set(DROGON_SOURCES ${DROGON_SOURCES} third_party/mman-win32/mman.c)
endif (NOT WIN32)
if(BUILD_ORM)
# find postgres
find_package(pg)
if(pg_FOUND)
message(STATUS "libpq inc path:" ${PG_INCLUDE_DIRS})
message(STATUS "libpq lib:" ${PG_LIBRARIES})
target_link_libraries(${PROJECT_NAME} PRIVATE pg_lib)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PostgreSQLResultImpl.cc)
if(LIBPQ_BATCH_MODE)
try_compile(libpq_supports_batch ${CMAKE_BINARY_DIR}/cmaketest
${PROJECT_SOURCE_DIR}/cmake/tests/test_libpq_batch_mode.cc
LINK_LIBRARIES ${PostgreSQL_LIBRARIES}
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${PostgreSQL_INCLUDE_DIR}")
endif(LIBPQ_BATCH_MODE)
if(libpq_supports_batch)
message(STATUS "The libpq supports batch mode")
option(LIBPQ_SUPPORTS_BATCH_MODE "libpq batch mode" ON)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PgBatchConnection.cc)
else(libpq_supports_batch)
option(LIBPQ_SUPPORTS_BATCH_MODE "libpq batch mode" OFF)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PgConnection.cc)
endif(libpq_supports_batch)
endif(pg_FOUND)
if (BUILD_ORM)
# find postgres
find_package(pg)
if (pg_FOUND)
message(STATUS "libpq inc path:" ${PG_INCLUDE_DIRS})
message(STATUS "libpq lib:" ${PG_LIBRARIES})
target_link_libraries(${PROJECT_NAME} PRIVATE pg_lib)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PostgreSQLResultImpl.cc)
if (LIBPQ_BATCH_MODE)
try_compile(libpq_supports_batch ${CMAKE_BINARY_DIR}/cmaketest
${PROJECT_SOURCE_DIR}/cmake/tests/test_libpq_batch_mode.cc
LINK_LIBRARIES ${PostgreSQL_LIBRARIES}
CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${PostgreSQL_INCLUDE_DIR}")
endif (LIBPQ_BATCH_MODE)
if (libpq_supports_batch)
message(STATUS "The libpq supports batch mode")
option(LIBPQ_SUPPORTS_BATCH_MODE "libpq batch mode" ON)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PgBatchConnection.cc)
else (libpq_supports_batch)
option(LIBPQ_SUPPORTS_BATCH_MODE "libpq batch mode" OFF)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PgConnection.cc)
endif (libpq_supports_batch)
endif (pg_FOUND)
# Find mysql, only mariadb client liberary is supported
find_package(MySQL)
if(MySQL_FOUND)
message(STATUS "Ok! We find the mariadb!")
target_link_libraries(${PROJECT_NAME} PRIVATE MySQL_lib)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/mysql_impl/MysqlConnection.cc
orm_lib/src/mysql_impl/MysqlResultImpl.cc)
endif(MySQL_FOUND)
# Find mysql, only mariadb client liberary is supported
find_package(MySQL)
if (MySQL_FOUND)
message(STATUS "Ok! We find the mariadb!")
target_link_libraries(${PROJECT_NAME} PRIVATE MySQL_lib)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/mysql_impl/MysqlConnection.cc
orm_lib/src/mysql_impl/MysqlResultImpl.cc)
endif (MySQL_FOUND)
# Find sqlite3.
find_package(SQLite3)
if(SQLite3_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE SQLite3_lib)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/sqlite3_impl/Sqlite3Connection.cc
orm_lib/src/sqlite3_impl/Sqlite3ResultImpl.cc)
endif(SQLite3_FOUND)
endif(BUILD_ORM)
# Find sqlite3.
find_package(SQLite3)
if (SQLite3_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE SQLite3_lib)
set(DROGON_SOURCES ${DROGON_SOURCES}
orm_lib/src/sqlite3_impl/Sqlite3Connection.cc
orm_lib/src/sqlite3_impl/Sqlite3ResultImpl.cc)
endif (SQLite3_FOUND)
endif (BUILD_ORM)
find_package(ZLIB REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE ZLIB::ZLIB)
find_package(OpenSSL)
if(OpenSSL_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
else(OpenSSL_FOUND)
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/ssl_funcs/Md5.cc
lib/src/ssl_funcs/Sha1.cc)
endif(OpenSSL_FOUND)
if (OpenSSL_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
else (OpenSSL_FOUND)
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/ssl_funcs/Md5.cc
lib/src/ssl_funcs/Sha1.cc)
endif (OpenSSL_FOUND)
if (BUILD_REDIS)
find_package(Hiredis)
if (Hiredis_FOUND)
add_definitions(-DUSE_REDIS)
target_link_libraries(${PROJECT_NAME} PRIVATE Hiredis_lib)
set(DROGON_SOURCES
${DROGON_SOURCES}
nosql_lib/redis/src/RedisClientImpl.cc
nosql_lib/redis/src/RedisConnection.cc
nosql_lib/redis/src/RedisResult.cc
nosql_lib/redis/src/RedisClientLockFree.cc
nosql_lib/redis/src/RedisClientManager.cc
nosql_lib/redis/src/RedisTransactionImpl.cc)
if (BUILD_TESTING)
add_subdirectory(nosql_lib/redis/tests)
endif (BUILD_TESTING)
else ()
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/RedisClientManagerSkipped.cc)
endif (Hiredis_FOUND)
endif (BUILD_REDIS)
execute_process(COMMAND "git" rev-parse HEAD
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
OUTPUT_VARIABLE GIT_SHA1
ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
configure_file("${PROJECT_SOURCE_DIR}/cmake/templates/version.h.in"
"${PROJECT_SOURCE_DIR}/lib/inc/drogon/version.h" @ONLY)
"${PROJECT_SOURCE_DIR}/lib/inc/drogon/version.h" @ONLY)
if(DROGON_CXX_STANDARD EQUAL 20)
option(USE_COROUTINE "Enable C++20 coroutine support" ON)
else(DROGON_CXX_STANDARD EQUAL 20)
option(USE_COROUTINE "Enable C++20 coroutine support" OFF)
endif(DROGON_CXX_STANDARD EQUAL 20)
if (DROGON_CXX_STANDARD EQUAL 20)
option(USE_COROUTINE "Enable C++20 coroutine support" ON)
else (DROGON_CXX_STANDARD EQUAL 20)
option(USE_COROUTINE "Enable C++20 coroutine support" OFF)
endif (DROGON_CXX_STANDARD EQUAL 20)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif(BUILD_EXAMPLES)
if (BUILD_EXAMPLES)
add_subdirectory(examples)
endif (BUILD_EXAMPLES)
if(BUILD_CTL)
add_subdirectory(drogon_ctl)
endif(BUILD_CTL)
if (BUILD_CTL)
add_subdirectory(drogon_ctl)
endif (BUILD_CTL)
if(COZ_PROFILING)
find_package(coz-profiler REQUIRED)
target_compile_definitions(${PROJECT_NAME} PRIVATE -DCOZ_PROFILING=1)
# If linked will not need to be ran with `coz run --- [executable]` to run the
# profiler, but drogon_ctl currently won't build because it doesn't find debug
# information while trying to generate it's own sources
# target_link_libraries(${PROJECT_NAME} PUBLIC coz::coz)
target_include_directories(${PROJECT_NAME} PUBLIC ${COZ_INCLUDE_DIRS})
endif(COZ_PROFILING)
if (COZ_PROFILING)
find_package(coz-profiler REQUIRED)
target_compile_definitions(${PROJECT_NAME} PRIVATE -DCOZ_PROFILING=1)
# If linked will not need to be ran with `coz run --- [executable]` to run the
# profiler, but drogon_ctl currently won't build because it doesn't find debug
# information while trying to generate it's own sources
# target_link_libraries(${PROJECT_NAME} PUBLIC coz::coz)
target_include_directories(${PROJECT_NAME} PUBLIC ${COZ_INCLUDE_DIRS})
endif (COZ_PROFILING)
if(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
set(DROGON_SOURCES
${DROGON_SOURCES}
orm_lib/src/ArrayParser.cc
orm_lib/src/Criteria.cc
orm_lib/src/DbClient.cc
orm_lib/src/DbClientImpl.cc
orm_lib/src/DbClientLockFree.cc
orm_lib/src/DbClientManager.cc
orm_lib/src/DbConnection.cc
orm_lib/src/Exception.cc
orm_lib/src/Field.cc
orm_lib/src/Result.cc
orm_lib/src/Row.cc
orm_lib/src/SqlBinder.cc
orm_lib/src/TransactionImpl.cc
orm_lib/src/RestfulController.cc)
else(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/DbClientManagerSkipped.cc)
endif(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
if (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
set(DROGON_SOURCES
${DROGON_SOURCES}
orm_lib/src/ArrayParser.cc
orm_lib/src/Criteria.cc
orm_lib/src/DbClient.cc
orm_lib/src/DbClientImpl.cc
orm_lib/src/DbClientLockFree.cc
orm_lib/src/DbClientManager.cc
orm_lib/src/DbConnection.cc
orm_lib/src/Exception.cc
orm_lib/src/Field.cc
orm_lib/src/Result.cc
orm_lib/src/Row.cc
orm_lib/src/SqlBinder.cc
orm_lib/src/TransactionImpl.cc
orm_lib/src/RestfulController.cc)
else (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
set(DROGON_SOURCES ${DROGON_SOURCES} lib/src/DbClientManagerSkipped.cc)
endif (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
target_sources(${PROJECT_NAME} PRIVATE ${DROGON_SOURCES})
set_target_properties(${PROJECT_NAME}
PROPERTIES CXX_STANDARD ${DROGON_CXX_STANDARD})
PROPERTIES CXX_STANDARD ${DROGON_CXX_STANDARD})
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF)
set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME Drogon)
if(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
if(pg_FOUND)
option(USE_POSTGRESQL "Enable PostgreSQL" ON)
else(pg_FOUND)
option(USE_POSTGRESQL "Disable PostgreSQL" OFF)
endif(pg_FOUND)
if (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
if (pg_FOUND)
option(USE_POSTGRESQL "Enable PostgreSQL" ON)
else (pg_FOUND)
option(USE_POSTGRESQL "Disable PostgreSQL" OFF)
endif (pg_FOUND)
if(MySQL_FOUND)
option(USE_MYSQL "Enable Mysql" ON)
else(MySQL_FOUND)
option(USE_MYSQL "DisableMysql" OFF)
endif(MySQL_FOUND)
if (MySQL_FOUND)
option(USE_MYSQL "Enable Mysql" ON)
else (MySQL_FOUND)
option(USE_MYSQL "DisableMysql" OFF)
endif (MySQL_FOUND)
if(SQLite3_FOUND)
option(USE_SQLITE3 "Enable Sqlite3" ON)
else(SQLite3_FOUND)
option(USE_SQLITE3 "Disable Sqlite3" OFF)
endif(SQLite3_FOUND)
endif(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
if (SQLite3_FOUND)
option(USE_SQLITE3 "Enable Sqlite3" ON)
else (SQLite3_FOUND)
option(USE_SQLITE3 "Disable Sqlite3" OFF)
endif (SQLite3_FOUND)
endif (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
set(COMPILER_COMMAND ${CMAKE_CXX_COMPILER})
set(COMPILER_ID ${CMAKE_CXX_COMPILER_ID})
if(CMAKE_BUILD_TYPE)
string(TOLOWER ${CMAKE_BUILD_TYPE} _type)
if(_type STREQUAL release)
set(COMPILATION_FLAGS "${CMAKE_CXX_FLAGS_RELEASE} -std=c++")
elseif(_type STREQUAL debug)
set(COMPILATION_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} -std=c++")
else()
if (CMAKE_BUILD_TYPE)
string(TOLOWER ${CMAKE_BUILD_TYPE} _type)
if (_type STREQUAL release)
set(COMPILATION_FLAGS "${CMAKE_CXX_FLAGS_RELEASE} -std=c++")
elseif (_type STREQUAL debug)
set(COMPILATION_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} -std=c++")
else ()
set(COMPILATION_FLAGS "-std=c++")
endif ()
else (CMAKE_BUILD_TYPE)
set(COMPILATION_FLAGS "-std=c++")
endif()
else(CMAKE_BUILD_TYPE)
set(COMPILATION_FLAGS "-std=c++")
endif(CMAKE_BUILD_TYPE)
endif (CMAKE_BUILD_TYPE)
list(APPEND INCLUDE_DIRS_FOR_DYNAMIC_VIEW
"${CMAKE_INSTALL_PREFIX}/${INSTALL_INCLUDE_DIR}")
"${CMAKE_INSTALL_PREFIX}/${INSTALL_INCLUDE_DIR}")
list(REMOVE_DUPLICATES INCLUDE_DIRS_FOR_DYNAMIC_VIEW)
set(INS_STRING "")
foreach(loop_var ${INCLUDE_DIRS_FOR_DYNAMIC_VIEW})
set(INS_STRING "${INS_STRING} -I${loop_var}")
endforeach(loop_var)
foreach (loop_var ${INCLUDE_DIRS_FOR_DYNAMIC_VIEW})
set(INS_STRING "${INS_STRING} -I${loop_var}")
endforeach (loop_var)
set(INCLUDING_DIRS ${INS_STRING})
configure_file(${PROJECT_SOURCE_DIR}/cmake/templates/config.h.in
${PROJECT_BINARY_DIR}/drogon/config.h @ONLY)
${PROJECT_BINARY_DIR}/drogon/config.h @ONLY)
if(BUILD_TESTING)
add_subdirectory(lib/tests)
if(pg_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/postgresql_impl/test)
endif(pg_FOUND)
if(MySQL_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/mysql_impl/test)
endif(MySQL_FOUND)
if(SQLite3_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/sqlite3_impl/test)
endif(SQLite3_FOUND)
if(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/tests)
endif(pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
find_package(GTest)
if(GTest_FOUND)
message(STATUS "gtest found")
enable_testing()
add_subdirectory(unittest)
endif(GTest_FOUND)
endif(BUILD_TESTING)
if (BUILD_TESTING)
add_subdirectory(lib/tests)
if (pg_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/postgresql_impl/test)
endif (pg_FOUND)
if (MySQL_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/mysql_impl/test)
endif (MySQL_FOUND)
if (SQLite3_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/sqlite3_impl/test)
endif (SQLite3_FOUND)
if (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/tests)
endif (pg_FOUND OR MySQL_FOUND OR SQLite3_FOUND)
find_package(GTest)
if (GTest_FOUND)
message(STATUS "gtest found")
enable_testing()
add_subdirectory(unittest)
endif (GTest_FOUND)
endif (BUILD_TESTING)
# Installation
@ -383,79 +415,84 @@ install(TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib)
set(DROGON_HEADERS
lib/inc/drogon/Attribute.h
lib/inc/drogon/CacheMap.h
lib/inc/drogon/Cookie.h
lib/inc/drogon/DrClassMap.h
lib/inc/drogon/DrObject.h
lib/inc/drogon/DrTemplate.h
lib/inc/drogon/DrTemplateBase.h
lib/inc/drogon/HttpAppFramework.h
lib/inc/drogon/HttpBinder.h
lib/inc/drogon/HttpClient.h
lib/inc/drogon/HttpController.h
lib/inc/drogon/HttpFilter.h
lib/inc/drogon/HttpRequest.h
lib/inc/drogon/HttpResponse.h
lib/inc/drogon/HttpSimpleController.h
lib/inc/drogon/HttpTypes.h
lib/inc/drogon/HttpViewData.h
lib/inc/drogon/IntranetIpFilter.h
lib/inc/drogon/IOThreadStorage.h
lib/inc/drogon/LocalHostFilter.h
lib/inc/drogon/MultiPart.h
lib/inc/drogon/NotFound.h
lib/inc/drogon/Session.h
lib/inc/drogon/UploadFile.h
lib/inc/drogon/WebSocketClient.h
lib/inc/drogon/WebSocketConnection.h
lib/inc/drogon/WebSocketController.h
lib/inc/drogon/drogon.h
lib/inc/drogon/version.h
lib/inc/drogon/drogon_callbacks.h
lib/inc/drogon/PubSubService.h)
lib/inc/drogon/Attribute.h
lib/inc/drogon/CacheMap.h
lib/inc/drogon/Cookie.h
lib/inc/drogon/DrClassMap.h
lib/inc/drogon/DrObject.h
lib/inc/drogon/DrTemplate.h
lib/inc/drogon/DrTemplateBase.h
lib/inc/drogon/HttpAppFramework.h
lib/inc/drogon/HttpBinder.h
lib/inc/drogon/HttpClient.h
lib/inc/drogon/HttpController.h
lib/inc/drogon/HttpFilter.h
lib/inc/drogon/HttpRequest.h
lib/inc/drogon/HttpResponse.h
lib/inc/drogon/HttpSimpleController.h
lib/inc/drogon/HttpTypes.h
lib/inc/drogon/HttpViewData.h
lib/inc/drogon/IntranetIpFilter.h
lib/inc/drogon/IOThreadStorage.h
lib/inc/drogon/LocalHostFilter.h
lib/inc/drogon/MultiPart.h
lib/inc/drogon/NotFound.h
lib/inc/drogon/Session.h
lib/inc/drogon/UploadFile.h
lib/inc/drogon/WebSocketClient.h
lib/inc/drogon/WebSocketConnection.h
lib/inc/drogon/WebSocketController.h
lib/inc/drogon/drogon.h
lib/inc/drogon/version.h
lib/inc/drogon/drogon_callbacks.h
lib/inc/drogon/PubSubService.h)
install(FILES ${DROGON_HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/drogon)
set(ORM_HEADERS
orm_lib/inc/drogon/orm/ArrayParser.h
orm_lib/inc/drogon/orm/Criteria.h
orm_lib/inc/drogon/orm/DbClient.h
orm_lib/inc/drogon/orm/DbTypes.h
orm_lib/inc/drogon/orm/Exception.h
orm_lib/inc/drogon/orm/Field.h
orm_lib/inc/drogon/orm/FunctionTraits.h
orm_lib/inc/drogon/orm/Mapper.h
orm_lib/inc/drogon/orm/Result.h
orm_lib/inc/drogon/orm/ResultIterator.h
orm_lib/inc/drogon/orm/Row.h
orm_lib/inc/drogon/orm/RowIterator.h
orm_lib/inc/drogon/orm/SqlBinder.h
orm_lib/inc/drogon/orm/RestfulController.h)
orm_lib/inc/drogon/orm/ArrayParser.h
orm_lib/inc/drogon/orm/Criteria.h
orm_lib/inc/drogon/orm/DbClient.h
orm_lib/inc/drogon/orm/DbTypes.h
orm_lib/inc/drogon/orm/Exception.h
orm_lib/inc/drogon/orm/Field.h
orm_lib/inc/drogon/orm/FunctionTraits.h
orm_lib/inc/drogon/orm/Mapper.h
orm_lib/inc/drogon/orm/Result.h
orm_lib/inc/drogon/orm/ResultIterator.h
orm_lib/inc/drogon/orm/Row.h
orm_lib/inc/drogon/orm/RowIterator.h
orm_lib/inc/drogon/orm/SqlBinder.h
orm_lib/inc/drogon/orm/RestfulController.h)
install(FILES ${ORM_HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/orm)
set(NOSQL_HEADERS nosql_lib/redis/inc/drogon/nosql/RedisClient.h
nosql_lib/redis/inc/drogon/nosql/RedisResult.h
nosql_lib/redis/inc/drogon/nosql/RedisException.h)
install(FILES ${NOSQL_HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/nosql)
set(DROGON_UTIL_HEADERS
lib/inc/drogon/utils/FunctionTraits.h
lib/inc/drogon/utils/Utilities.h
lib/inc/drogon/utils/any.h
lib/inc/drogon/utils/string_view.h
lib/inc/drogon/utils/optional.h
lib/inc/drogon/utils/coroutine.h
lib/inc/drogon/utils/HttpConstraint.h
lib/inc/drogon/utils/OStringStream.h)
lib/inc/drogon/utils/FunctionTraits.h
lib/inc/drogon/utils/Utilities.h
lib/inc/drogon/utils/any.h
lib/inc/drogon/utils/string_view.h
lib/inc/drogon/utils/optional.h
lib/inc/drogon/utils/coroutine.h
lib/inc/drogon/utils/HttpConstraint.h
lib/inc/drogon/utils/OStringStream.h)
install(FILES ${DROGON_UTIL_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/utils)
set(DROGON_PLUGIN_HEADERS lib/inc/drogon/plugins/Plugin.h
lib/inc/drogon/plugins/SecureSSLRedirector.h)
lib/inc/drogon/plugins/SecureSSLRedirector.h)
install(FILES ${DROGON_PLUGIN_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/plugins)
source_group("Public API"
FILES
${DROGON_HEADERS}
${ORM_HEADERS}
${DROGON_UTIL_HEADERS}
${DROGON_PLUGIN_HEADERS})
FILES
${DROGON_HEADERS}
${ORM_HEADERS}
${DROGON_UTIL_HEADERS}
${DROGON_PLUGIN_HEADERS})
# Export the package for use from the build-tree (this registers the build-tree
# with a global cmake-registry) export(PACKAGE Drogon)
@ -463,20 +500,20 @@ source_group("Public API"
include(CMakePackageConfigHelpers)
# ... for the install tree
configure_package_config_file(
cmake/templates/DrogonConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/DrogonConfig.cmake
INSTALL_DESTINATION
${INSTALL_DROGON_CMAKE_DIR})
cmake/templates/DrogonConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/DrogonConfig.cmake
INSTALL_DESTINATION
${INSTALL_DROGON_CMAKE_DIR})
# version
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/DrogonConfigVersion.cmake
VERSION ${DROGON_VERSION}
COMPATIBILITY SameMajorVersion)
${CMAKE_CURRENT_BINARY_DIR}/DrogonConfigVersion.cmake
VERSION ${DROGON_VERSION}
COMPATIBILITY SameMajorVersion)
# Install the DrogonConfig.cmake and DrogonConfigVersion.cmake
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/DrogonConfig.cmake"
FILES "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/DrogonConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/DrogonConfigVersion.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/FindUUID.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/FindJsoncpp.cmake"
@ -485,9 +522,10 @@ install(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/Findpg.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/FindBrotli.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/Findcoz-profiler.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/FindHiredis.cmake"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/DrogonUtilities.cmake"
DESTINATION "${INSTALL_DROGON_CMAKE_DIR}"
COMPONENT dev)
DESTINATION "${INSTALL_DROGON_CMAKE_DIR}"
COMPONENT dev)
# Install the export set for use with the install-tree
install(EXPORT DrogonTargets

View File

@ -36,6 +36,9 @@ endif()
if(@COZ-PROFILER_FOUND@)
find_dependency(coz-profiler)
endif()
if(@Hiredis_FOUND@)
find_dependency(Hiredis)
endif()
# Our library dependencies (contains definitions for IMPORTED targets)

View File

@ -0,0 +1,40 @@
# Try to find hiredis
# Once done, this will define
#
# HIREDIS_FOUND - system has hiredis
# HIREDIS_INCLUDE_DIRS - hiredis include directories
# HIREDIS_LIBRARIES - libraries need to use hiredis
if (HIREDIS_INCLUDE_DIRS AND HIREDIS_LIBRARIES)
set(HIREDIS_FIND_QUIETLY TRUE)
else ()
find_path(
HIREDIS_INCLUDE_DIR
NAMES hiredis/hiredis.h
HINTS ${HIREDIS_ROOT_DIR}
PATH_SUFFIXES include)
find_library(
HIREDIS_LIBRARY
NAMES hiredis
HINTS ${HIREDIS_ROOT_DIR}
PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR})
set(HIREDIS_INCLUDE_DIRS ${HIREDIS_INCLUDE_DIR})
set(HIREDIS_LIBRARIES ${HIREDIS_LIBRARY})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
Hiredis DEFAULT_MSG HIREDIS_LIBRARY HIREDIS_INCLUDE_DIR)
mark_as_advanced(HIREDIS_LIBRARY HIREDIS_INCLUDE_DIR)
endif ()
if(Hiredis_FOUND)
add_library(Hiredis_lib INTERFACE IMPORTED)
set_target_properties(Hiredis_lib
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"${HIREDIS_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES
"${HIREDIS_LIBRARIES}")
endif(Hiredis_FOUND)

View File

@ -52,15 +52,33 @@
//client_encoding: The character set used by the client. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//connection_number: 1 by default, if the 'is_fast' is true, the number is the number of
//number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"connection_number": 1
"number_of_connections": 1
}
],
"redis_clients": [
{
//name: Name of the client,'default' by default
//"name":"",
//host: Server IP, 127.0.0.1 by default
"host": "127.0.0.1",
//port: Server port, 6379 by default
"port": 6379,
//passwd: '' by default
"passwd": "",
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"number_of_connections": 1
}
],*/
"app": {
//threads_num: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//number_of_threads: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//is the number of CPU cores
"threads_num": 1,
"number_of_threads": 1,
//enable_session: False by default
"enable_session": true,
"session_timeout": 0,
@ -128,9 +146,9 @@
"filters": []
}
],
//max_connections: maximum connections number,100000 by default
//max_connections: maximum number of connections, 100000 by default
"max_connections": 100000,
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit
//max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit
"max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined

View File

@ -52,15 +52,33 @@
//client_encoding: The character set used by the client. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//connection_number: 1 by default, if the 'is_fast' is true, the number is the number of
//number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"connection_number": 1
"number_of_connections": 1
}
],
"redis_clients": [
{
//name: Name of the client,'default' by default
//"name":"",
//host: Server IP, 127.0.0.1 by default
"host": "127.0.0.1",
//port: Server port, 6379 by default
"port": 6379,
//passwd: '' by default
"passwd": "",
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//number_of_connections: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"number_of_connections": 1
}
],*/
"app": {
//threads_num: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//number_of_threads: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//is the number of CPU cores
"threads_num": 1,
"number_of_threads": 1,
//enable_session: False by default
"enable_session": false,
"session_timeout": 0,
@ -128,9 +146,9 @@
"filters": []
}
],
//max_connections: maximum connections number,100000 by default
//max_connections: maximum number of connections, 100000 by default
"max_connections": 100000,
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit
//max_connections_per_ip: maximum number of connections per clinet, 0 by default which means no limit
"max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined

View File

@ -56,6 +56,11 @@ void version::handleCommand(std::vector<std::string> &parameters)
std::cout << " boost: yes\n";
#else
std::cout << " boost: no\n";
#endif
#ifdef USE_REDIS
std::cout << " hiredis: yes\n";
#else
std::cout << " hiredis: no\n";
#endif
std::cout << " c-ares: "
<< (trantor::Resolver::isCAresUsed() ? "yes\n" : "no\n");

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#find lib orm_lib examples drogon_ctl -name *.h -o -name *.cc -exec dos2unix {} \;
find lib orm_lib examples drogon_ctl -name *.h -o -name *.cc|xargs clang-format -i -style=file
find lib orm_lib nosql_lib examples drogon_ctl -name *.h -o -name *.cc|xargs clang-format -i -style=file

View File

@ -28,6 +28,7 @@
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <drogon/orm/DbClient.h>
#include <drogon/nosql/RedisClient.h>
#include <trantor/net/Resolver.h>
#include <trantor/net/EventLoop.h>
#include <trantor/utils/NonCopyable.h>
@ -59,7 +60,7 @@ class WebSocketControllerBase;
class HttpAppFramework : public trantor::NonCopyable
{
public:
virtual ~HttpAppFramework();
virtual ~HttpAppFramework() = default;
/// Get the instance of HttpAppFramework
/**
* HttpAppFramework works at singleton mode, so any calling of this
@ -109,7 +110,7 @@ class HttpAppFramework : public trantor::NonCopyable
* @note
* The event loop is one of the network IO loops. Use the loop
* for events/actions rather then the main thread.
* REMAKRS : Function assumed the number of threads will not exceed 2^32.
* REMARKS : Function assumed the number of threads will not exceed 2^32.
* Change to long long for alien computers.
*/
virtual trantor::EventLoop *getIOLoop(size_t id) const = 0;
@ -200,7 +201,7 @@ class HttpAppFramework : public trantor::NonCopyable
* Users can use the callback to modify the response if they want.
* @note This advice is called before any subsequent operation on the
* response is performed by drogon or applications, so some modification
* (e.g. modification on the status code) in this callback may be overrided
* (e.g. modification on the status code) in this callback may be override
* by subsequent operations.
* @return HttpAppFramework&
*/
@ -282,7 +283,7 @@ class HttpAppFramework : public trantor::NonCopyable
/// Register an advice called after routing
/**
* @param advice is called immediately after the request matchs a handler
* @param advice is called immediately after the request matches a handler
* path and before any 'doFilter' method of filters applies. The parameters
* of the advice are same as those of the doFilter method of the Filter
* class.
@ -507,7 +508,7 @@ class HttpAppFramework : public trantor::NonCopyable
*/
virtual HttpAppFramework &registerWebSocketController(
const std::string &pathName,
const std::string &crtlName,
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{}) = 0;
@ -563,7 +564,7 @@ class HttpAppFramework : public trantor::NonCopyable
HttpAppFramework &registerFilter(const std::shared_ptr<T> &filterPtr)
{
static_assert(std::is_base_of<HttpFilterBase, T>::value,
"Error! Only fitler objects can be registered here");
"Error! Only filter objects can be registered here");
static_assert(!T::isAutoCreation,
"Filters created and initialized "
"automatically by drogon cannot be "
@ -696,7 +697,7 @@ class HttpAppFramework : public trantor::NonCopyable
*/
virtual HttpAppFramework &disableSession() = 0;
/// Set the root path of HTTP document, defaut path is ./
/// Set the root path of HTTP document, default path is ./
/**
* @note
* This operation can be performed by an option in the configuration file.
@ -771,7 +772,7 @@ class HttpAppFramework : public trantor::NonCopyable
/// Enable supporting for dynamic views loading.
/**
*
* @param libPaths is a vactor that contains paths to view files.
* @param libPaths is a vector that contains paths to view files.
*
* @param outputPath is the directory where the output source files locate. if
* it is set to an empty string, drogon use libPaths as output paths. If the
@ -1138,6 +1139,22 @@ class HttpAppFramework : public trantor::NonCopyable
*/
virtual bool areAllDbClientsAvailable() const noexcept = 0;
/// Get a redis client by name
/**
* @note
* This method must be called after the framework has been run.
*/
virtual nosql::RedisClientPtr getRedisClient(
const std::string &name = "default") = 0;
/// Get a 'fast' redis client by name
/**
* @note
* This method must be called after the framework has been run.
*/
virtual nosql::RedisClientPtr getFastRedisClient(
const std::string &name = "default") = 0;
/**
* @brief This method is to enable or disable the unicode escaping (\u) in
* the json string of HTTP responses or requests. it works (disable
@ -1204,6 +1221,26 @@ class HttpAppFramework : public trantor::NonCopyable
const bool isFast = false,
const std::string &characterSet = "") = 0;
/// Create a redis client
/**
* @param ip IP of redis server.
* @param port The port on which the redis server is listening.
* @param name The client name.
* @param password Password for the redis server
* @param connectionNum The number of connections to the redis server.
* @param isFast Indicates if the client is a fast database client.
*
* @note
* This operation can be performed by an option in the configuration file.
*/
virtual HttpAppFramework &createRedisClient(
const std::string &ip,
unsigned short port,
const std::string &name = "default",
const std::string &password = "",
size_t connectionNum = 1,
bool isFast = false) = 0;
/// Get the DNS resolver
/**
* @note

View File

@ -413,9 +413,9 @@ struct AsyncTask final
}
};
/// Helper class that provices the infrastructure for turning callback into
/// corourines
// The user is responsible to fill in `await_suspend()` and construtors.
/// Helper class that provides the infrastructure for turning callback into
/// coroutines
// The user is responsible to fill in `await_suspend()` and constructors.
template <typename T = void>
struct CallbackAwaiter
{
@ -427,7 +427,7 @@ struct CallbackAwaiter
const T &await_resume() const noexcept(false)
{
// await_resume() should always be called after co_await
// (await_suspend()) is called. Therefor the value should always be set
// (await_suspend()) is called. Therefore the value should always be set
// (or there's an exception)
assert(result_.has_value() == true || exception_ != nullptr);
@ -437,7 +437,7 @@ struct CallbackAwaiter
}
private:
// HACK: Not all desired types are default contructable. But we need the
// HACK: Not all desired types are default constructable. But we need the
// entire struct to be constructed for awaiting. std::optional takes care of
// that.
optional<T> result_;

View File

@ -1,7 +1,7 @@
/**
*
* string_view.h
* An Tao
* @file string_view.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon

View File

@ -231,6 +231,10 @@ static void loadApp(const Json::Value &app)
return;
// threads number
auto threadsNum = app.get("threads_num", 1).asUInt64();
if (threadsNum == 1)
{
threadsNum = app.get("number_of_threads", 1).asUInt64();
}
if (threadsNum == 0)
{
// set the number to the number of processors.
@ -497,6 +501,10 @@ static void loadDbClients(const Json::Value &dbClients)
password = client.get("password", "").asString();
}
auto connNum = client.get("connection_number", 1).asUInt();
if (connNum == 1)
{
connNum = client.get("number_of_connections", 1).asUInt();
}
auto name = client.get("name", "default").asString();
auto filename = client.get("filename", "").asString();
auto isFast = client.get("is_fast", false).asBool();
@ -518,6 +526,32 @@ static void loadDbClients(const Json::Value &dbClients)
characterSet);
}
}
static void loadRedisClients(const Json::Value &redisClients)
{
if (!redisClients)
return;
for (auto const &client : redisClients)
{
auto host = client.get("host", "127.0.0.1").asString();
auto port = client.get("port", 6379).asUInt();
auto password = client.get("passwd", "").asString();
if (password.empty())
{
password = client.get("password", "").asString();
}
auto connNum = client.get("connection_number", 1).asUInt();
if (connNum == 1)
{
connNum = client.get("number_of_connections", 1).asUInt();
}
auto name = client.get("name", "default").asString();
auto isFast = client.get("is_fast", false).asBool();
drogon::app().createRedisClient(
host, port, name, password, connNum, isFast);
}
}
static void loadListeners(const Json::Value &listeners)
{
if (!listeners)
@ -550,4 +584,5 @@ void ConfigLoader::load()
loadSSL(configJsonRoot_["ssl"]);
loadListeners(configJsonRoot_["listeners"]);
loadDbClients(configJsonRoot_["db_clients"]);
loadRedisClients(configJsonRoot_["redis_clients"]);
}

View File

@ -30,6 +30,7 @@
#include "SharedLibManager.h"
#include "SessionManager.h"
#include "DbClientManager.h"
#include "RedisClientManager.h"
#include <drogon/config.h>
#include <algorithm>
#include <drogon/version.h>
@ -86,6 +87,7 @@ HttpAppFrameworkImpl::HttpAppFrameworkImpl()
listenerManagerPtr_(new ListenerManager),
pluginsManagerPtr_(new PluginsManager),
dbClientManagerPtr_(new orm::DbClientManager),
redisClientManagerPtr_(new nosql::RedisClientManager),
uploadPath_(rootPath_ + "uploads")
{
}
@ -116,7 +118,7 @@ HttpResponsePtr defaultErrorHandler(HttpStatusCode code)
return std::make_shared<HttpResponseImpl>(code, CT_TEXT_HTML);
}
static void godaemon(void)
static void godaemon()
{
printf("Initializing daemon mode\n");
#ifndef _WIN32
@ -374,7 +376,7 @@ HttpAppFramework &HttpAppFrameworkImpl::setLogPath(
const std::string &logfileBaseName,
size_t logfileSize)
{
if (logPath == "")
if (logPath.empty())
return *this;
#ifdef _WIN32
if (_access(logPath.c_str(), 0) != 0)
@ -482,7 +484,7 @@ void HttpAppFrameworkImpl::run()
else
{
std::string baseName = logfileBaseName_;
if (baseName == "")
if (baseName.empty())
{
baseName = "drogon";
}
@ -504,8 +506,9 @@ void HttpAppFrameworkImpl::run()
#ifndef _WIN32
if (!libFilePaths_.empty())
{
sharedLibManagerPtr_ = std::unique_ptr<SharedLibManager>(
new SharedLibManager(libFilePaths_, libFileOutputPath_));
sharedLibManagerPtr_ =
std::make_unique<SharedLibManager>(libFilePaths_,
libFileOutputPath_);
}
#endif
// Create all listeners.
@ -528,11 +531,11 @@ void HttpAppFrameworkImpl::run()
// loop, so put the main loop into ioLoops.
ioLoops.push_back(getLoop());
dbClientManagerPtr_->createDbClients(ioLoops);
redisClientManagerPtr_->createRedisClients(ioLoops);
if (useSession_)
{
sessionManagerPtr_ = std::unique_ptr<SessionManager>(
new SessionManager(getLoop(), sessionTimeout_));
sessionManagerPtr_ =
std::make_unique<SessionManager>(getLoop(), sessionTimeout_);
}
// Initialize plugins
@ -852,9 +855,6 @@ HttpAppFramework &HttpAppFramework::instance()
return HttpAppFrameworkImpl::instance();
}
HttpAppFramework::~HttpAppFramework()
{
}
void HttpAppFrameworkImpl::forward(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
@ -926,6 +926,16 @@ orm::DbClientPtr HttpAppFrameworkImpl::getFastDbClient(const std::string &name)
{
return dbClientManagerPtr_->getFastDbClient(name);
}
nosql::RedisClientPtr HttpAppFrameworkImpl::getRedisClient(
const std::string &name)
{
return redisClientManagerPtr_->getRedisClient(name);
}
nosql::RedisClientPtr HttpAppFrameworkImpl::getFastRedisClient(
const std::string &name)
{
return redisClientManagerPtr_->getFastRedisClient(name);
}
HttpAppFramework &HttpAppFrameworkImpl::createDbClient(
const std::string &dbType,
const std::string &host,
@ -954,6 +964,19 @@ HttpAppFramework &HttpAppFrameworkImpl::createDbClient(
return *this;
}
HttpAppFramework &HttpAppFrameworkImpl::createRedisClient(
const std::string &ip,
unsigned short port,
const std::string &name,
const std::string &password,
size_t connectionNum,
bool isFast)
{
assert(!running_);
redisClientManagerPtr_->createRedisClient(
name, ip, port, password, connectionNum, isFast);
return *this;
}
void HttpAppFrameworkImpl::quit()
{
if (getLoop()->isRunning())

View File

@ -38,44 +38,44 @@ struct InitBeforeMainFunction
}
};
class HttpAppFrameworkImpl : public HttpAppFramework
class HttpAppFrameworkImpl final : public HttpAppFramework
{
public:
HttpAppFrameworkImpl();
virtual const Json::Value &getCustomConfig() const override
const Json::Value &getCustomConfig() const override
{
return jsonConfig_["custom_config"];
}
virtual PluginBase *getPlugin(const std::string &name) override;
virtual HttpAppFramework &addListener(const std::string &ip,
uint16_t port,
bool useSSL = false,
const std::string &certFile = "",
const std::string &keyFile = "",
bool useOldTLS = false) override;
virtual HttpAppFramework &setThreadNum(size_t threadNum) override;
virtual size_t getThreadNum() const override
PluginBase *getPlugin(const std::string &name) override;
HttpAppFramework &addListener(const std::string &ip,
uint16_t port,
bool useSSL,
const std::string &certFile,
const std::string &keyFile,
bool useOldTLS) override;
HttpAppFramework &setThreadNum(size_t threadNum) override;
size_t getThreadNum() const override
{
return threadNum_;
}
virtual HttpAppFramework &setSSLFiles(const std::string &certPath,
const std::string &keyPath) override;
virtual void run() override;
virtual HttpAppFramework &registerWebSocketController(
HttpAppFramework &setSSLFiles(const std::string &certPath,
const std::string &keyPath) override;
void run() override;
HttpAppFramework &registerWebSocketController(
const std::string &pathName,
const std::string &crtlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{}) override;
virtual HttpAppFramework &registerHttpSimpleController(
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods)
override;
HttpAppFramework &registerHttpSimpleController(
const std::string &pathName,
const std::string &crtlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{}) override;
const std::string &ctrlName,
const std::vector<internal::HttpConstraint> &filtersAndMethods)
override;
virtual HttpAppFramework &setCustom404Page(const HttpResponsePtr &resp,
bool set404) override
HttpAppFramework &setCustom404Page(const HttpResponsePtr &resp,
bool set404) override
{
if (set404)
{
@ -91,25 +91,24 @@ class HttpAppFrameworkImpl : public HttpAppFramework
const HttpResponsePtr &getCustom404Page();
virtual void forward(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString = "",
double timeout = 0) override;
void forward(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString,
double timeout) override;
void forward(const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &hostString,
double timeout = 0);
virtual HttpAppFramework &registerBeginningAdvice(
HttpAppFramework &registerBeginningAdvice(
const std::function<void()> &advice) override
{
beginningAdvices_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerNewConnectionAdvice(
HttpAppFramework &registerNewConnectionAdvice(
const std::function<bool(const trantor::InetAddress &,
const trantor::InetAddress &)> &advice)
override
@ -118,7 +117,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return *this;
}
virtual HttpAppFramework &registerHttpResponseCreationAdvice(
HttpAppFramework &registerHttpResponseCreationAdvice(
const std::function<void(const HttpResponsePtr &)> &advice) override
{
responseCreationAdvices_.emplace_back(advice);
@ -131,14 +130,14 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return responseCreationAdvices_;
}
virtual HttpAppFramework &registerSyncAdvice(
HttpAppFramework &registerSyncAdvice(
const std::function<HttpResponsePtr(const HttpRequestPtr &)> &advice)
override
{
syncAdvices_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerPreRoutingAdvice(
HttpAppFramework &registerPreRoutingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override
@ -146,7 +145,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
preRoutingAdvices_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerPostRoutingAdvice(
HttpAppFramework &registerPostRoutingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override
@ -154,7 +153,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
postRoutingAdvices_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerPreHandlingAdvice(
HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
AdviceChainCallback &&)> &advice) override
@ -163,25 +162,25 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return *this;
}
virtual HttpAppFramework &registerPreRoutingAdvice(
HttpAppFramework &registerPreRoutingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override
{
preRoutingObservers_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerPostRoutingAdvice(
HttpAppFramework &registerPostRoutingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override
{
postRoutingObservers_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerPreHandlingAdvice(
HttpAppFramework &registerPreHandlingAdvice(
const std::function<void(const HttpRequestPtr &)> &advice) override
{
preHandlingObservers_.emplace_back(advice);
return *this;
}
virtual HttpAppFramework &registerPostHandlingAdvice(
HttpAppFramework &registerPostHandlingAdvice(
const std::function<void(const HttpRequestPtr &,
const HttpResponsePtr &)> &advice) override
{
@ -189,152 +188,140 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return *this;
}
virtual HttpAppFramework &enableSession(const size_t timeout = 0) override
HttpAppFramework &enableSession(const size_t timeout) override
{
useSession_ = true;
sessionTimeout_ = timeout;
return *this;
}
virtual HttpAppFramework &disableSession() override
HttpAppFramework &disableSession() override
{
useSession_ = false;
return *this;
}
virtual const std::string &getDocumentRoot() const override
const std::string &getDocumentRoot() const override
{
return rootPath_;
}
virtual HttpAppFramework &setDocumentRoot(
const std::string &rootPath) override
HttpAppFramework &setDocumentRoot(const std::string &rootPath) override
{
rootPath_ = rootPath;
return *this;
}
virtual HttpAppFramework &setStaticFileHeaders(
HttpAppFramework &setStaticFileHeaders(
const std::vector<std::pair<std::string, std::string>> &headers)
override;
virtual HttpAppFramework &addALocation(
HttpAppFramework &addALocation(
const std::string &uriPrefix,
const std::string &defaultContentType = "",
const std::string &alias = "",
bool isCaseSensitive = false,
bool allowAll = true,
bool isRecursive = true,
const std::vector<std::string> &filters = {}) override;
const std::string &defaultContentType,
const std::string &alias,
bool isCaseSensitive,
bool allowAll,
bool isRecursive,
const std::vector<std::string> &filters) override;
virtual const std::string &getUploadPath() const override
const std::string &getUploadPath() const override
{
return uploadPath_;
}
virtual const std::shared_ptr<trantor::Resolver> &getResolver()
const override
const std::shared_ptr<trantor::Resolver> &getResolver() const override
{
static auto resolver = trantor::Resolver::newResolver(getLoop());
return resolver;
}
virtual HttpAppFramework &setUploadPath(
const std::string &uploadPath) override;
virtual HttpAppFramework &setFileTypes(
HttpAppFramework &setUploadPath(const std::string &uploadPath) override;
HttpAppFramework &setFileTypes(
const std::vector<std::string> &types) override;
#ifndef _WIN32
virtual HttpAppFramework &enableDynamicViewsLoading(
HttpAppFramework &enableDynamicViewsLoading(
const std::vector<std::string> &libPaths,
const std::string &outputPath) override;
#endif
virtual HttpAppFramework &setMaxConnectionNum(
size_t maxConnections) override;
virtual HttpAppFramework &setMaxConnectionNumPerIP(
HttpAppFramework &setMaxConnectionNum(size_t maxConnections) override;
HttpAppFramework &setMaxConnectionNumPerIP(
size_t maxConnectionsPerIP) override;
virtual HttpAppFramework &loadConfigFile(
const std::string &fileName) override;
virtual HttpAppFramework &loadConfigJson(const Json::Value &data) override;
virtual HttpAppFramework &loadConfigJson(Json::Value &&data) override;
virtual HttpAppFramework &enableRunAsDaemon() override
HttpAppFramework &loadConfigFile(const std::string &fileName) override;
HttpAppFramework &loadConfigJson(const Json::Value &data) override;
HttpAppFramework &loadConfigJson(Json::Value &&data) override;
HttpAppFramework &enableRunAsDaemon() override
{
runAsDaemon_ = true;
return *this;
}
virtual HttpAppFramework &disableSigtermHandling() override
HttpAppFramework &disableSigtermHandling() override
{
handleSigterm_ = false;
return *this;
}
virtual HttpAppFramework &enableRelaunchOnError() override
HttpAppFramework &enableRelaunchOnError() override
{
relaunchOnError_ = true;
return *this;
}
virtual HttpAppFramework &setLogPath(
const std::string &logPath,
const std::string &logfileBaseName = "",
size_t logfileSize = 100000000) override;
virtual HttpAppFramework &setLogLevel(
trantor::Logger::LogLevel level) override;
virtual HttpAppFramework &enableSendfile(bool sendFile) override
HttpAppFramework &setLogPath(const std::string &logPath,
const std::string &logfileBaseName,
size_t logfileSize) override;
HttpAppFramework &setLogLevel(trantor::Logger::LogLevel level) override;
HttpAppFramework &enableSendfile(bool sendFile) override
{
useSendfile_ = sendFile;
return *this;
}
virtual HttpAppFramework &enableGzip(bool useGzip) override
HttpAppFramework &enableGzip(bool useGzip) override
{
useGzip_ = useGzip;
return *this;
}
virtual bool isGzipEnabled() const override
bool isGzipEnabled() const override
{
return useGzip_;
}
virtual HttpAppFramework &enableBrotli(bool useBrotli) override
HttpAppFramework &enableBrotli(bool useBrotli) override
{
useBrotli_ = useBrotli;
return *this;
}
virtual bool isBrotliEnabled() const override
bool isBrotliEnabled() const override
{
return useBrotli_;
}
virtual HttpAppFramework &setStaticFilesCacheTime(int cacheTime) override;
virtual int staticFilesCacheTime() const override;
virtual HttpAppFramework &setIdleConnectionTimeout(size_t timeout) override
HttpAppFramework &setStaticFilesCacheTime(int cacheTime) override;
int staticFilesCacheTime() const override;
HttpAppFramework &setIdleConnectionTimeout(size_t timeout) override
{
idleConnectionTimeout_ = timeout;
return *this;
}
virtual HttpAppFramework &setKeepaliveRequestsNumber(
const size_t number) override
HttpAppFramework &setKeepaliveRequestsNumber(const size_t number) override
{
keepaliveRequestsNumber_ = number;
return *this;
}
virtual HttpAppFramework &setPipeliningRequestsNumber(
const size_t number) override
HttpAppFramework &setPipeliningRequestsNumber(const size_t number) override
{
pipeliningRequestsNumber_ = number;
return *this;
}
virtual HttpAppFramework &setGzipStatic(bool useGzipStatic) override;
virtual HttpAppFramework &setBrStatic(bool useGzipStatic) override;
virtual HttpAppFramework &setClientMaxBodySize(size_t maxSize) override
HttpAppFramework &setGzipStatic(bool useGzipStatic) override;
HttpAppFramework &setBrStatic(bool useGzipStatic) override;
HttpAppFramework &setClientMaxBodySize(size_t maxSize) override
{
clientMaxBodySize_ = maxSize;
return *this;
}
virtual HttpAppFramework &setClientMaxMemoryBodySize(
size_t maxSize) override
HttpAppFramework &setClientMaxMemoryBodySize(size_t maxSize) override
{
clientMaxMemoryBodySize_ = maxSize;
return *this;
}
virtual HttpAppFramework &setClientMaxWebSocketMessageSize(
size_t maxSize) override
HttpAppFramework &setClientMaxWebSocketMessageSize(size_t maxSize) override
{
clientMaxWebSocketMessageSize_ = maxSize;
return *this;
}
virtual HttpAppFramework &setHomePage(
const std::string &homePageFile) override
HttpAppFramework &setHomePage(const std::string &homePageFile) override
{
homePageFile_ = homePageFile;
return *this;
@ -343,7 +330,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
{
return homePageFile_;
}
virtual HttpAppFramework &setTermSignalHandler(
HttpAppFramework &setTermSignalHandler(
const std::function<void()> &handler) override
{
termSignalHandler_ = handler;
@ -353,10 +340,9 @@ class HttpAppFrameworkImpl : public HttpAppFramework
{
return termSignalHandler_;
}
virtual HttpAppFramework &setImplicitPageEnable(
bool useImplicitPage) override;
HttpAppFramework &setImplicitPageEnable(bool useImplicitPage) override;
bool isImplicitPageEnabled() const override;
virtual HttpAppFramework &setImplicitPage(
HttpAppFramework &setImplicitPage(
const std::string &implicitPageFile) override;
const std::string &getImplicitPage() const override;
size_t getClientMaxBodySize() const
@ -371,7 +357,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
{
return clientMaxWebSocketMessageSize_;
}
virtual std::vector<std::tuple<std::string, HttpMethod, std::string>>
std::vector<std::tuple<std::string, HttpMethod, std::string>>
getHandlersInfo() const override;
size_t keepaliveRequestsNumber() const
@ -383,44 +369,42 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return pipeliningRequestsNumber_;
}
virtual ~HttpAppFrameworkImpl() noexcept;
virtual bool isRunning() override
~HttpAppFrameworkImpl() noexcept override;
bool isRunning() override
{
return running_;
}
virtual HttpAppFramework &setUnicodeEscapingInJson(
bool enable) noexcept override
HttpAppFramework &setUnicodeEscapingInJson(bool enable) noexcept override
{
usingUnicodeEscaping_ = enable;
return *this;
}
virtual bool isUnicodeEscapingUsedInJson() const noexcept override
bool isUnicodeEscapingUsedInJson() const noexcept override
{
return usingUnicodeEscaping_;
}
virtual HttpAppFramework &setFloatPrecisionInJson(
HttpAppFramework &setFloatPrecisionInJson(
unsigned int precision,
const std::string &precisionType = "significant") noexcept override
const std::string &precisionType) noexcept override
{
floatPrecisionInJson_ = std::make_pair(precision, precisionType);
return *this;
}
virtual const std::pair<unsigned int, std::string>
&getFloatPrecisionInJson() const noexcept override
const std::pair<unsigned int, std::string> &getFloatPrecisionInJson()
const noexcept override
{
return floatPrecisionInJson_;
}
virtual trantor::EventLoop *getLoop() const override;
trantor::EventLoop *getLoop() const override;
virtual trantor::EventLoop *getIOLoop(size_t id) const override;
trantor::EventLoop *getIOLoop(size_t id) const override;
virtual void quit() override;
void quit() override;
virtual HttpAppFramework &setServerHeaderField(
const std::string &server) override
HttpAppFramework &setServerHeaderField(const std::string &server) override
{
assert(!running_);
assert(server.find("\r\n") == std::string::npos);
@ -428,12 +412,12 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return *this;
}
virtual HttpAppFramework &enableServerHeader(bool flag) override
HttpAppFramework &enableServerHeader(bool flag) override
{
enableServerHeader_ = flag;
return *this;
}
virtual HttpAppFramework &enableDateHeader(bool flag) override
HttpAppFramework &enableDateHeader(bool flag) override
{
enableDateHeader_ = flag;
return *this;
@ -451,29 +435,34 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return serverHeader_;
}
virtual orm::DbClientPtr getDbClient(
const std::string &name = "default") override;
virtual orm::DbClientPtr getFastDbClient(
const std::string &name = "default") override;
virtual HttpAppFramework &createDbClient(
const std::string &dbType,
const std::string &host,
const unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
const size_t connectionNum = 1,
const std::string &filename = "",
const std::string &name = "default",
const bool isFast = false,
const std::string &characterSet = "") override;
virtual std::vector<trantor::InetAddress> getListeners() const override;
orm::DbClientPtr getDbClient(const std::string &name) override;
orm::DbClientPtr getFastDbClient(const std::string &name) override;
HttpAppFramework &createDbClient(const std::string &dbType,
const std::string &host,
unsigned short port,
const std::string &databaseName,
const std::string &userName,
const std::string &password,
size_t connectionNum,
const std::string &filename,
const std::string &name,
bool isFast,
const std::string &characterSet) override;
HttpAppFramework &createRedisClient(const std::string &ip,
unsigned short port,
const std::string &name,
const std::string &password,
size_t connectionNum,
bool isFast) override;
nosql::RedisClientPtr getRedisClient(const std::string &name) override;
nosql::RedisClientPtr getFastRedisClient(const std::string &name) override;
std::vector<trantor::InetAddress> getListeners() const override;
inline static HttpAppFrameworkImpl &instance()
{
static HttpAppFrameworkImpl instance;
return instance;
}
bool useSendfile()
bool useSendfile() const
{
return useSendfile_;
}
@ -482,7 +471,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
const HttpResponsePtr &resp,
const std::function<void(const HttpResponsePtr &)> &callback);
virtual bool supportSSL() const override
bool supportSSL() const override
{
#ifdef OpenSSL_FOUND
return true;
@ -490,7 +479,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
return false;
}
virtual size_t getCurrentThreadIndex() const override
size_t getCurrentThreadIndex() const override
{
auto *loop = trantor::EventLoop::getEventLoopOfCurrentThread();
if (loop)
@ -504,30 +493,29 @@ class HttpAppFrameworkImpl : public HttpAppFramework
#endif
}
virtual bool areAllDbClientsAvailable() const noexcept override;
bool areAllDbClientsAvailable() const noexcept override;
const std::function<HttpResponsePtr(HttpStatusCode)>
&getCustomErrorHandler() const override;
bool isUsingCustomErrorHandler() const
{
return usingCustomErrorHandler_;
}
virtual void enableReusePort(bool enable = true) override
void enableReusePort(bool enable) override
{
reusePort_ = enable;
}
virtual bool reusePort() const override
bool reusePort() const override
{
return reusePort_;
}
private:
virtual void registerHttpController(
const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods = std::vector<HttpMethod>(),
const std::vector<std::string> &filters = std::vector<std::string>(),
const std::string &handlerName = "") override;
virtual void registerHttpControllerViaRegex(
void registerHttpController(const std::string &pathPattern,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters,
const std::string &handlerName) override;
void registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
@ -541,10 +529,6 @@ class HttpAppFrameworkImpl : public HttpAppFramework
std::function<void(const HttpResponsePtr &)> &&callback,
const WebSocketConnectionImplPtr &wsConnPtr);
void onConnection(const trantor::TcpConnectionPtr &conn);
void addHttpPath(const std::string &path,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters);
void findSessionForRequest(const HttpRequestImplPtr &req);
@ -566,7 +550,7 @@ class HttpAppFrameworkImpl : public HttpAppFramework
const std::unique_ptr<ListenerManager> listenerManagerPtr_;
const std::unique_ptr<PluginsManager> pluginsManagerPtr_;
const std::unique_ptr<orm::DbClientManager> dbClientManagerPtr_;
const std::unique_ptr<nosql::RedisClientManager> redisClientManagerPtr_;
std::string rootPath_{"./"};
std::string uploadPath_;
std::atomic_bool running_{false};
@ -590,8 +574,8 @@ class HttpAppFrameworkImpl : public HttpAppFramework
bool runAsDaemon_{false};
bool handleSigterm_{true};
bool relaunchOnError_{false};
std::string logPath_{""};
std::string logfileBaseName_{""};
std::string logPath_;
std::string logfileBaseName_;
size_t logfileSize_{100000000};
size_t keepaliveRequestsNumber_{0};
size_t pipeliningRequestsNumber_{0};

View File

@ -0,0 +1,68 @@
/**
*
* RedisClientManager.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/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/nosql/RedisClient.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/IOThreadStorage.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoop.h>
#include <string>
#include <memory>
namespace drogon
{
namespace nosql
{
class RedisClientManager : public trantor::NonCopyable
{
public:
void createRedisClients(const std::vector<trantor::EventLoop *> &ioLoops);
RedisClientPtr getRedisClient(const std::string &name)
{
assert(redisClientsMap_.find(name) != redisClientsMap_.end());
return redisClientsMap_[name];
}
RedisClientPtr getFastRedisClient(const std::string &name)
{
auto iter = redisFastClientsMap_.find(name);
assert(iter != redisFastClientsMap_.end());
return iter->second.getThreadData();
}
void createRedisClient(const std::string &name,
const std::string &host,
unsigned short port,
const std::string &password,
size_t connectionNum,
bool isFast);
// bool areAllRedisClientsAvailable() const noexcept;
private:
std::map<std::string, RedisClientPtr> redisClientsMap_;
std::map<std::string, IOThreadStorage<RedisClientPtr>> redisFastClientsMap_;
struct RedisInfo
{
std::string name_;
std::string addr_;
std::string password_;
unsigned short port_;
bool isFast_;
size_t connectionNumber_;
};
std::vector<RedisInfo> redisInfos_;
};
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,47 @@
/**
*
* RedisClientManagerSkipped.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "RedisClientManager.h"
#include <drogon/config.h>
#include <drogon/utils/Utilities.h>
#include <algorithm>
#include <cstdlib>
using namespace drogon::nosql;
using namespace drogon;
void RedisClientManager::createRedisClients(
const std::vector<trantor::EventLoop *> &ioloops)
{
return;
}
void RedisClientManager::createRedisClient(const std::string &name,
const std::string &host,
unsigned short port,
const std::string &password,
size_t connectionNum,
bool isFast)
{
LOG_FATAL << "Redis is supported by drogon, please install the "
"hiredis library first.";
abort();
}
// bool RedisClientManager::areAllRedisClientsAvailable() const noexcept
// {
// LOG_FATAL << "Redis is supported by drogon, please install the "
// "hiredis library first.";
// abort();
// }

View File

@ -42,6 +42,12 @@ class DbClient;
using DbClientPtr = std::shared_ptr<DbClient>;
class DbClientManager;
} // namespace orm
namespace nosql
{
class RedisClient;
using RedisClientPtr = std::shared_ptr<RedisClient>;
class RedisClientManager;
} // namespace nosql
} // namespace drogon
namespace trantor

View File

@ -0,0 +1,259 @@
/**
*
* @file RedisClient.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/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/nosql/RedisResult.h>
#include <drogon/nosql/RedisException.h>
#include <drogon/utils/string_view.h>
#include <trantor/net/InetAddress.h>
#include <trantor/utils/Logger.h>
#include <memory>
#include <functional>
#ifdef __cpp_impl_coroutine
#include <drogon/utils/coroutine.h>
#endif
namespace drogon
{
namespace nosql
{
#ifdef __cpp_impl_coroutine
class RedisClient;
class RedisTransaction;
namespace internal
{
struct RedisAwaiter : public CallbackAwaiter<RedisResult>
{
using RedisFunction =
std::function<void(RedisResultCallback &&, RedisExceptionCallback &&)>;
explicit RedisAwaiter(RedisFunction &&function)
: function_(std::move(function))
{
}
void await_suspend(std::coroutine_handle<> handle)
{
function_(
[handle, this](const RedisResult &result) {
this->setValue(result);
handle.resume();
},
[handle, this](const RedisException &e) {
LOG_ERROR << e.what();
this->setException(std::make_exception_ptr(e));
handle.resume();
});
}
private:
RedisFunction function_;
};
struct RedisTransactionAwaiter
: public CallbackAwaiter<std::shared_ptr<RedisTransaction>>
{
RedisTransactionAwaiter(RedisClient *client) : client_(client)
{
}
void await_suspend(std::coroutine_handle<> handle);
private:
RedisClient *client_;
};
} // namespace internal
#endif
class RedisTransaction;
/**
* @brief This class represents a redis client that contains several connections
* to a redis server.
*
*/
class RedisClient
{
public:
/**
* @brief Create a new redis client with multiple connections;
*
* @param serverAddress The server address.
* @param numberOfConnections The number of connections. 1 by default.
* @param password The password to authenticate if necessary.
* @return std::shared_ptr<RedisClient>
*/
static std::shared_ptr<RedisClient> newRedisClient(
const trantor::InetAddress &serverAddress,
size_t numberOfConnections = 1,
const std::string &password = "");
/**
* @brief Execute a redis command
*
* @param resultCallback The callback is called when a redis reply is
* received successfully.
* @param exceptionCallback The callback is called when an error occurs.
* @note When a redis reply with REDIS_REPLY_ERROR code is received, this
* callback is called.
* @param command The command to be executed. the command string can contain
* some placeholders for parameters, such as '%s', '%d', etc.
* @param ... The command parameters.
* For example:
* @code
redisClientPtr->execCommandAsync([](const RedisResult &r){
std::cout << r.getStringForDisplaying() << std::endl;
},[](const std::exception &err){
std::cerr << err.what() << std::endl;
}, "get %s", key.data());
@endcode
*/
virtual void execCommandAsync(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept = 0;
/**
* @brief Create a redis transaction object.
*
* @return std::shared_ptr<RedisTransaction>
*/
virtual std::shared_ptr<RedisTransaction> newTransaction() = 0;
/**
* @brief Create a transaction object in asynchronous mode.
*
* @return std::shared_ptr<RedisTransaction>
*/
virtual void newTransactionAsync(
const std::function<void(const std::shared_ptr<RedisTransaction> &)>
&callback) = 0;
virtual ~RedisClient() = default;
#ifdef __cpp_impl_coroutine
/**
* @brief Send a Redis command and await the RedisResult in a coroutine.
*
* @tparam Arguments
* @param command
* @param args
* @return internal::RedisAwaiter that can be awaited in a coroutine.
* For example:
* @code
try
{
auto result = co_await redisClient->execCommandCoro("get %s",
"keyname");
std::cout << result.getStringForDisplaying() << "\n";
}
catch(const RedisException &err)
{
std::cout << err.what() << "\n";
}
@endcode
*/
template <typename... Arguments>
internal::RedisAwaiter execCommandCoro(string_view command,
Arguments... args)
{
return internal::RedisAwaiter(
[command,
this,
args...](RedisResultCallback &&commandCallback,
RedisExceptionCallback &&exceptionCallback) {
execCommandAsync(std::move(commandCallback),
std::move(exceptionCallback),
command,
args...);
});
}
/**
* @brief await a RedisTransactionPtr in a coroutine.
*
* @return internal::RedisTransactionAwaiter that can be awaited in a
* coroutine.
* For example:
* @code
try
{
auto transPtr = co_await redisClient->newTransactionCoro();
...
}
catch(const RedisException &err)
{
std::cout << err.what() << "\n";
}
@endcode
*/
internal::RedisTransactionAwaiter newTransactionCoro()
{
return internal::RedisTransactionAwaiter(this);
}
#endif
};
class RedisTransaction : public RedisClient
{
public:
// virtual void cancel() = 0;
virtual void execute(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback) = 0;
#ifdef __cpp_impl_coroutine
/**
* @brief Send a "exec" command to execute the transaction and await a
* RedisResult in a coroutine.
*
* @return internal::RedisAwaiter that can be awaited in a coroutine.
* For example:
* @code
try
{
auto transPtr = co_await redisClient->newTransactionCoro();
...
auto result = co_await transPtr->executeCoro();
std::cout << result.getStringForDisplaying() << "\n";
}
catch(const RedisException &err)
{
std::cout << err.what() << "\n";
}
@endcode
*/
internal::RedisAwaiter executeCoro()
{
return internal::RedisAwaiter(
[this](RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback) {
execute(std::move(resultCallback),
std::move(exceptionCallback));
});
}
#endif
};
using RedisClientPtr = std::shared_ptr<RedisClient>;
using RedisTransactionPtr = std::shared_ptr<RedisTransaction>;
#ifdef __cpp_impl_coroutine
inline void internal::RedisTransactionAwaiter::await_suspend(
std::coroutine_handle<> handle)
{
assert(client_ != nullptr);
client_->newTransactionAsync(
[this, &handle](const std::shared_ptr<RedisTransaction> &transaction) {
if (transaction == nullptr)
setException(std::make_exception_ptr(
RedisException(RedisErrorCode::kInternalError,
"Failed to create transaction")));
else
setValue(transaction);
handle.resume();
});
}
#endif
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,60 @@
/**
*
* @file RedisException.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <exception>
#include <functional>
namespace drogon
{
namespace nosql
{
enum class RedisErrorCode
{
kNone = 0,
kUnknown,
kConnectionBroken,
kNoConnectionAvailable,
kRedisError,
kInternalError,
kTransactionCancelled,
kBadType
};
class RedisException final : public std::exception
{
public:
const char *what() const noexcept override
{
return message_.data();
}
RedisErrorCode code() const
{
return code_;
}
RedisException(RedisErrorCode code, const std::string &message)
: code_(code), message_(message)
{
}
RedisException(RedisErrorCode code, std::string &&message)
: code_(code), message_(std::move(message))
{
}
RedisException() = delete;
private:
std::string message_;
RedisErrorCode code_{RedisErrorCode::kNone};
};
using RedisExceptionCallback = std::function<void(const RedisException &)>;
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,124 @@
/**
*
* @file RedisResult.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <vector>
#include <string>
#include <memory>
#include <functional>
struct redisReply;
namespace drogon
{
namespace nosql
{
enum class RedisResultType
{
kInteger = 0,
kString,
kArray,
kStatus,
kNil,
kError
};
/**
* @brief This class represents a redis reply with no error.
* @note Limited by the hiredis library, the RedisResult object is only
* available in the context of the result callback, one can't hold or copy or
* move a RedisResult object for later use after the callback is returned.
*/
class RedisResult
{
public:
explicit RedisResult(redisReply *result) : result_(result)
{
}
~RedisResult() = default;
/**
* @brief Return the type of the result_
* @return RedisResultType
* @note The kError type is never obtained here.
*/
RedisResultType type() const noexcept;
/**
* @brief Get the string value of the result.
*
* @return std::string
* @note Calling the method of a result object which is the kArray type
* throws a runtime exception.
*/
std::string asString() const noexcept(false);
/**
* @brief Get the array value of the result.
*
* @return std::vector<RedisResult>
* @note Calling the method of a result object whose type is not kArray type
* throws a runtime exception.
*/
std::vector<RedisResult> asArray() const noexcept(false);
/**
* @brief Get the integer value of the result.
*
* @return long long
* @note Calling the method of a result object whose type is not kInteger
* type throws a runtime exception.
*/
long long asInteger() const noexcept(false);
/**
* @brief Get the string for displaying the result.
*
* @return std::string
*/
std::string getStringForDisplaying() const noexcept;
/**
* @brief Get the string for displaying with indent.
*
* @param indent The indent value.
* @return std::string
*/
std::string getStringForDisplayingWithIndent(
size_t indent = 0) const noexcept;
/**
* @brief return true if the result object is nil.
*
* @return true
* @return false
*/
bool isNil() const noexcept;
/**
* @brief Check if the result object is not nil.
*
* @return true
* @return false
*/
explicit operator bool() const
{
return !isNil();
}
private:
redisReply *result_;
};
using RedisResultCallback = std::function<void(const RedisResult &)>;
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,233 @@
/**
*
* @file RedisClientImpl.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "RedisClientImpl.h"
#include "RedisTransactionImpl.h"
using namespace drogon::nosql;
std::shared_ptr<RedisClient> RedisClient::newRedisClient(
const trantor::InetAddress &serverAddress,
size_t connectionNumber,
const std::string &password)
{
return std::make_shared<RedisClientImpl>(serverAddress,
connectionNumber,
password);
}
RedisClientImpl::RedisClientImpl(const trantor::InetAddress &serverAddress,
size_t numberOfConnections,
std::string password)
: loops_(numberOfConnections < std::thread::hardware_concurrency()
? numberOfConnections
: std::thread::hardware_concurrency(),
"RedisLoop"),
serverAddr_(serverAddress),
password_(std::move(password)),
numberOfConnections_(numberOfConnections)
{
loops_.start();
for (size_t i = 0; i < numberOfConnections_; ++i)
{
auto loop = loops_.getNextLoop();
loop->queueInLoop([this, loop]() {
std::lock_guard<std::mutex> lock(connectionsMutex_);
connections_.insert(newConnection(loop));
});
}
}
RedisConnectionPtr RedisClientImpl::newConnection(trantor::EventLoop *loop)
{
auto conn = std::make_shared<RedisConnection>(serverAddr_, password_, loop);
std::weak_ptr<RedisClientImpl> thisWeakPtr = shared_from_this();
conn->setConnectCallback([thisWeakPtr](RedisConnectionPtr &&conn) {
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
{
std::lock_guard<std::mutex> lock(thisPtr->connectionsMutex_);
thisPtr->readyConnections_.push_back(conn);
}
thisPtr->handleNextTask(conn);
}
});
conn->setDisconnectCallback([thisWeakPtr](RedisConnectionPtr &&conn) {
// assert(status == REDIS_CONNECTED);
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
std::lock_guard<std::mutex> lock(thisPtr->connectionsMutex_);
thisPtr->connections_.erase(conn);
for (auto iter = thisPtr->readyConnections_.begin();
iter != thisPtr->readyConnections_.end();
++iter)
{
if (*iter == conn)
{
thisPtr->readyConnections_.erase(iter);
break;
}
}
auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
assert(loop);
loop->runAfter(2.0, [thisPtr, loop, conn]() {
thisPtr->connections_.insert(thisPtr->newConnection(loop));
});
}
});
conn->setIdleCallback([thisWeakPtr](const RedisConnectionPtr &connPtr) {
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->handleNextTask(connPtr);
}
});
return conn;
}
void RedisClientImpl::execCommandAsync(
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept
{
RedisConnectionPtr connPtr;
{
std::lock_guard<std::mutex> lock(connectionsMutex_);
if (!readyConnections_.empty())
{
if (connectionPos_ >= readyConnections_.size())
{
connPtr = readyConnections_[0];
connectionPos_ = 1;
}
else
{
connPtr = readyConnections_[connectionPos_++];
}
}
}
if (connPtr)
{
va_list args;
va_start(args, command);
connPtr->sendvCommand(command,
std::move(resultCallback),
std::move(exceptionCallback),
args);
va_end(args);
}
else
{
LOG_TRACE << "no connection available, push command to buffer";
std::weak_ptr<RedisClientImpl> thisWeakPtr = shared_from_this();
va_list args;
va_start(args, command);
auto formattedCmd = RedisConnection::getFormattedCommand(command, args);
va_end(args);
std::lock_guard<std::mutex> lock(connectionsMutex_);
tasks_.emplace([thisWeakPtr,
resultCallback = std::move(resultCallback),
exceptionCallback = std::move(exceptionCallback),
formattedCmd = std::move(formattedCmd)](
const RedisConnectionPtr &connPtr) mutable {
connPtr->sendFormattedCommand(std::move(formattedCmd),
std::move(resultCallback),
std::move(exceptionCallback));
});
}
}
RedisClientImpl::~RedisClientImpl()
{
std::lock_guard<std::mutex> lock(connectionsMutex_);
for (auto &conn : connections_)
{
conn->disconnect();
}
readyConnections_.clear();
connections_.clear();
}
void RedisClientImpl::newTransactionAsync(
const std::function<void(const std::shared_ptr<RedisTransaction> &)>
&callback)
{
RedisConnectionPtr connPtr;
{
std::lock_guard<std::mutex> lock(connectionsMutex_);
if (!readyConnections_.empty())
{
connPtr = readyConnections_[readyConnections_.size() - 1];
readyConnections_.resize(readyConnections_.size() - 1);
}
}
if (connPtr)
{
callback(makeTransaction(connPtr));
}
else
{
std::weak_ptr<RedisClientImpl> thisWeakPtr = shared_from_this();
std::lock_guard<std::mutex> lock(connectionsMutex_);
tasks_.emplace(
[callback, thisWeakPtr](const RedisConnectionPtr &connPtr) {
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->newTransactionAsync(callback);
}
});
}
}
std::shared_ptr<RedisTransaction> RedisClientImpl::makeTransaction(
const RedisConnectionPtr &connPtr)
{
std::weak_ptr<RedisClientImpl> thisWeakPtr = shared_from_this();
auto trans = std::shared_ptr<RedisTransactionImpl>(
new RedisTransactionImpl(connPtr),
[thisWeakPtr, connPtr](RedisTransactionImpl *p) {
delete p;
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
{
std::lock_guard<std::mutex> lock(
thisPtr->connectionsMutex_);
thisPtr->readyConnections_.push_back(connPtr);
}
thisPtr->handleNextTask(connPtr);
}
});
trans->doBegin();
return trans;
}
void RedisClientImpl::handleNextTask(const RedisConnectionPtr &connPtr)
{
std::function<void(const RedisConnectionPtr &)> task;
{
std::lock_guard<std::mutex> lock(connectionsMutex_);
if (!tasks_.empty())
{
task = std::move(tasks_.front());
tasks_.pop();
}
}
if (task)
{
task(connPtr);
}
}

View File

@ -0,0 +1,72 @@
/**
*
* @file RedisClientImpl.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "RedisConnection.h"
#include <drogon/nosql/RedisClient.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoopThreadPool.h>
#include <vector>
#include <unordered_set>
#include <queue>
#include <future>
namespace drogon
{
namespace nosql
{
class RedisClientImpl final
: public RedisClient,
public trantor::NonCopyable,
public std::enable_shared_from_this<RedisClientImpl>
{
public:
RedisClientImpl(const trantor::InetAddress &serverAddress,
size_t numberOfConnections,
std::string password = "");
void execCommandAsync(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept override;
~RedisClientImpl() override;
RedisTransactionPtr newTransaction() override
{
std::promise<RedisTransactionPtr> prom;
auto f = prom.get_future();
newTransactionAsync([&prom](const RedisTransactionPtr &transPtr) {
prom.set_value(transPtr);
});
return f.get();
}
void newTransactionAsync(
const std::function<void(const RedisTransactionPtr &)> &callback)
override;
private:
trantor::EventLoopThreadPool loops_;
std::mutex connectionsMutex_;
std::unordered_set<RedisConnectionPtr> connections_;
std::vector<RedisConnectionPtr> readyConnections_;
size_t connectionPos_{0};
RedisConnectionPtr newConnection(trantor::EventLoop *loop);
const trantor::InetAddress serverAddr_;
const std::string password_;
const size_t numberOfConnections_;
std::queue<std::function<void(const RedisConnectionPtr &)>> tasks_;
std::shared_ptr<RedisTransaction> makeTransaction(
const RedisConnectionPtr &connPtr);
void handleNextTask(const RedisConnectionPtr &connPtr);
};
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,208 @@
/**
*
* @file RedisClientLockFree.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "RedisClientLockFree.h"
#include "RedisTransactionImpl.h"
using namespace drogon::nosql;
RedisClientLockFree::RedisClientLockFree(
const trantor::InetAddress &serverAddress,
size_t numberOfConnections,
trantor::EventLoop *loop,
std::string password)
: loop_(loop),
serverAddr_(serverAddress),
password_(std::move(password)),
numberOfConnections_(numberOfConnections)
{
assert(loop_);
for (size_t i = 0; i < numberOfConnections_; ++i)
{
loop_->queueInLoop([this]() { connections_.insert(newConnection()); });
}
}
RedisConnectionPtr RedisClientLockFree::newConnection()
{
loop_->assertInLoopThread();
auto conn =
std::make_shared<RedisConnection>(serverAddr_, password_, loop_);
std::weak_ptr<RedisClientLockFree> thisWeakPtr = shared_from_this();
conn->setConnectCallback([thisWeakPtr](RedisConnectionPtr &&conn) {
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->readyConnections_.push_back(conn);
thisPtr->handleNextTask(conn);
}
});
conn->setDisconnectCallback([thisWeakPtr](RedisConnectionPtr &&conn) {
// assert(status == REDIS_CONNECTED);
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->connections_.erase(conn);
for (auto iter = thisPtr->readyConnections_.begin();
iter != thisPtr->readyConnections_.end();
++iter)
{
if (*iter == conn)
{
thisPtr->readyConnections_.erase(iter);
break;
}
}
thisPtr->loop_->runAfter(2.0, [thisPtr, conn]() {
thisPtr->connections_.insert(thisPtr->newConnection());
});
}
});
conn->setIdleCallback([thisWeakPtr](const RedisConnectionPtr &connPtr) {
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->handleNextTask(connPtr);
}
});
return conn;
}
void RedisClientLockFree::execCommandAsync(
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept
{
loop_->assertInLoopThread();
RedisConnectionPtr connPtr;
{
if (!readyConnections_.empty())
{
if (connectionPos_ >= readyConnections_.size())
{
connPtr = readyConnections_[0];
connectionPos_ = 1;
}
else
{
connPtr = readyConnections_[connectionPos_++];
}
}
}
if (connPtr)
{
va_list args;
va_start(args, command);
connPtr->sendvCommand(command,
std::move(resultCallback),
std::move(exceptionCallback),
args);
va_end(args);
}
else
{
LOG_TRACE << "no connection available, push command to buffer";
std::weak_ptr<RedisClientLockFree> thisWeakPtr = shared_from_this();
va_list args;
va_start(args, command);
auto formattedCmd = RedisConnection::getFormattedCommand(command, args);
va_end(args);
tasks_.emplace([thisWeakPtr,
resultCallback = std::move(resultCallback),
exceptionCallback = std::move(exceptionCallback),
formattedCmd = std::move(formattedCmd)](
const RedisConnectionPtr &connPtr) mutable {
connPtr->sendFormattedCommand(std::move(formattedCmd),
std::move(resultCallback),
std::move(exceptionCallback));
});
}
}
RedisClientLockFree::~RedisClientLockFree()
{
for (auto &conn : connections_)
{
conn->disconnect();
}
readyConnections_.clear();
connections_.clear();
}
void RedisClientLockFree::newTransactionAsync(
const std::function<void(const std::shared_ptr<RedisTransaction> &)>
&callback)
{
loop_->assertInLoopThread();
RedisConnectionPtr connPtr;
if (!readyConnections_.empty())
{
connPtr = readyConnections_[readyConnections_.size() - 1];
readyConnections_.resize(readyConnections_.size() - 1);
}
if (connPtr)
{
callback(makeTransaction(connPtr));
}
else
{
std::weak_ptr<RedisClientLockFree> thisWeakPtr = shared_from_this();
tasks_.emplace(
[callback, thisWeakPtr](const RedisConnectionPtr &connPtr) {
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->newTransactionAsync(callback);
}
});
}
}
std::shared_ptr<RedisTransaction> RedisClientLockFree::makeTransaction(
const RedisConnectionPtr &connPtr)
{
std::weak_ptr<RedisClientLockFree> thisWeakPtr = shared_from_this();
auto trans = std::shared_ptr<RedisTransactionImpl>(
new RedisTransactionImpl(connPtr),
[thisWeakPtr, connPtr](RedisTransactionImpl *p) {
delete p;
auto thisPtr = thisWeakPtr.lock();
if (thisPtr)
{
thisPtr->readyConnections_.push_back(connPtr);
thisPtr->handleNextTask(connPtr);
}
});
trans->doBegin();
return trans;
}
void RedisClientLockFree::handleNextTask(const RedisConnectionPtr &connPtr)
{
loop_->assertInLoopThread();
std::function<void(const RedisConnectionPtr &)> task;
if (!tasks_.empty())
{
task = std::move(tasks_.front());
tasks_.pop();
}
if (task)
{
task(connPtr);
}
}

View File

@ -0,0 +1,72 @@
/**
*
* @file RedisClientLockFree.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "RedisConnection.h"
#include <drogon/nosql/RedisClient.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/EventLoopThreadPool.h>
#include <vector>
#include <unordered_set>
#include <queue>
#include <future>
namespace drogon
{
namespace nosql
{
class RedisClientLockFree final
: public RedisClient,
public trantor::NonCopyable,
public std::enable_shared_from_this<RedisClientLockFree>
{
public:
RedisClientLockFree(const trantor::InetAddress &serverAddress,
size_t numberOfConnections,
trantor::EventLoop *loop,
std::string password = "");
void execCommandAsync(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept override;
~RedisClientLockFree() override;
RedisTransactionPtr newTransaction() override
{
LOG_ERROR
<< "You can't use the synchronous interface in the fast redis "
"client, please use the asynchronous version "
"(newTransactionAsync)";
assert(0);
return nullptr;
}
void newTransactionAsync(
const std::function<void(const RedisTransactionPtr &)> &callback)
override;
private:
trantor::EventLoop *loop_;
std::unordered_set<RedisConnectionPtr> connections_;
std::vector<RedisConnectionPtr> readyConnections_;
size_t connectionPos_{0};
RedisConnectionPtr newConnection();
const trantor::InetAddress serverAddr_;
const std::string password_;
const size_t numberOfConnections_;
std::queue<std::function<void(const RedisConnectionPtr &)>> tasks_;
std::shared_ptr<RedisTransaction> makeTransaction(
const RedisConnectionPtr &connPtr);
void handleNextTask(const RedisConnectionPtr &connPtr);
};
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,92 @@
/**
*
* @file RedisClientManager.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "../../lib/src/RedisClientManager.h"
#include "RedisClientLockFree.h"
#include "RedisClientImpl.h"
#include <algorithm>
using namespace drogon::nosql;
using namespace drogon;
void RedisClientManager::createRedisClients(
const std::vector<trantor::EventLoop *> &ioLoops)
{
assert(redisClientsMap_.empty());
assert(redisFastClientsMap_.empty());
for (auto &redisInfo : redisInfos_)
{
if (redisInfo.isFast_)
{
redisFastClientsMap_[redisInfo.name_] =
IOThreadStorage<RedisClientPtr>();
redisFastClientsMap_[redisInfo.name_].init([&](RedisClientPtr &c,
size_t idx) {
assert(idx == ioLoops[idx]->index());
LOG_TRACE << "create fast redis client for the thread " << idx;
c = std::make_shared<RedisClientLockFree>(
trantor::InetAddress(redisInfo.addr_, redisInfo.port_),
redisInfo.connectionNumber_,
ioLoops[idx],
redisInfo.password_);
});
}
else
{
redisClientsMap_[redisInfo.name_] =
std::make_shared<RedisClientImpl>(
trantor::InetAddress(redisInfo.addr_, redisInfo.port_),
redisInfo.connectionNumber_,
redisInfo.password_);
}
}
}
void RedisClientManager::createRedisClient(const std::string &name,
const std::string &addr,
unsigned short port,
const std::string &password,
const size_t connectionNum,
const bool isFast)
{
RedisInfo info;
info.name_ = name;
info.addr_ = addr;
info.port_ = port;
info.password_ = password;
info.connectionNumber_ = connectionNum;
info.isFast_ = isFast;
redisInfos_.emplace_back(std::move(info));
}
// bool RedisClientManager::areAllRedisClientsAvailable() const noexcept
//{
// for (auto const &pair : redisClientsMap_)
// {
// if (!(pair.second)->hasAvailableConnections())
// return false;
// }
// auto loop = trantor::EventLoop::getEventLoopOfCurrentThread();
// if (loop && loop->index() < app().getThreadNum())
// {
// for (auto const &pair : redisFastClientsMap_)
// {
// if (!(*(pair.second))->hasAvailableConnections())
// return false;
// }
// }
// return true;
//}

View File

@ -0,0 +1,254 @@
/**
*
* @file RedisConnection.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "RedisConnection.h"
#include <drogon/nosql/RedisResult.h>
#include <future>
using namespace drogon::nosql;
RedisConnection::RedisConnection(const trantor::InetAddress &serverAddress,
const std::string &password,
trantor::EventLoop *loop)
: serverAddr_(serverAddress), password_(password), loop_(loop)
{
assert(loop_);
loop_->queueInLoop([this]() { startConnectionInLoop(); });
}
void RedisConnection::startConnectionInLoop()
{
loop_->assertInLoopThread();
assert(!redisContext_);
redisContext_ =
::redisAsyncConnect(serverAddr_.toIp().c_str(), serverAddr_.toPort());
status_ = ConnectStatus::kConnecting;
if (redisContext_->err)
{
LOG_ERROR << "Error: " << redisContext_->errstr;
if (disconnectCallback_)
{
disconnectCallback_(shared_from_this());
}
}
redisContext_->ev.addWrite = addWrite;
redisContext_->ev.delWrite = delWrite;
redisContext_->ev.addRead = addRead;
redisContext_->ev.delRead = delRead;
redisContext_->ev.cleanup = cleanup;
redisContext_->ev.data = this;
channel_ = std::make_unique<trantor::Channel>(loop_, redisContext_->c.fd);
channel_->setReadCallback([this]() { handleRedisRead(); });
channel_->setWriteCallback([this]() { handleRedisWrite(); });
redisAsyncSetConnectCallback(
redisContext_, [](const redisAsyncContext *context, int status) {
auto thisPtr = static_cast<RedisConnection *>(context->ev.data);
if (status != REDIS_OK)
{
LOG_ERROR << "Failed to connect to "
<< thisPtr->serverAddr_.toIpPort() << "! "
<< context->errstr;
thisPtr->handleDisconnect();
if (thisPtr->disconnectCallback_)
{
thisPtr->disconnectCallback_(thisPtr->shared_from_this());
}
}
else
{
LOG_TRACE << "Connected successfully to "
<< thisPtr->serverAddr_.toIpPort();
if (thisPtr->password_.empty())
{
thisPtr->status_ = ConnectStatus::kConnected;
if (thisPtr->connectCallback_)
{
thisPtr->connectCallback_(thisPtr->shared_from_this());
}
}
else
{
std::weak_ptr<RedisConnection> weakThisPtr =
thisPtr->shared_from_this();
thisPtr->sendCommand(
[weakThisPtr](const RedisResult &r) {
auto thisPtr = weakThisPtr.lock();
if (!thisPtr)
return;
if (r.asString() == "OK")
{
if (thisPtr->connectCallback_)
thisPtr->connectCallback_(
thisPtr->shared_from_this());
}
else
{
LOG_ERROR << r.asString();
thisPtr->disconnect();
}
},
[weakThisPtr](const std::exception &err) {
LOG_ERROR << err.what();
auto thisPtr = weakThisPtr.lock();
if (!thisPtr)
return;
thisPtr->disconnect();
},
"auth %s",
thisPtr->password_.data());
}
}
});
redisAsyncSetDisconnectCallback(
redisContext_, [](const redisAsyncContext *context, int status) {
auto thisPtr = static_cast<RedisConnection *>(context->ev.data);
thisPtr->handleDisconnect();
if (thisPtr->disconnectCallback_)
{
thisPtr->disconnectCallback_(thisPtr->shared_from_this());
}
LOG_TRACE << "Disconnected from "
<< thisPtr->serverAddr_.toIpPort();
});
}
void RedisConnection::handleDisconnect()
{
LOG_TRACE << "handleDisconnect";
loop_->assertInLoopThread();
while ((!resultCallbacks_.empty()) && (!exceptionCallbacks_.empty()))
{
if (exceptionCallbacks_.front())
{
exceptionCallbacks_.front()(
RedisException(RedisErrorCode::kConnectionBroken,
"Connection is broken"));
}
resultCallbacks_.pop();
exceptionCallbacks_.pop();
}
status_ = ConnectStatus::kEnd;
channel_->disableAll();
channel_->remove();
redisContext_->ev.addWrite = nullptr;
redisContext_->ev.delWrite = nullptr;
redisContext_->ev.addRead = nullptr;
redisContext_->ev.delRead = nullptr;
redisContext_->ev.cleanup = nullptr;
redisContext_->ev.data = nullptr;
}
void RedisConnection::addWrite(void *userData)
{
auto thisPtr = static_cast<RedisConnection *>(userData);
assert(thisPtr->channel_);
thisPtr->channel_->enableWriting();
}
void RedisConnection::delWrite(void *userData)
{
auto thisPtr = static_cast<RedisConnection *>(userData);
assert(thisPtr->channel_);
thisPtr->channel_->disableWriting();
}
void RedisConnection::addRead(void *userData)
{
auto thisPtr = static_cast<RedisConnection *>(userData);
assert(thisPtr->channel_);
thisPtr->channel_->enableReading();
}
void RedisConnection::delRead(void *userData)
{
auto thisPtr = static_cast<RedisConnection *>(userData);
assert(thisPtr->channel_);
thisPtr->channel_->disableReading();
}
void RedisConnection::cleanup(void *userData)
{
LOG_TRACE << "cleanup";
}
void RedisConnection::handleRedisRead()
{
redisAsyncHandleRead(redisContext_);
}
void RedisConnection::handleRedisWrite()
{
if (redisContext_->c.flags == REDIS_DISCONNECTING)
{
channel_->disableAll();
channel_->remove();
}
redisAsyncHandleWrite(redisContext_);
}
void RedisConnection::sendCommandInLoop(
const std::string &command,
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback)
{
resultCallbacks_.emplace(std::move(resultCallback));
exceptionCallbacks_.emplace(std::move(exceptionCallback));
redisAsyncFormattedCommand(
redisContext_,
[](redisAsyncContext *context, void *r, void *userData) {
auto thisPtr = static_cast<RedisConnection *>(context->ev.data);
thisPtr->handleResult(static_cast<redisReply *>(r));
},
nullptr,
command.c_str(),
command.length());
}
void RedisConnection::handleResult(redisReply *result)
{
LOG_TRACE << "redis reply: " << result->type;
auto commandCallback = std::move(resultCallbacks_.front());
resultCallbacks_.pop();
auto exceptionCallback = std::move(exceptionCallbacks_.front());
exceptionCallbacks_.pop();
if (result->type != REDIS_REPLY_ERROR)
{
commandCallback(RedisResult(result));
}
else
{
exceptionCallback(
RedisException(RedisErrorCode::kRedisError,
std::string{result->str, result->len}));
}
if (resultCallbacks_.empty())
{
assert(exceptionCallbacks_.empty());
if (idleCallback_)
{
idleCallback_(shared_from_this());
}
}
}
void RedisConnection::disconnect()
{
std::promise<int> pro;
auto f = pro.get_future();
auto thisPtr = shared_from_this();
loop_->runInLoop([thisPtr, &pro]() {
redisAsyncDisconnect(thisPtr->redisContext_);
pro.set_value(1);
});
f.get();
}

View File

@ -0,0 +1,195 @@
/**
*
* @file RedisConnection.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/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/utils/string_view.h>
#include <drogon/nosql/RedisException.h>
#include <drogon/nosql/RedisResult.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/net/InetAddress.h>
#include <trantor/net/EventLoop.h>
#include <trantor/net/Channel.h>
#include <hiredis/async.h>
#include <hiredis/hiredis.h>
#include <memory>
#include <queue>
namespace drogon
{
namespace nosql
{
enum class ConnectStatus
{
kNone = 0,
kConnecting,
kConnected,
kEnd
};
class RedisConnection : public trantor::NonCopyable,
public std::enable_shared_from_this<RedisConnection>
{
public:
RedisConnection(const trantor::InetAddress &serverAddress,
const std::string &password,
trantor::EventLoop *loop);
void setConnectCallback(
const std::function<void(std::shared_ptr<RedisConnection> &&)>
&callback)
{
connectCallback_ = callback;
}
void setDisconnectCallback(
const std::function<void(std::shared_ptr<RedisConnection> &&)>
&callback)
{
disconnectCallback_ = callback;
}
void setIdleCallback(
const std::function<void(const std::shared_ptr<RedisConnection> &)>
&callback)
{
idleCallback_ = callback;
}
static std::string getFormattedCommand(const string_view &command,
va_list ap) noexcept(false)
{
char *cmd;
auto len = redisvFormatCommand(&cmd, command.data(), ap);
if (len == -1)
{
throw RedisException(RedisErrorCode::kInternalError,
"Out of memory");
}
else if (len == -2)
{
throw RedisException(RedisErrorCode::kInternalError,
"Invalid format string");
}
else if (len <= 0)
{
throw RedisException(RedisErrorCode::kInternalError,
"Unknown format error");
}
std::string fullCommand{cmd, static_cast<size_t>(len)};
free(cmd);
return fullCommand;
}
void sendFormattedCommand(std::string &&command,
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback)
{
if (loop_->isInLoopThread())
{
sendCommandInLoop(command,
std::move(resultCallback),
std::move(exceptionCallback));
}
else
{
loop_->queueInLoop(
[this,
callback = std::move(resultCallback),
exceptionCallback = std::move(exceptionCallback),
command = std::move(command)]() mutable {
sendCommandInLoop(command,
std::move(callback),
std::move(exceptionCallback));
});
}
}
void sendvCommand(string_view command,
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
va_list ap)
{
LOG_TRACE << "redis command: " << command;
try
{
auto fullCommand = getFormattedCommand(command, ap);
if (loop_->isInLoopThread())
{
sendCommandInLoop(fullCommand,
std::move(resultCallback),
std::move(exceptionCallback));
}
else
{
loop_->queueInLoop(
[this,
callback = std::move(resultCallback),
exceptionCallback = std::move(exceptionCallback),
fullCommand = std::move(fullCommand)]() mutable {
sendCommandInLoop(fullCommand,
std::move(callback),
std::move(exceptionCallback));
});
}
}
catch (const RedisException &err)
{
exceptionCallback(err);
}
}
~RedisConnection()
{
LOG_TRACE << (int)status_;
if (redisContext_ && status_ != ConnectStatus::kEnd)
redisAsyncDisconnect(redisContext_);
}
void disconnect();
void sendCommand(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...)
{
va_list args;
va_start(args, command);
sendvCommand(command,
std::move(resultCallback),
std::move(exceptionCallback),
args);
va_end(args);
}
private:
redisAsyncContext *redisContext_{nullptr};
const trantor::InetAddress serverAddr_;
const std::string password_;
trantor::EventLoop *loop_{nullptr};
std::unique_ptr<trantor::Channel> channel_{nullptr};
std::function<void(std::shared_ptr<RedisConnection> &&)> connectCallback_;
std::function<void(std::shared_ptr<RedisConnection> &&)>
disconnectCallback_;
std::function<void(const std::shared_ptr<RedisConnection> &)> idleCallback_;
std::queue<RedisResultCallback> resultCallbacks_;
std::queue<RedisExceptionCallback> exceptionCallbacks_;
ConnectStatus status_{ConnectStatus::kNone};
void startConnectionInLoop();
static void addWrite(void *userData);
static void delWrite(void *userData);
static void addRead(void *userData);
static void delRead(void *userData);
static void cleanup(void *userData);
void handleRedisRead();
void handleRedisWrite();
void handleResult(redisReply *result);
void sendCommandInLoop(const std::string &command,
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback);
void handleDisconnect();
};
using RedisConnectionPtr = std::shared_ptr<RedisConnection>;
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,127 @@
/**
*
* @file RedisResult.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include <drogon/nosql/RedisResult.h>
#include <drogon/nosql/RedisClient.h>
#include <hiredis/hiredis.h>
using namespace drogon::nosql;
std::string RedisResult::getStringForDisplaying() const noexcept
{
return getStringForDisplayingWithIndent(0);
}
std::string RedisResult::getStringForDisplayingWithIndent(
size_t indent) const noexcept
{
switch (result_->type)
{
case REDIS_REPLY_STRING:
return "\"" + std::string{result_->str, result_->len} + "\"";
case REDIS_REPLY_STATUS:
return std::string{result_->str, result_->len};
case REDIS_REPLY_ERROR:
return "'ERROR:" + std::string{result_->str, result_->len} + "'";
case REDIS_REPLY_NIL:
return "(nil)";
case REDIS_REPLY_INTEGER:
return std::to_string(result_->integer);
case REDIS_REPLY_ARRAY:
{
std::string ret;
for (size_t i = 0; i < result_->elements; ++i)
{
std::string lineNum = std::to_string(i + 1) + ") ";
if (i > 0)
{
ret += std::string(indent, ' ');
}
ret += lineNum;
ret += RedisResult(result_->element[i])
.getStringForDisplayingWithIndent(lineNum.length());
if (i != result_->elements - 1)
{
ret += '\n';
}
}
return ret;
}
default:
return "*";
}
}
std::string RedisResult::asString() const noexcept(false)
{
auto rtype = type();
if (rtype == RedisResultType::kString ||
rtype == RedisResultType::kStatus || rtype == RedisResultType::kError)
{
return std::string(result_->str, result_->len);
}
else if (rtype == RedisResultType::kInteger)
{
return std::to_string(result_->integer);
}
else
{
throw RedisException(RedisErrorCode::kBadType, "bad type");
}
}
RedisResultType RedisResult::type() const noexcept
{
switch (result_->type)
{
case REDIS_REPLY_STRING:
return RedisResultType::kString;
case REDIS_REPLY_ARRAY:
return RedisResultType::kArray;
case REDIS_REPLY_INTEGER:
return RedisResultType::kInteger;
case REDIS_REPLY_NIL:
return RedisResultType::kNil;
case REDIS_REPLY_STATUS:
return RedisResultType::kStatus;
case REDIS_REPLY_ERROR:
default:
return RedisResultType::kError;
}
}
std::vector<RedisResult> RedisResult::asArray() const noexcept(false)
{
auto rtype = type();
if (rtype == RedisResultType::kArray)
{
std::vector<RedisResult> array;
for (size_t i = 0; i < result_->elements; ++i)
{
array.emplace_back(result_->element[i]);
}
return array;
}
throw RedisException(RedisErrorCode::kBadType, "bad type");
}
long long RedisResult::asInteger() const noexcept(false)
{
if (type() == RedisResultType::kInteger)
return result_->integer;
throw RedisException(RedisErrorCode::kBadType, "bad type");
}
bool RedisResult::isNil() const noexcept
{
return type() == RedisResultType::kNil;
}

View File

@ -0,0 +1,83 @@
/**
*
* @file RedisConnection.cc
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "RedisTransactionImpl.h"
using namespace drogon::nosql;
RedisTransactionImpl::RedisTransactionImpl(RedisConnectionPtr connPtr) noexcept
: connPtr_(std::move(connPtr))
{
}
void RedisTransactionImpl::execute(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback)
{
execCommandAsync(
[thisPtr = shared_from_this(),
resultCallback =
std::move(resultCallback)](const RedisResult &result) {
thisPtr->isExecutedOrCancelled_ = true;
resultCallback(result);
},
std::move(exceptionCallback),
"EXEC");
}
void RedisTransactionImpl::execCommandAsync(
RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept
{
if (isExecutedOrCancelled_)
{
exceptionCallback(RedisException(RedisErrorCode::kTransactionCancelled,
"Transaction was cancelled"));
return;
}
va_list args;
va_start(args, command);
connPtr_->sendvCommand(
command,
std::move(resultCallback),
[thisPtr = shared_from_this(),
exceptionCallback =
std::move(exceptionCallback)](const RedisException &err) {
LOG_ERROR << err.what();
thisPtr->isExecutedOrCancelled_ = true;
exceptionCallback(err);
},
args);
va_end(args);
}
void RedisTransactionImpl::doBegin()
{
assert(!isExecutedOrCancelled_);
execCommandAsync([](const RedisResult &result) {},
[](const RedisException &err) {},
"MULTI");
}
RedisTransactionImpl::~RedisTransactionImpl()
{
if (!isExecutedOrCancelled_)
{
LOG_WARN << "The transaction is not executed before being destroyed";
connPtr_->sendCommand([](const RedisResult &result) {},
[](const RedisException &err) {},
"DISCARD");
}
LOG_TRACE << "transaction is destroyed";
}

View File

@ -0,0 +1,55 @@
/**
*
* @file RedisConnection.h
* @author An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include "RedisConnection.h"
#include <drogon/nosql/RedisClient.h>
#include <memory>
namespace drogon
{
namespace nosql
{
class RedisTransactionImpl final
: public RedisTransaction,
public std::enable_shared_from_this<RedisTransactionImpl>
{
public:
explicit RedisTransactionImpl(RedisConnectionPtr connection) noexcept;
// virtual void cancel() override;
void execute(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback) override;
void execCommandAsync(RedisResultCallback &&resultCallback,
RedisExceptionCallback &&exceptionCallback,
string_view command,
...) noexcept override;
std::shared_ptr<RedisTransaction> newTransaction() override
{
return shared_from_this();
}
void newTransactionAsync(
const std::function<void(const std::shared_ptr<RedisTransaction> &)>
&callback) override
{
callback(shared_from_this());
}
void doBegin();
~RedisTransactionImpl() override;
private:
bool isExecutedOrCancelled_{false};
RedisConnectionPtr connPtr_;
};
} // namespace nosql
} // namespace drogon

View File

@ -0,0 +1,9 @@
link_libraries(${PROJECT_NAME})
add_executable(redis_test
redis_test.cc
)
set_property(TARGET redis_test PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD})
set_property(TARGET redis_test PROPERTY CXX_STANDARD_REQUIRED ON)
set_property(TARGET redis_test PROPERTY CXX_EXTENSIONS OFF)

View File

@ -0,0 +1,133 @@
#include <drogon/nosql/RedisClient.h>
#include <drogon/drogon.h>
#include <iostream>
#include <thread>
#define RESET "\033[0m"
#define RED "\033[31m" /* Red */
#define GREEN "\033[32m" /* Green */
using namespace std::chrono_literals;
using namespace drogon::nosql;
#ifdef __cpp_impl_coroutine
#define TEST_COUNT 8
#else
#define TEST_COUNT 7
#endif
std::promise<int> pro;
auto globalf = pro.get_future();
int counter = 0;
void addCount(int &count, std::promise<int> &pro)
{
++count;
// LOG_DEBUG << count;
if (count == TEST_COUNT)
{
pro.set_value(1);
}
}
void testoutput(bool isGood, const std::string &testMessage)
{
if (isGood)
{
std::cout << GREEN << counter + 1 << ".\t" << testMessage << "\t\tOK\n";
std::cout << RESET;
addCount(counter, pro);
}
else
{
std::cout << RED << testMessage << "\t\tBAD\n";
std::cout << RESET;
exit(1);
}
}
void doTest(const RedisClientPtr &redisClient)
{
// std::this_thread::sleep_for(1s);
redisClient->newTransactionAsync([](const RedisTransactionPtr &transPtr) {
// 1
transPtr->execCommandAsync(
[](const drogon::nosql::RedisResult &r) {
testoutput(true, r.getStringForDisplaying());
},
[](const std::exception &err) { testoutput(false, err.what()); },
"ping");
// 2
transPtr->execute(
[](const drogon::nosql::RedisResult &r) {
testoutput(true, r.getStringForDisplaying());
},
[](const std::exception &err) { testoutput(false, err.what()); });
});
// 3
redisClient->execCommandAsync(
[](const drogon::nosql::RedisResult &r) {
testoutput(true, r.getStringForDisplaying());
},
[](const std::exception &err) { testoutput(false, err.what()); },
"set %s %s",
"id_123",
"drogon");
// 4
redisClient->execCommandAsync(
[](const drogon::nosql::RedisResult &r) {
testoutput(r.type() == RedisResultType::kArray &&
r.asArray().size() == 1,
r.getStringForDisplaying());
},
[](const std::exception &err) { testoutput(false, err.what()); },
"keys *");
// 5
redisClient->execCommandAsync(
[](const drogon::nosql::RedisResult &r) {
testoutput(r.asString() == "hello", r.getStringForDisplaying());
},
[](const RedisException &err) { testoutput(false, err.what()); },
"echo %s",
"hello");
// 6
redisClient->execCommandAsync(
[](const drogon::nosql::RedisResult &r) {
testoutput(true, r.getStringForDisplaying());
},
[](const RedisException &err) { testoutput(false, err.what()); },
"flushall");
// 7
redisClient->execCommandAsync(
[](const drogon::nosql::RedisResult &r) {
testoutput(r.type() == RedisResultType::kNil,
r.getStringForDisplaying());
},
[](const RedisException &err) { testoutput(false, err.what()); },
"get %s",
"xxxxx");
std::cout << "start\n";
#ifdef __cpp_impl_coroutine
auto coro_test = [redisClient]() -> drogon::Task<> {
// 8
try
{
auto r = co_await redisClient->execCommandCoro("get %s", "haha");
testoutput(r.type() == RedisResultType::kNil,
r.getStringForDisplaying());
}
catch (const RedisException &err)
{
testoutput(false, err.what());
}
};
drogon::sync_wait(coro_test());
#endif
globalf.get();
}
int main()
{
drogon::app().setLogLevel(trantor::Logger::kWarn);
auto redisClient = drogon::nosql::RedisClient::newRedisClient(
trantor::InetAddress("127.0.0.1", 6379), 1);
doTest(redisClient);
std::cout << "Test passed\n";
return 0;
}

View File

@ -49,7 +49,7 @@ using namespace drogon::orm;
DbClientImpl::DbClientImpl(const std::string &connInfo,
const size_t connNum,
ClientType type)
: connectionsNumber_(connNum),
: numberOfConnections_(connNum),
loops_(type == ClientType::Sqlite3
? 1
: (connNum < std::thread::hardware_concurrency()
@ -66,7 +66,7 @@ DbClientImpl::DbClientImpl(const std::string &connInfo,
if (type == ClientType::PostgreSQL)
{
std::thread([this]() {
for (size_t i = 0; i < connectionsNumber_; ++i)
for (size_t i = 0; i < numberOfConnections_; ++i)
{
auto loop = loops_.getNextLoop();
loop->runInLoop([this, loop]() {
@ -79,7 +79,7 @@ DbClientImpl::DbClientImpl(const std::string &connInfo,
else if (type == ClientType::Mysql)
{
std::thread([this]() {
for (size_t i = 0; i < connectionsNumber_; ++i)
for (size_t i = 0; i < numberOfConnections_; ++i)
{
auto loop = loops_.getNextLoop();
loop->runAfter(0.1 * (i + 1), [this, loop]() {
@ -96,7 +96,7 @@ DbClientImpl::DbClientImpl(const std::string &connInfo,
auto loop = loops_.getNextLoop();
loop->runInLoop([this]() {
std::lock_guard<std::mutex> lock(connectionsMutex_);
for (size_t i = 0; i < connectionsNumber_; ++i)
for (size_t i = 0; i < numberOfConnections_; ++i)
{
connections_.insert(newConnection(nullptr));
}

View File

@ -53,7 +53,7 @@ class DbClientImpl : public DbClient,
virtual bool hasAvailableConnections() const noexcept override;
private:
size_t connectionsNumber_;
size_t numberOfConnections_;
trantor::EventLoopThreadPool loops_;
std::shared_ptr<SharedMutex> sharedMutexPtr_;

View File

@ -48,14 +48,14 @@ DbClientLockFree::DbClientLockFree(const std::string &connInfo,
size_t connectionNumberPerLoop)
: connectionInfo_(connInfo),
loop_(loop),
connectionsNumber_(connectionNumberPerLoop)
numberOfConnections_(connectionNumberPerLoop)
{
type_ = type;
LOG_TRACE << "type=" << (int)type;
if (type == ClientType::PostgreSQL || type == ClientType::Mysql)
{
loop_->queueInLoop([this]() {
for (size_t i = 0; i < connectionsNumber_; ++i)
for (size_t i = 0; i < numberOfConnections_; ++i)
connectionHolders_.push_back(newConnection());
});
}

View File

@ -57,7 +57,7 @@ class DbClientLockFree : public DbClient,
std::string connectionInfo_;
trantor::EventLoop *loop_;
DbConnectionPtr newConnection();
const size_t connectionsNumber_;
const size_t numberOfConnections_;
std::vector<DbConnectionPtr> connections_;
std::vector<DbConnectionPtr> connectionHolders_;
std::unordered_set<DbConnectionPtr> transSet_;

24
test.sh
View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
drogon_ctl_exec=`pwd`/build/drogon_ctl/drogon_ctl
drogon_ctl_exec=$(pwd)/build/drogon_ctl/drogon_ctl
echo ${drogon_ctl_exec}
cd build/examples/
@ -11,14 +11,14 @@ parallel=1
# simulate ninja's parallelism
case $(nproc) in
1)
parallel=$(( $(nproc) + 1 ))
1)
parallel=$(($(nproc) + 1))
;;
2)
parallel=$(( $(nproc) + 1 ))
2)
parallel=$(($(nproc) + 1))
;;
*)
parallel=$(( $(nproc) + 2 ))
*)
parallel=$(($(nproc) + 2))
;;
esac
@ -130,7 +130,7 @@ fi
cd ../views
echo "Hello, world!" >> hello.csp
echo "Hello, world!" >>hello.csp
cd ../build
cmake .. $cmake_gen
@ -170,6 +170,14 @@ if [ "$1" = "-t" ]; then
echo "Error in testing"
exit -1
fi
if [ -f "./nosql_lib/redis/tests/redis_test" ]; then
echo "Test redis"
./nosql_lib/redis/tests/redis_test
if [ $? -ne 0 ]; then
echo "Error in testing"
exit -1
fi
fi
fi
echo "Everything is ok!"