├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── benchmarks ├── README.md ├── _runner.py ├── run_benchmarks.sh └── targets │ ├── actix-web │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.sh │ ├── run.sh │ ├── src │ │ └── main.rs │ └── version.sh │ ├── aspnetcore │ ├── .gitignore │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.json │ ├── aspnetcore.csproj │ ├── build.sh │ ├── run.sh │ └── version.sh │ ├── cpv-framework │ ├── CMakeLists.txt │ ├── Main.cpp │ ├── build.sh │ ├── run.sh │ └── version.sh │ ├── hyper │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.sh │ ├── run.sh │ ├── src │ │ └── main.rs │ └── version.sh │ └── seastar-httpd │ ├── build.sh │ ├── httpd.cpp │ ├── run.sh │ └── version.sh ├── build.sh ├── clean.sh ├── debian ├── changelog ├── control ├── copyright ├── rules ├── scripts │ ├── build_cpvframework.sh │ ├── get_version.sh │ └── install_cpvframework.sh └── source │ └── format ├── docs ├── ApplicationAndModules.md ├── DependencyInjectionContainer.md ├── HttpServerRequestHandlers.md ├── ReleaseNotes.md ├── Roadmap.md ├── Serialization.md └── img │ ├── example-app-design.png │ └── example-app-design.xml ├── examples └── HelloWorld │ ├── Main.cpp │ └── run.sh ├── include └── CPVFramework │ ├── Allocators │ ├── DebugAllocator.hpp │ └── StackAllocator.hpp │ ├── Application │ ├── Application.hpp │ ├── ApplicationState.hpp │ ├── ModuleBase.hpp │ └── Modules │ │ ├── HttpServerModule.hpp │ │ ├── HttpServerRoutingModule.hpp │ │ └── LoggingModule.hpp │ ├── Container │ ├── Container.hpp │ ├── ServiceDescriptor.hpp │ ├── ServiceDescriptorBase.hpp │ ├── ServiceFactory.hpp │ ├── ServiceFactoryBase.hpp │ ├── ServiceLifetime.hpp │ ├── ServicePatcher.hpp │ ├── ServiceStorage.hpp │ └── ServiceTraits.hpp │ ├── Exceptions │ ├── ContainerException.hpp │ ├── DeserializeException.hpp │ ├── Exception.hpp │ ├── FileSystemException.hpp │ ├── FormatException.hpp │ ├── LengthException.hpp │ ├── LogicException.hpp │ ├── NetworkException.hpp │ ├── NotImplementedException.hpp │ ├── OutOfRangeException.hpp │ ├── OverflowException.hpp │ └── UUIDConflictException.hpp │ ├── Http │ ├── HttpConstantStrings.hpp │ ├── HttpForm.hpp │ ├── HttpRequest.hpp │ ├── HttpRequestCookies.hpp │ ├── HttpRequestExtensions.hpp │ ├── HttpRequestHeaders.hpp │ ├── HttpRequestUri.hpp │ ├── HttpResponse.hpp │ ├── HttpResponseExtensions.hpp │ └── HttpResponseHeaders.hpp │ ├── HttpServer │ ├── Handlers │ │ ├── HttpServerRequest404Handler.hpp │ │ ├── HttpServerRequest500Handler.hpp │ │ ├── HttpServerRequestFunctionHandler.hpp │ │ ├── HttpServerRequestHandlerBase.hpp │ │ ├── HttpServerRequestParametersFunctionHandler.hpp │ │ ├── HttpServerRequestRoutingHandler.hpp │ │ └── HttpServerRequestStaticFileHandler.hpp │ ├── HttpContext.hpp │ ├── HttpContextExtensions.hpp │ ├── HttpServer.hpp │ └── HttpServerConfiguration.hpp │ ├── Logging │ └── Logger.hpp │ ├── Serialize │ ├── FormDeserializer.hpp │ ├── FormSerializer.hpp │ ├── JsonDeserializer.hpp │ ├── JsonDeserializer.sajson.hpp │ └── JsonSerializer.hpp │ ├── Stream │ ├── InputStreamBase.hpp │ ├── InputStreamExtensions.hpp │ ├── OutputStreamBase.hpp │ ├── OutputStreamExtensions.hpp │ ├── PacketInputStream.hpp │ ├── PacketOutputStream.hpp │ ├── StringInputStream.hpp │ └── StringOutputStream.hpp │ ├── Testing │ └── GTestUtils.hpp │ └── Utility │ ├── CodeInfo.hpp │ ├── ConstantStrings.hpp │ ├── DateUtils.hpp │ ├── EnumUtils.hpp │ ├── FileUtils.hpp │ ├── HashUtils.hpp │ ├── HttpUtils.hpp │ ├── LRUCache.hpp │ ├── Macros.hpp │ ├── NetworkUtils.hpp │ ├── ObjectTrait.hpp │ ├── Packet.hpp │ ├── Reusable.hpp │ ├── SharedString.hpp │ ├── SharedStringBuilder.hpp │ ├── SocketHolder.hpp │ ├── StringUtils.hpp │ ├── UUIDUtils.hpp │ └── Uri.hpp ├── src ├── Application │ ├── Application.cpp │ ├── ApplicationState.cpp │ └── Modules │ │ ├── HttpServerModule.cpp │ │ ├── HttpServerRoutingModule.cpp │ │ └── LoggingModule.cpp ├── CMakeLists.txt ├── Container │ ├── Container.cpp │ └── ServiceStorage.cpp ├── Exceptions │ └── Exception.cpp ├── Http │ ├── HttpForm.cpp │ ├── HttpRequest.cpp │ ├── HttpRequestCookies.cpp │ ├── HttpRequestExtensions.cpp │ ├── HttpRequestHeaders.cpp │ ├── HttpResponse.cpp │ ├── HttpResponseExtensions.cpp │ └── HttpResponseHeaders.cpp ├── HttpServer │ ├── Connections │ │ ├── Http11Parser.cpp │ │ ├── Http11Parser.hpp │ │ ├── Http11ServerConnection.cpp │ │ ├── Http11ServerConnection.hpp │ │ ├── Http11ServerConnectionRequestStream.cpp │ │ ├── Http11ServerConnectionRequestStream.hpp │ │ ├── Http11ServerConnectionResponseStream.cpp │ │ ├── Http11ServerConnectionResponseStream.hpp │ │ └── HttpServerConnectionBase.hpp │ ├── Handlers │ │ ├── HttpServerRequest404Handler.cpp │ │ ├── HttpServerRequest500Handler.cpp │ │ ├── HttpServerRequestRealLastHandler.cpp │ │ ├── HttpServerRequestRealLastHandler.hpp │ │ ├── HttpServerRequestRoutingHandler.cpp │ │ └── HttpServerRequestStaticFileHandler.cpp │ ├── HttpServer.cpp │ ├── HttpServerConfiguration.cpp │ ├── HttpServerSharedData.cpp │ └── HttpServerSharedData.hpp ├── Logging │ ├── ConsoleLogger.cpp │ ├── ConsoleLogger.hpp │ ├── Logger.cpp │ ├── NoopLogger.cpp │ └── NoopLogger.hpp ├── Serialize │ ├── JsonDeserializer.sajson.cpp │ ├── JsonSerializer.JsonEncodeMapping.hpp │ └── JsonSerializer.cpp ├── Stream │ ├── InputStreamExtensions.cpp │ ├── PacketInputStream.cpp │ ├── PacketOutputStream.cpp │ ├── StringInputStream.cpp │ └── StringOutputStream.cpp ├── Testing │ └── GTestUtils.cpp └── Utility │ ├── ConstantStrings.cpp │ ├── DateUtils.cpp │ ├── FileUtils.cpp │ ├── HttpUtils.HtmlEncodeMapping.hpp │ ├── HttpUtils.HtmlEntitiesMapping.hpp │ ├── HttpUtils.MimeMapping.hpp │ ├── HttpUtils.UrlEncodeMapping.hpp │ ├── HttpUtils.cpp │ ├── NetworkUtils.cpp │ ├── Packet.cpp │ ├── SharedString.cpp │ ├── SharedStringBuilder.cpp │ ├── StringUtils.cpp │ ├── UUIDUtils.cpp │ └── Uri.cpp ├── tests ├── CMakeLists.txt ├── Cases │ ├── Allocators │ │ ├── TestDebugAllocator.cpp │ │ ├── TestStackAllocator.Map.cpp │ │ ├── TestStackAllocator.Vector.cpp │ │ └── TestStackAllocator.cpp │ ├── Application │ │ ├── Modules │ │ │ ├── TestHttpServerModule.cpp │ │ │ ├── TestHttpServerRoutingModule.cpp │ │ │ └── TestLoggingModule.cpp │ │ └── TestApplication.cpp │ ├── Container │ │ └── TestContainer.cpp │ ├── Exceptions │ │ └── TestException.cpp │ ├── Http │ │ ├── TestHttpForm.cpp │ │ ├── TestHttpRequest.cpp │ │ ├── TestHttpRequestExtensions.cpp │ │ ├── TestHttpResponse.cpp │ │ └── TestHttpResponseExtensions.cpp │ ├── HttpServer │ │ ├── Handlers │ │ │ ├── TestHttpServerRequest404Handler.cpp │ │ │ ├── TestHttpServerRequest500Handler.cpp │ │ │ ├── TestHttpServerRequestRoutingHandler.cpp │ │ │ └── TestHttpServerRequestStaticFileHandler.cpp │ │ ├── TestHttpContext.cpp │ │ ├── TestHttpContextExtensions.cpp │ │ ├── TestHttpServer.Base.cpp │ │ ├── TestHttpServer.Base.hpp │ │ ├── TestHttpServer.Http11.cpp │ │ └── TestHttpServerConfiguration.cpp │ ├── Logging │ │ └── TestLogger.cpp │ ├── Serialize │ │ ├── TestFormDeserializer.cpp │ │ ├── TestFormSerializer.cpp │ │ ├── TestJsonDeserializer.cpp │ │ └── TestJsonSerializer.cpp │ ├── Stream │ │ ├── TestInputStreamExtensions.cpp │ │ ├── TestOutputStreamExtensions.cpp │ │ ├── TestPacketInputStream.cpp │ │ ├── TestPacketOutputStream.cpp │ │ ├── TestStringInputStream.cpp │ │ └── TestStringOutputStream.cpp │ └── Utility │ │ ├── TestCodeInfo.cpp │ │ ├── TestDateUtils.cpp │ │ ├── TestEnumUtils.cpp │ │ ├── TestFileUtils.cpp │ │ ├── TestHashUtils.cpp │ │ ├── TestHttpUtils.cpp │ │ ├── TestLRUCache.cpp │ │ ├── TestNetworkUtils.cpp │ │ ├── TestObjectTrait.cpp │ │ ├── TestPacket.cpp │ │ ├── TestReusable.cpp │ │ ├── TestSharedString.cpp │ │ ├── TestSharedStringBuilder.cpp │ │ ├── TestSocketHolder.cpp │ │ ├── TestStringUtils.cpp │ │ ├── TestUUIDUtils.cpp │ │ └── TestUri.cpp ├── Main.cpp ├── run_tests.sh ├── tool_cppcheck.sh └── travis_run_tests.sh ├── tmp ├── CMakeLists.txt ├── Main.cpp ├── run_tmp_debug.sh └── run_tmp_release.sh └── tools └── make-gzip.sh /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.swp 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | script: cd tests && sh travis_run_tests.sh 3 | os: linux 4 | dist: trusty 5 | sudo: required 6 | addons: 7 | apt: 8 | packages: 9 | - realpath 10 | services: 11 | - docker 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright <2018-2020> <303248153@github> 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | This folder manages source code of benchmark targets, and the tool used to generate benchmark results. 4 | 5 | ## Prepare environment 6 | 7 | First, ensure you created development environment from `cpv-ops/local`, then execute `devenv-root-enter.sh` to enter devenv container with root, and run following commands: 8 | 9 | ``` sh 10 | curl https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -o packages-microsoft-prod.deb 11 | dpkg -i packages-microsoft-prod.deb 12 | rm packages-microsoft-prod.deb 13 | add-apt-repository universe 14 | apt-get update 15 | apt-get install -y apt-transport-https python3-psutil 16 | apt-get update 17 | apt-get install -y dotnet-sdk-3.1 18 | 19 | su ubuntu 20 | curl -sf -L https://static.rust-lang.org/rustup.sh | sh 21 | exit 22 | 23 | cd /tmp 24 | git clone https://github.com/giltene/wrk2 25 | cd wrk2 26 | make 27 | mv wrk /usr/local/bin/wrk2 28 | cd /tmp 29 | git clone https://github.com/wg/wrk 30 | cd wrk 31 | make 32 | mv wrk /usr/local/bin/wrk 33 | ``` 34 | 35 | ## Generate benchmark results 36 | 37 | Use `devent-enter.sh` from `cpv-ops/local/scripts` to enter devenv container, and run following command: 38 | 39 | ``` sh 40 | sh run_benchmarks.sh 41 | ``` 42 | 43 | It will perform benchmarks for all targets, and output results in markdown format. 44 | -------------------------------------------------------------------------------- /benchmarks/run_benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ulimit -n 65536 3 | ulimit -a 4 | echo 5 | python3 _runner.py 6 | echo "done" 7 | 8 | -------------------------------------------------------------------------------- /benchmarks/targets/actix-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix-web-benchmark" 3 | version = "1.0.0" 4 | authors = ["303248153 <303248153@qq.com>"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | actix-rt = "1.0.0" 9 | actix-web = "2.0.0" 10 | actix-files = "0.2.1" 11 | actix-session = "0.3.0" 12 | actix-utils = "1.0.3" 13 | futures = "0.3.1" 14 | env_logger = "0.5" 15 | bytes = "0.5" 16 | -------------------------------------------------------------------------------- /benchmarks/targets/actix-web/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/actix-web-benchmark 5 | mkdir -p ${BUILDDIR} 6 | 7 | . $HOME/.cargo/env 8 | CARGO_TARGET_DIR=../../../build/actix-web-benchmark cargo build --release 9 | -------------------------------------------------------------------------------- /benchmarks/targets/actix-web/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/actix-web-benchmark 5 | cd ${BUILDDIR} 6 | cd release 7 | 8 | ./actix-web-benchmark 9 | -------------------------------------------------------------------------------- /benchmarks/targets/actix-web/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate actix_web; 3 | use std::{env, io}; 4 | use actix_web::{ App, HttpResponse, HttpServer }; 5 | 6 | #[get("/")] 7 | async fn hello() -> HttpResponse { 8 | HttpResponse::Ok() 9 | .content_type("text/plain") 10 | .body("Hello World!") 11 | } 12 | 13 | #[actix_rt::main] 14 | async fn main() -> io::Result<()> { 15 | env::set_var("RUST_LOG", "actix_web=debug,actix_server=info"); 16 | env_logger::init(); 17 | 18 | HttpServer::new(|| { 19 | App::new() 20 | .service(hello) 21 | }) 22 | .bind("127.0.0.1:8000")? 23 | .run() 24 | .await 25 | } 26 | -------------------------------------------------------------------------------- /benchmarks/targets/actix-web/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cat Cargo.lock | grep "actix-web " | head -n 1 | awk '{ print $2 }' 4 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace aspnetcore 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:12969", 7 | "sslPort": 44311 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Production" 16 | } 17 | }, 18 | "aspnetcore": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:8001;http://localhost:8000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Production" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | 11 | namespace aspnetcore 12 | { 13 | public class Startup 14 | { 15 | // This method gets called by the runtime. Use this method to add services to the container. 16 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 17 | public void ConfigureServices(IServiceCollection services) 18 | { 19 | } 20 | 21 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 22 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 23 | { 24 | if (env.IsDevelopment()) 25 | { 26 | app.UseDeveloperExceptionPage(); 27 | } 28 | 29 | app.UseRouting(); 30 | 31 | app.UseEndpoints(endpoints => 32 | { 33 | endpoints.MapGet("/", async context => 34 | { 35 | await context.Response.WriteAsync("Hello World!"); 36 | }); 37 | }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/aspnetcore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | dotnet build -c Release 4 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | dotnet run -c Release 4 | -------------------------------------------------------------------------------- /benchmarks/targets/aspnetcore/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | dotnet --info | grep AspNetCore | awk '{print $2}' | sort -r | head -n 1 4 | -------------------------------------------------------------------------------- /benchmarks/targets/cpv-framework/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | project (CPVFrameworkBenchmark) 3 | 4 | include(FindPkgConfig) 5 | 6 | # add subdirectory 7 | add_subdirectory(../../../src CPVFramework) 8 | 9 | # add target and source files 10 | FILE(GLOB_RECURSE Files ./*.cpp) 11 | FILE(GLOB_RECURSE PublicHeaders ../../../include/*.hpp) 12 | FILE(GLOB_RECURSE InternalHeaders ../../../src/*.hpp) 13 | add_executable(${PROJECT_NAME} ${Files} ${PublicHeaders} ${InternalHeaders}) 14 | 15 | # find dependencies 16 | find_package(PkgConfig REQUIRED) 17 | pkg_check_modules(SEASTAR REQUIRED seastar) 18 | pkg_check_modules(SEASTAR_DEBUG REQUIRED seastar-debug) 19 | 20 | # set compile options 21 | set(CMAKE_VERBOSE_MAKEFILE TRUE) 22 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) 23 | target_include_directories(${PROJECT_NAME} PRIVATE 24 | ../../../include ../../../src ./) 25 | target_compile_options(${PROJECT_NAME} PRIVATE 26 | -Wall -Wextra 27 | -Wno-unused-variable -Wno-unused-function) 28 | 29 | # set compile options dependent on build type 30 | if (CMAKE_BUILD_TYPE MATCHES Release OR 31 | CMAKE_BUILD_TYPE MATCHES RelWithDebInfo OR 32 | CMAKE_BUILD_TYPE MATCHES MinSizeRel) 33 | target_compile_options(${PROJECT_NAME} PRIVATE 34 | ${SEASTAR_CFLAGS}) 35 | target_link_libraries(${PROJECT_NAME} PRIVATE 36 | ${SEASTAR_LDFLAGS} CPVFramework) 37 | elseif (CMAKE_BUILD_TYPE MATCHES Debug) 38 | target_compile_options(${PROJECT_NAME} PRIVATE 39 | ${SEASTAR_DEBUG_CFLAGS}) 40 | target_link_libraries(${PROJECT_NAME} PRIVATE 41 | asan ubsan ${SEASTAR_DEBUG_LDFLAGS} CPVFramework) 42 | endif() 43 | 44 | -------------------------------------------------------------------------------- /benchmarks/targets/cpv-framework/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char** argv) { 9 | seastar::app_template app; 10 | app.run(argc, argv, [] { 11 | cpv::Application application; 12 | application.add(); 13 | application.add([] (auto& module) { 14 | module.getConfig().setListenAddresses({ "0.0.0.0:8000", "127.0.0.1:8001" }); 15 | }); 16 | application.add([] (auto& module) { 17 | module.route(cpv::constants::GET, "/", [] (cpv::HttpContext& context) { 18 | return cpv::extensions::reply(context.getResponse(), "Hello World!"); 19 | }); 20 | }); 21 | return application.runForever(); 22 | }); 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /benchmarks/targets/cpv-framework/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/cpvframework-benchmark 5 | 6 | mkdir -p ${BUILDDIR} 7 | cd ${BUILDDIR} 8 | cmake -DCMAKE_BUILD_TYPE=Release \ 9 | -DCMAKE_C_COMPILER=gcc-9 \ 10 | -DCMAKE_CXX_COMPILER=g++-9 \ 11 | ../../benchmarks/targets/cpv-framework 12 | make V=1 --jobs=$(printf "%d\n4" $(nproc) | sort -n | head -1) 13 | 14 | -------------------------------------------------------------------------------- /benchmarks/targets/cpv-framework/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/cpvframework-benchmark 5 | 6 | cd ${BUILDDIR} 7 | 8 | ./CPVFrameworkBenchmark \ 9 | --task-quota-ms=20 \ 10 | --smp=$(nproc) \ 11 | --reactor-backend=epoll 12 | 13 | -------------------------------------------------------------------------------- /benchmarks/targets/cpv-framework/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | sh ../../../debian/scripts/get_version.sh 4 | -------------------------------------------------------------------------------- /benchmarks/targets/hyper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyper-benchmark" 3 | version = "1.0.0" 4 | authors = ["303248153 <303248153@qq.com>"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hyper = "0.13.1" 9 | tokio = { version = "0.2", features = ["macros", "sync", "rt-threaded"] } 10 | log = "0.4" 11 | -------------------------------------------------------------------------------- /benchmarks/targets/hyper/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/hyper-benchmark 5 | mkdir -p ${BUILDDIR} 6 | 7 | . $HOME/.cargo/env 8 | CARGO_TARGET_DIR=../../../build/hyper-benchmark cargo build --release 9 | -------------------------------------------------------------------------------- /benchmarks/targets/hyper/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/hyper-benchmark 5 | cd ${BUILDDIR} 6 | cd release 7 | 8 | ./hyper-benchmark 9 | -------------------------------------------------------------------------------- /benchmarks/targets/hyper/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use hyper::service::{make_service_fn, service_fn}; 3 | use hyper::{Body, Request, Response, Server, Method, StatusCode, header}; 4 | 5 | async fn hello(req: Request) -> Result, Infallible> { 6 | match (req.method(), req.uri().path()) { 7 | (&Method::GET, "/") => { 8 | Ok(Response::builder() 9 | .status(StatusCode::OK) 10 | .header(header::CONTENT_TYPE, "text/plain") 11 | .body(Body::from("Hello World!")) 12 | .unwrap()) 13 | }, 14 | _ => { 15 | Ok(Response::builder() 16 | .status(StatusCode::NOT_FOUND) 17 | .header(header::CONTENT_TYPE, "text/plain") 18 | .body(Body::from("Not Found")) 19 | .unwrap()) 20 | } 21 | } 22 | } 23 | 24 | #[tokio::main] 25 | pub async fn main() -> Result<(), Box> { 26 | let make_svc = make_service_fn(|_conn| { 27 | async { Ok::<_, Infallible>(service_fn(hello)) } 28 | }); 29 | let addr = ([127, 0, 0, 1], 8000).into(); 30 | let server = Server::bind(&addr).serve(make_svc); 31 | println!("Listening on http://{}", addr); 32 | server.await?; 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /benchmarks/targets/hyper/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | cat Cargo.lock | grep "hyper " | head -n 1 | awk '{ print $2 }' 4 | -------------------------------------------------------------------------------- /benchmarks/targets/seastar-httpd/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/seastar-httpd-benchmark 5 | 6 | mkdir -p ${BUILDDIR} 7 | cd ${BUILDDIR} 8 | 9 | g++-9 $(pkg-config --cflags seastar) ../../benchmarks/targets/seastar-httpd/httpd.cpp $(pkg-config --libs seastar) -O3 -o httpd 10 | -------------------------------------------------------------------------------- /benchmarks/targets/seastar-httpd/httpd.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace bpo = boost::program_options; 9 | 10 | using namespace seastar; 11 | using namespace httpd; 12 | 13 | class handl : public httpd::handler_base { 14 | public: 15 | virtual future > handle(const sstring& path, 16 | std::unique_ptr req, std::unique_ptr rep) { 17 | rep->_content = "hello"; 18 | rep->done("html"); 19 | return make_ready_future>(std::move(rep)); 20 | } 21 | }; 22 | 23 | void set_routes(routes& r) { 24 | function_handler* h1 = new function_handler([](const_req req) { 25 | return "Hello World!"; 26 | }); 27 | r.add(operation_type::GET, url("/"), h1); 28 | r.add(operation_type::GET, url("/file").remainder("path"), 29 | new directory_handler("/")); 30 | } 31 | 32 | int main(int ac, char** av) { 33 | app_template app; 34 | app.add_options()("port", bpo::value()->default_value(8000), 35 | "HTTP Server port"); 36 | return app.run_deprecated(ac, av, [&] { 37 | auto&& config = app.configuration(); 38 | uint16_t port = config["port"].as(); 39 | auto server = new http_server_control(); 40 | auto rb = make_shared("apps/httpd/"); 41 | return server->start().then([server] { 42 | return server->set_routes(set_routes); 43 | }).then([server, rb]{ 44 | return server->set_routes([rb](routes& r){rb->set_api_doc(r);}); 45 | }).then([server, rb]{ 46 | return server->set_routes([rb](routes& r) {rb->register_function(r, "demo", "hello world application");}); 47 | }).then([server, rb] { 48 | prometheus::config ctx; 49 | return prometheus::start(*server, ctx); 50 | }).then([server, port] { 51 | seastar::listen_options lo; 52 | lo.reuse_address = true; 53 | lo.listen_backlog = 65535; 54 | return server->listen(port, lo); 55 | }).then([server, port] { 56 | std::cout << "Seastar HTTP server listening on port " << port << " ...\n"; 57 | engine().at_exit([server] { 58 | return server->stop(); 59 | }); 60 | }); 61 | 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /benchmarks/targets/seastar-httpd/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | BUILDDIR=../../../build/seastar-httpd-benchmark 5 | 6 | cd ${BUILDDIR} 7 | 8 | ./httpd \ 9 | --task-quota-ms=20 \ 10 | --smp=$(nproc) \ 11 | --reactor-backend=epoll 12 | 13 | -------------------------------------------------------------------------------- /benchmarks/targets/seastar-httpd/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | dpkg-query --showformat='${Version}\n' --show seastar 4 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | VERSION=$(sh ./debian/scripts/get_version.sh) 5 | BUILDDIR=build/cpvframework-${VERSION} 6 | TYPE=$1 7 | 8 | mkdir -p ${BUILDDIR} 9 | cd ${BUILDDIR} 10 | cp -rf ../../debian . 11 | cp -rf ../../src . 12 | cp -rf ../../include . 13 | cp -f ../../LICENSE . 14 | 15 | if [ "${TYPE}" = "local" ]; then 16 | debuild 17 | elif [ "${TYPE}" = "ppa" ]; then 18 | debuild -S -sa 19 | else 20 | echo "build.sh {local,ppa}" 21 | exit 1 22 | fi 23 | 24 | 25 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | rm -rfv build 5 | 6 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | cpvframework (0.2.0.0) bionic; urgency=low 2 | 3 | * version 0.2 4 | 5 | -- compiv Tue, 01 Jan 2020 20:16:00 +0000 6 | 7 | cpvframework (0.1.0.3) bionic; urgency=low 8 | 9 | * Initial release 10 | 11 | -- compiv Mon, 21 Oct 2019 19:01:00 +0000 12 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: cpvframework 2 | Section: misc 3 | Priority: optional 4 | Maintainer: compiv 5 | Build-Depends: 6 | cmake, g++-9, make, python3, patchelf, seastar 7 | Homepage: https://github.com/cpv-project/cpv-framework 8 | 9 | Package: cpvframework 10 | Architecture: amd64 11 | Depends: 12 | seastar 13 | Description: C++ web framework based on seastar framework 14 | 15 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: * 4 | Copyright: 2018-2019 303248153@github 5 | License: MIT License 6 | 7 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | SRCDIR=$(shell realpath .)/src 4 | BUILDDIR=$(shell realpath .)/build/release 5 | BUILDDIR_DEBUG=$(shell realpath .)/build/debug 6 | ROOTDIR=$(shell realpath .)/debian/tmp 7 | ROOTDIR_DEBUG=$(shell realpath .)/debian/tmp_debug 8 | 9 | build-indep: 10 | build-arch: 11 | cd $(SRCDIR); \ 12 | SRCDIR=$(SRCDIR) \ 13 | BUILDDIR=$(BUILDDIR) BUILDDIR_DEBUG=$(BUILDDIR_DEBUG) \ 14 | ROOTDIR=$(ROOTDIR) ROOTDIR_DEBUG=$(ROOTDIR_DEBUG) \ 15 | sh ../debian/scripts/build_cpvframework.sh 16 | build: build-indep build-arch 17 | 18 | binary-indep: 19 | binary-arch: 20 | mkdir -p $(ROOTDIR)/DEBIAN 21 | mkdir -p $(ROOTDIR_DEBUG) 22 | cd $(ROOTDIR); \ 23 | SRCDIR=$(SRCDIR) \ 24 | BUILDDIR=$(BUILDDIR) BUILDDIR_DEBUG=$(BUILDDIR_DEBUG) \ 25 | ROOTDIR=$(ROOTDIR) ROOTDIR_DEBUG=$(ROOTDIR_DEBUG) \ 26 | sh ../scripts/install_cpvframework.sh 27 | dpkg-gencontrol -pcpvframework 28 | dpkg --build $(ROOTDIR) .. 29 | binary: binary-indep binary-arch 30 | 31 | clean: 32 | rm -rf $(ROOTDIR) 33 | 34 | .PHONY: build-indep build-arch build binary-indep binary-arch binary clean 35 | 36 | -------------------------------------------------------------------------------- /debian/scripts/build_cpvframework.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | mkdir -p ${BUILDDIR} 5 | cd ${BUILDDIR} 6 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 7 | -DCMAKE_C_COMPILER=gcc-9 \ 8 | -DCMAKE_CXX_COMPILER=g++-9 \ 9 | -DCMAKE_INSTALL_PREFIX=/usr \ 10 | ${SRCDIR} 11 | make V=1 12 | 13 | mkdir -p ${BUILDDIR_DEBUG} 14 | cd ${BUILDDIR_DEBUG} 15 | cmake -DCMAKE_BUILD_TYPE=Debug \ 16 | -DCMAKE_C_COMPILER=gcc-9 \ 17 | -DCMAKE_CXX_COMPILER=g++-9 \ 18 | -DCMAKE_INSTALL_PREFIX=/usr \ 19 | ${SRCDIR} 20 | make V=1 21 | 22 | -------------------------------------------------------------------------------- /debian/scripts/get_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cat $(dirname $0)/../changelog | head -n 1 | python3 -c "import re;print(re.search('\((.+)\)', input()).groups()[0])" 3 | -------------------------------------------------------------------------------- /debian/scripts/install_cpvframework.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cd ${BUILDDIR} 5 | DESTDIR=${ROOTDIR} make install V=1 6 | 7 | cd ${BUILDDIR_DEBUG} 8 | DESTDIR=${ROOTDIR_DEBUG} make install V=1 9 | 10 | cd ${ROOTDIR} 11 | cp -f ${ROOTDIR_DEBUG}/usr/lib/x86_64-linux-gnu/libCPVFramework.so \ 12 | ${ROOTDIR}/usr/lib/x86_64-linux-gnu/libCPVFramework_debug.so 13 | cp -f ${ROOTDIR_DEBUG}/usr/lib/x86_64-linux-gnu/pkgconfig/cpvframework.pc \ 14 | ${ROOTDIR}/usr/lib/x86_64-linux-gnu/pkgconfig/cpvframework-debug.pc 15 | strip ${ROOTDIR}/usr/lib/x86_64-linux-gnu/*.so 16 | 17 | sed -i "s#-lCPVFramework#-lCPVFramework_debug#g" \ 18 | ${ROOTDIR}/usr/lib/x86_64-linux-gnu/pkgconfig/cpvframework-debug.pc 19 | 20 | patchelf --set-soname libCPVFramework_debug.so ${ROOTDIR}/usr/lib/x86_64-linux-gnu/libCPVFramework_debug.so 21 | 22 | 23 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /docs/ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | # Release notes 2 | 3 | ## 0.2 4 | 5 | - (api change) add `SharedString` and use it instead of `std::string_view`, it make lifetime management much easier 6 | - (api change) remove `getCacheControl` and `setCacheControl` from `HttpRequestHeaders` 7 | - (api change) change paramter type from `const HttpServerRequestHandlerIterator&` to `HttpServerRequestHandlerIterator` in http request handler 8 | - (api change) require types under `cpv::extensions::http_context_parameters` when registering http request handler with parameters 9 | - add json serializer and deserializer 10 | - add form serializer and deserializer 11 | - add static file handler 12 | - support pre-compressed gzip (detect original-filename.gz) 13 | - support bytes range 14 | - support return 304 not modified when If-Modified-Since matched 15 | - add benchmark tools 16 | 17 | ## 0.1 18 | 19 | - Add dependency injection container 20 | - Add http server (supports http 1.0/1.1 and pipeline) 21 | - Add application and module framework 22 | - Provide LoggingModule 23 | - Provide HttpServerModule 24 | - Provide HttpServerRoutingModule 25 | 26 | -------------------------------------------------------------------------------- /docs/Roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## 0.3 4 | 5 | - add http client 6 | - add ssl support 7 | - support certbot 8 | - add dynamic compress handler 9 | - support compress in once and compress in chunk 10 | 11 | ## 0.4 12 | 13 | - add template engine 14 | - add multipart form serializer and deserializer 15 | - add cql driver module (header only) 16 | - add crud example 17 | 18 | ## Backlog 19 | 20 | - upgrade to c++20 for coroutine support 21 | - add prometheus module (big job because seastar's impl only works for seastar httpd) 22 | - add base class of model that provides id and underlying buffer member for convenient 23 | 24 | -------------------------------------------------------------------------------- /docs/img/example-app-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpv-project/cpv-framework/b0da79c8c57ceecb6b13f4d8658ec4d4c0237668/docs/img/example-app-design.png -------------------------------------------------------------------------------- /docs/img/example-app-design.xml: -------------------------------------------------------------------------------- 1 | 7V1bb5tIFP41lroPa3EHPyZO2qzUXVWNtG36RvAET4sZFo9ju79+Z2DGgGdikxgYcBxFERwuhu/cDx/OyJwuNp9SP5n/jWYgGhnabDMyb0aGMTEN8pcKtrnA1rxcEKZwlov0QnAPfwMm1Jh0BWdgWdkRIxRhmFSFAYpjEOCKzE9TtK7u9oSi6qcmfggEwX3gR6L0G5zheS71bK2Q3wEYzvkn6xrbsvD5zkywnPsztC6JzNuROU0RwvnSYjMFEcWO45If9/GFrbsLS0GM6xzwz90Nmn5zH5+ff2wX0zV+Wljf/2RnefajFbthdrF4yxFI0SqeAXoSbWRer+cQg/vED+jWNVE5kc3xIiJrOllkpwMpBpsXr1Pf3T2xGoAWAKdbsgs/wGKAMYvhq+sCfl3joM5L2FsuE/pM5+Hu3AUsZIEh8wqU7IZRWuIU/QJTFKE0O9rUsh+yxY9gGBNZBJ7IXV1TICExxismxihpCGSnCrIngmx5Moz11jDWBUzBjLgiW0UpnqMQxX50W0ivq6gX+3xGFKgM658A4y2LK/4Ko6omwAbi7+xwuvxAl8eGzVZvNqVtN1u+EpMbzo4auzZffyhvLI7L1viB+R3S2zqsNoICWqUBOACXyfSA/TQE+MCOliY3hBREPobP1SuRaTU79CpN/W1phwTBGC9LZ/5CBYV9GXtObBjmnoXkZyzsZXdpbzchjkmHfhoQxYG0GZ807SpmOx8tOaXhSpxyB3bjTmkJgAbJ88i8Ir9XSRKR0IQhigWQCQa4LpJPMIq4PEYxkIG7HwYXcDbL/F+mv6qGu0pIMrW0pRVH0MoUpYBItHPXxPGs5XSpCEuML8py1uGU1WDmMSZdZZ6TdMMvs+Qln1EYwjgkPcqKaGLImUGXhCBdYvlmW5Zvio3DHcbJPUjJjZ4BwJLUKwW4vdBiv8PQYpqDCC38MqXG/xWt8FkEGcuQ+IDdaZDpvu9uFFB3rwfSJPW8KQFU99pCVFfbZBeN9UNp09Em21DVZLs1w5GuNBy5h6IR+G8FltjWtDs/JpV6OigH8uyqA0k6L6PTzku3Lv7zCv/hFdNxBzJUOpDuqNWqW9aqVler1dGj3kOtukqVKg4DhbDIarVzCI2SUUjHoVFtwzI4JzIGUVvwyzzkRZZmnYEHmZJ2p1sPMr0DWA8K2hqNT7cTc1McBn54QCsiCfyY/l0tMVrQIEM8ck7/zrlFaxFc4j8E+I8N1tkAvf8zdcEPbNV+wC9ATSbRy2nkyOirSCSVAntsd5dHrLrFWD5MUZVHLLEau/mLrE9RjH0YDyu+7eYzPL65tuAyMvJEa4MySxxIfgUhiVt5AENhSBeciEasR7oU0iUYL7EfB+KQsh+xLfIfQXTtB7/CTF5S7lP205Au93KVLimku81Vlvh4t6RMggUu5afl+1beniOakgFRt44olnD3wCdulmaxPPgFKEfGDwKQYHRizKsBcbdh0d7Thj1R/QDBFruXkitdXIiGMduq5jLJo2XH6FJnIufoU+Y1M5As6QfGM3qKGGJI0P79vrOX4Uyq2nPcsViLuBL98efLzetPTF9t13Ztk2Wd/pFlXQHTC1n2AFwsLR+fWDtyQ2iXLOuqIMvaYqnStp82WWx4/SPL2uKQifEyxafu58XLrBEgO+Vl8g/rQ3jsjDzl8NejWg9yp+lGZA4OlpcpBCHlvExeqp8JL7NOlO+Wl+mY7zG0WMMILWLreAa8zH0fUM/LdIbNy/T6x8t0FDOQBsYrc+oSM12l5AnnbJmZXu+YmY538aBXeBAPgcc9yFDqQRO1Wh0Yqay2Vh2l1Ex+medJzRRio3Jqpqv4rY+heZExiOqCX+b5UTP3PUg9NdM99MbfoKDtHTXTFZv2CzXzBT9QTs10lZL8B0bNdJ261ZhSaqYrPrAfLjXT6Rs10xVnABdqZk1d9o6a6YrPzC/UzHqOqJya6YpP5y9cwKrO3L5xAT1xUHDhAr6ovf5xAT1x8FAgozc6cq09VvCq9eBkYh2pCH+uFgkvcJlx7L7KlW4PSHcS8COPFsPZub+AFBKEqV01/j5QXarbye8DvYnq5uhexUhtFjs+vrC/bZuH9m+HGucZgtm+psvR94xCYkBdjdMqhqrCyk5ubd5mZZODViPsr2tKzEwcKt2QeiSFj3kdmb8mAmiKY99uDVH8visUu8hpXFOSmYjOm75OmoLJadP1tqPFm5Lkq0bvlSBDVhJ6SU8R2FzR719vNvDwV4qOf0Ocd2LgOc0mxML1X7iE2Ttf2gei/TXtOqbZeDON8qXxeCzOMs/rjbBdR6+MujQ5Mm32Z1m4zWbOZGGRscWWbcyYX3hppXchV/haQBkFU9bJtxdwxZHaRYcHdShQCGU6nDSjQ7Ja/E+LvFQq/jGIefs/ 2 | -------------------------------------------------------------------------------- /examples/HelloWorld/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char** argv) { 9 | seastar::app_template app; 10 | app.run(argc, argv, [] { 11 | cpv::Application application; 12 | application.add(); 13 | application.add([] (auto& module) { 14 | module.getConfig().setListenAddresses({ "0.0.0.0:8000", "127.0.0.1:8001" }); 15 | }); 16 | application.add([] (auto& module) { 17 | module.route(cpv::constants::GET, "/", [] (cpv::HttpContext& context) { 18 | return cpv::extensions::reply(context.getResponse(), "Hello World!"); 19 | }); 20 | }); 21 | return application.runForever(); 22 | }); 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /examples/HelloWorld/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | BIN_PATH="../../build/example-hello-world/Main" 3 | mkdir -p $(dirname "${BIN_PATH}") 4 | g++-9 $(pkg-config --cflags seastar) \ 5 | $(pkg-config --cflags cpvframework) \ 6 | Main.cpp \ 7 | $(pkg-config --libs seastar) \ 8 | $(pkg-config --libs cpvframework) \ 9 | -o ${BIN_PATH} && \ 10 | ${BIN_PATH} --reactor-backend epoll 11 | 12 | 13 | -------------------------------------------------------------------------------- /include/CPVFramework/Application/ApplicationState.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Utility/EnumUtils.hpp" 3 | 4 | namespace cpv { 5 | /** State of an application, used to invoke ModuleBase::handle */ 6 | enum class ApplicationState { 7 | /** The first state of initializaztion, application will only initialize once */ 8 | StartInitialize = 0, 9 | /** Register basic services that may used in custom initialize functions */ 10 | RegisterBasicServices = 100, 11 | /** Before calling custom initialize functions that passed at module registration */ 12 | BeforeCallCustomInitializeFunctions = 101, 13 | /** This is a dummy state, it won't used to invoke to ModuleBase::handle */ 14 | CallingCustomIntializeFunctions = 102, 15 | /** After custom initialize functions that passed at module registration called */ 16 | AfterCustomInitializeFunctionsCalled = 103, 17 | /** Register services that order matters and wants to put first */ 18 | RegisterHeadServices = 104, 19 | /** Register common services, order of services dependents on order of moduels */ 20 | RegisterServices = 105, 21 | /** Register services that order matters and wants to put last */ 22 | RegisterTailServices = 106, 23 | /** Patch services that already registered */ 24 | PatchServices = 107, 25 | /** After all services registered */ 26 | AfterServicesRegistered = 199, 27 | /** The last state of initializaztion */ 28 | AfterInitialized = 999, 29 | 30 | /** The first state when Application::start invoked */ 31 | BeforeStart = 10000, 32 | /** The second state when Application::start invoked */ 33 | Starting = 10001, 34 | /** The last state when Application::start invoked */ 35 | AfterStarted = 10099, 36 | 37 | /** The first state when Application::stopTemporary invoked */ 38 | BeforeTemporaryStop = 10100, 39 | /** The second state when Application::stopTemporary invoked */ 40 | TemporaryStopping = 10101, 41 | /** The last state when Application::stopTemporary invoked */ 42 | AfterTemporaryStopped = 10199, 43 | 44 | /** The first state when Application::stop invoked */ 45 | BeforeStop = 10200, 46 | /** The second state when Application::stop invoked */ 47 | Stopping = 10201, 48 | /** The last state when Application::stop invoked */ 49 | AfterStopped = 10299, 50 | }; 51 | 52 | /** Enum descriptions of ApplicationState */ 53 | template <> 54 | struct EnumDescriptions { 55 | static const std::vector>& get(); 56 | }; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /include/CPVFramework/Application/ModuleBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Container/Container.hpp" 4 | #include "./ApplicationState.hpp" 5 | 6 | namespace cpv { 7 | /** The base class of modules */ 8 | class ModuleBase { 9 | public: 10 | /** Do some work for given application state */ 11 | virtual seastar::future<> handle(Container& container, ApplicationState state) = 0; 12 | 13 | /** Virtual destructor */ 14 | virtual ~ModuleBase() = default; 15 | }; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /include/CPVFramework/Application/Modules/LoggingModule.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Logging/Logger.hpp" 3 | #include "../ModuleBase.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * Module used to provide logger 8 | * 9 | * By default, it uses console logger with Notice level, 10 | * you can set custom logger by using custom initialize function: 11 | * ``` 12 | * application.add(auto& module) { 13 | * module.setLogger(Logger::createConsole(LogLevel::Info)); 14 | * }); 15 | * ``` 16 | * and get logger instance from the container: 17 | * ``` 18 | * auto logger = container.get>(); 19 | * ``` 20 | * 21 | * The lifetime of logger instance is persistent. 22 | */ 23 | class LoggingModule : public ModuleBase { 24 | public: 25 | /** Set custom logger */ 26 | void setLogger(const seastar::shared_ptr& logger); 27 | 28 | /** Do some work for given application state */ 29 | seastar::future<> handle(Container& container, ApplicationState state) override; 30 | 31 | /** Constructor */ 32 | LoggingModule(); 33 | 34 | private: 35 | seastar::shared_ptr logger_; 36 | }; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /include/CPVFramework/Container/ServiceDescriptorBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "../Allocators/StackAllocator.hpp" 5 | 6 | namespace cpv { 7 | /** The base class of ServiceDescriptor */ 8 | class ServiceDescriptorBase { 9 | public: 10 | /** Virtual destructor */ 11 | virtual ~ServiceDescriptorBase() = default; 12 | }; 13 | 14 | /** The pointer type used to store all service descriptor in container */ 15 | using ServiceDescriptorPtr = std::unique_ptr; 16 | 17 | /** The collection type of service descriptor */ 18 | using ServiceDescriptorCollection = seastar::shared_ptr>; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /include/CPVFramework/Container/ServiceFactoryBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cpv { 4 | class Container; 5 | class ServiceStorage; 6 | 7 | /** Base class of service factory */ 8 | template 9 | class ServiceFactoryBase { 10 | public: 11 | /** 12 | * Create an instance of service. 13 | * The container and storage arguments are for resolving dependencies, 14 | * the lifetime of instance should not be managed here. 15 | */ 16 | virtual T operator()(const Container& container, ServiceStorage& storage) const = 0; 17 | 18 | /** Virtual destructor */ 19 | virtual ~ServiceFactoryBase() = default; 20 | }; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /include/CPVFramework/Container/ServiceLifetime.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cpv { 4 | /** Defines how to manage service lifetime */ 5 | enum class ServiceLifetime { 6 | /** Create new instance every times */ 7 | Transient = 0, 8 | /** For given container, only create instance once and reuse it in the future (a.k.a Singleton) */ 9 | Persistent = 1, 10 | /** For given storage, only create instance once and reuse it in the future (a.k.a Scoped) */ 11 | StoragePersistent = 2 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Container/ServiceStorage.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Allocators/StackAllocator.hpp" 4 | 5 | namespace cpv { 6 | /** The storage used to store instance of services with ServiceLifetime::StoragePersistent */ 7 | class ServiceStorage { 8 | public: 9 | /** Get the service instance with associated key, may return empty object */ 10 | std::any get(std::uintptr_t key) const; 11 | 12 | /** Set the service instance with associated key */ 13 | void set(std::uintptr_t key, std::any&& value); 14 | 15 | /** Clear all instances store in this storage */ 16 | void clear(); 17 | 18 | private: 19 | /** Store service instances with lifetime StoragePersistent, key is pointer of descriptor */ 20 | StackAllocatedUnorderedMap instances_; 21 | }; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /include/CPVFramework/Container/ServiceTraits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "../Allocators/StackAllocator.hpp" 5 | #include "../Utility/ObjectTrait.hpp" 6 | 7 | namespace cpv { 8 | /** Determine attributes of a service type, like is collection or not */ 9 | template 10 | struct ServiceTypeTrait { 11 | static const constexpr bool IsCollection = false; 12 | using Type = T; 13 | using ActualType = T; 14 | }; 15 | 16 | /** Specialize for collection like types */ 17 | template 18 | struct ServiceTypeTrait::IsCollectionLike>> { 19 | static const constexpr bool IsCollection = true; 20 | using Type = T; 21 | using ActualType = typename ObjectTrait::UnderlyingType; 22 | 23 | static void add(Type& collection, ActualType&& element) { 24 | ObjectTrait::add(collection, std::move(element)); 25 | } 26 | }; 27 | 28 | /** For std::optional, will store the last service instance if exists */ 29 | template 30 | struct ServiceTypeTrait> { 31 | static const constexpr bool IsCollection = true; 32 | using Type = std::optional; 33 | using ActualType = T; 34 | 35 | static void add(Type& collection, ActualType&& element) { 36 | collection.emplace(std::move(element)); 37 | } 38 | }; 39 | 40 | /** Get dependency types that should inject to the constructor, by default it's empty */ 41 | template 42 | struct ServiceDependencyTrait { 43 | using DependencyTypes = std::tuple<>; 44 | }; 45 | 46 | /** Get dependency types that should inject to the constructor, from T::DependencyTypes */ 47 | template 48 | struct ServiceDependencyTrait > 0)>> { 49 | // For example: std::tuple; 50 | using DependencyTypes = typename T::DependencyTypes; 51 | }; 52 | } 53 | 54 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/ContainerException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Container related exception like get service instance failed. 7 | * Example: throw ContainerException(CPV_CODEINFO, "some error"); 8 | */ 9 | class ContainerException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/DeserializeException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Deserialize error, usually mean the format of serialized value is incorrect 7 | * Example: throw Deserialize(CPV_CODEINFO, "some error"); 8 | */ 9 | class DeserializeException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/Exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Utility/StringUtils.hpp" 4 | #include "../Utility/CodeInfo.hpp" 5 | 6 | namespace cpv { 7 | /** 8 | * The base class of all exceptions this library will throw. 9 | * Example: throw Exception(CPV_CODEINFO, "some error"); 10 | */ 11 | class Exception : public std::runtime_error { 12 | public: 13 | /** Constructor */ 14 | template 15 | Exception(CodeInfo&& codeInfo, Args&&... args) : 16 | Exception(std::move(codeInfo).str(), joinString(" ", std::forward(args)...)) { } 17 | 18 | protected: 19 | /** Constructor */ 20 | Exception(std::string&& codeInfoStr, std::string&& message); 21 | }; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/FileSystemException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * File system related exception like open file error. 7 | * Example: throw FileSystemException(CPV_CODEINFO, "some error"); 8 | */ 9 | class FileSystemException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/FormatException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Data format error, usually cause by library user. 7 | * Example: throw FormatException(CPV_CODEINFO, "some error"); 8 | */ 9 | class FormatException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/LengthException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Error means buffer length not enough. 7 | * Example: throw LengthException(CPV_CODEINFO, "some error"); 8 | */ 9 | class LengthException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/LogicException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Logic error, usually mean there something wrong in the code. 7 | * Example: throw LogicException(CPV_CODEINFO, "some error"); 8 | */ 9 | class LogicException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/NetworkException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Network related exception like connect, send or receive error. 7 | * Example: throw NetworkException(CPV_CODEINFO, "some error"); 8 | */ 9 | class NetworkException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/NotImplementedException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Error means feature has not yet implemented. 7 | * Example: throw NotImplementedException(CPV_CODEINFO, "some error"); 8 | */ 9 | class NotImplementedException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/OutOfRangeException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Error means access by index out of range or by key not exists. 7 | * Example: throw OutOfRangeException(CPV_CODEINFO, "some error"); 8 | */ 9 | class OutOfRangeException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/OverflowException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Arithmetic overflow or underflow exception. 7 | * Example: throw OverflowException(CPV_CODEINFO, "some error"); 8 | */ 9 | class OverflowException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Exceptions/UUIDConflictException.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./Exception.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Exception throws when two uuid is conflict. 7 | * Example: throw UUIDConflictException(CPV_CODEINFO, "some error"); 8 | */ 9 | class UUIDConflictException : public Exception { 10 | public: 11 | using Exception::Exception; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpForm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Allocators/StackAllocator.hpp" 3 | #include "../Utility/Packet.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * Class used to parse and build url encoded form 8 | * 9 | * Example (url encoded): 10 | * a=1&b=2&b=3 11 | * ``` 12 | * formParameters: { "a": [ "1" ], b: [ "2", "3" ] } 13 | * ``` 14 | * 15 | * Notice: 16 | * Duplicated form parameter is supported, you can use getMany() to get all values, 17 | * or use get() to get the first value. 18 | * For performance reason, form parser will ignore all errors. 19 | * 20 | * TODO: add parseMultipart and buildMultipart, may require layout change for files. 21 | */ 22 | class HttpForm { 23 | public: 24 | using ValuesType = StackAllocatedVector; 25 | using FormParametersType = StackAllocatedMap; 26 | 27 | /** Get all values */ 28 | const FormParametersType& getAll() const& { return formParameters_; } 29 | FormParametersType& getAll() & { return formParameters_; } 30 | 31 | /** 32 | * Get the first value associated with given key. 33 | * return empty if key not exists. 34 | */ 35 | SharedString get(const SharedString& key) const { 36 | auto it = formParameters_.find(key); 37 | if (it != formParameters_.end() && !it->second.empty()) { 38 | return it->second.front().share(); 39 | } 40 | return SharedString(); 41 | } 42 | 43 | /** 44 | * Get values associated with given key. 45 | * return a static empty vector if key not exists. 46 | */ 47 | const ValuesType& getMany(const SharedString& key) const& { 48 | auto it = formParameters_.find(key); 49 | return (it != formParameters_.end()) ? it->second : Empty; 50 | } 51 | 52 | /** 53 | * Add value associated with given key. 54 | * Call it multiple times can associate multiple values with the same key. 55 | */ 56 | void add(SharedString&& key, SharedString&& value) { 57 | formParameters_[std::move(key)].emplace_back(std::move(value)); 58 | } 59 | 60 | /** Remove values associated with given key */ 61 | void remove(const SharedString& key) { 62 | formParameters_.erase(key); 63 | } 64 | 65 | /** Remove all values */ 66 | void clear() { 67 | formParameters_.clear(); 68 | } 69 | 70 | /** Parse url encoded form body */ 71 | void parseUrlEncoded(const SharedString& body); 72 | 73 | /** Apend url encoded form body to packet */ 74 | void buildUrlEncoded(Packet& packet); 75 | 76 | /** Constructor */ 77 | HttpForm(); 78 | 79 | /** Construct with url encoded form body */ 80 | explicit HttpForm(const SharedString& body) : HttpForm() { 81 | parseUrlEncoded(body); 82 | } 83 | 84 | private: 85 | static const ValuesType Empty; 86 | 87 | private: 88 | FormParametersType formParameters_; 89 | }; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpRequest.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Stream/InputStreamBase.hpp" 4 | #include "../Utility/Reusable.hpp" 5 | #include "../Utility/SharedString.hpp" 6 | #include "./HttpRequestHeaders.hpp" 7 | #include "./HttpRequestUri.hpp" 8 | #include "./HttpRequestCookies.hpp" 9 | 10 | namespace cpv { 11 | /** Members of HttpRequest */ 12 | class HttpRequestData; 13 | 14 | /** 15 | * Contains headers, body and addition informations of a http request. 16 | * It should contains only data so it can be mock easily. 17 | */ 18 | class HttpRequest { 19 | public: 20 | /** Get the request method, e.g. "GET" */ 21 | const SharedString& getMethod() const&; 22 | 23 | /* Set the request method */ 24 | void setMethod(SharedString&& method); 25 | 26 | /** Get the request url, e.g. "/test" */ 27 | const SharedString& getUrl() const&; 28 | 29 | /** Set the request url */ 30 | void setUrl(SharedString&& url); 31 | 32 | /** Get the http version string, e.g. "HTTP/1.1" */ 33 | const SharedString& getVersion() const&; 34 | 35 | /** Set the http version string */ 36 | void setVersion(SharedString&& version); 37 | 38 | /** Get request headers */ 39 | HttpRequestHeaders& getHeaders() &; 40 | const HttpRequestHeaders& getHeaders() const&; 41 | 42 | /** Set request header */ 43 | void setHeader(SharedString&& key, SharedString&& value); 44 | 45 | /** Get request body input stream, must check whether is null before access */ 46 | const Reusable& getBodyStream() const&; 47 | 48 | /** Set request body input stream */ 49 | void setBodyStream(Reusable&& bodyStream); 50 | 51 | /** Get the uri instance parsed from url */ 52 | HttpRequestUri& getUri() &; 53 | const HttpRequestUri& getUri() const&; 54 | 55 | /** Get the cookies collection parsed from Cookie header */ 56 | HttpRequestCookies& getCookies() &; 57 | const HttpRequestCookies& getCookies() const&; 58 | 59 | /** Constructor */ 60 | HttpRequest(); 61 | 62 | /** Constructor for null request */ 63 | explicit HttpRequest(nullptr_t); 64 | 65 | private: 66 | Reusable data_; 67 | }; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpRequestCookies.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Allocators/StackAllocator.hpp" 3 | #include "../Utility/SharedString.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * Cookies collection for http request 8 | * Notice: 9 | * It will assume cookies are already encoded by url encoding or base64. 10 | */ 11 | class HttpRequestCookies { 12 | public: 13 | using CookiesType = StackAllocatedMap; 14 | 15 | /** Get cookie value for given key, return empty string if key not exists */ 16 | SharedString get(const SharedString& key) const; 17 | 18 | /** Get all cookies */ 19 | const CookiesType& getAll() const&; 20 | 21 | /** Parse the value from Cookie header */ 22 | void parse(const SharedString& cookies); 23 | 24 | /** Clear all parsed cookies */ 25 | void clear(); 26 | 27 | private: 28 | /** Constructor */ 29 | HttpRequestCookies(); 30 | 31 | // make auto generated constructors and assign operators private 32 | HttpRequestCookies(const HttpRequestCookies&) = default; 33 | HttpRequestCookies(HttpRequestCookies&&) = default; 34 | HttpRequestCookies& operator=(const HttpRequestCookies&) = default; 35 | HttpRequestCookies& operator=(HttpRequestCookies&&) = default; 36 | 37 | friend class HttpRequestData; 38 | 39 | private: 40 | CookiesType cookies_; 41 | }; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpRequestExtensions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./HttpRequest.hpp" 3 | #include "../Serialize/FormDeserializer.hpp" 4 | #include "../Serialize/JsonDeserializer.hpp" 5 | #include "../Stream/InputStreamExtensions.hpp" 6 | #include "../Utility/ObjectTrait.hpp" 7 | 8 | namespace cpv::extensions { 9 | /** Read all data from request body stream and return as string */ 10 | static inline seastar::future 11 | readBodyStream(const HttpRequest& request) { 12 | return readAll(request.getBodyStream()); 13 | } 14 | 15 | /** Read json from request body stream and convert to model */ 16 | template 17 | seastar::future readBodyStreamAsJson(const HttpRequest& request) { 18 | return readBodyStream(request).then([] (SharedString str) { 19 | T model; 20 | auto error = deserializeJson(model, str); 21 | if (CPV_UNLIKELY(error.has_value())) { 22 | return seastar::make_exception_future(*error); 23 | } 24 | return seastar::make_ready_future(std::move(model)); 25 | }); 26 | } 27 | 28 | /** Read form body from request body stream and convert to model */ 29 | template 30 | seastar::future readBodyStreamAsForm(const HttpRequest& request) { 31 | return readBodyStream(request).then([] (SharedString str) { 32 | // TODO: support multiple part form 33 | T model; 34 | deserializeForm(model, str); 35 | return model; 36 | }); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpRequestUri.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Utility/Uri.hpp" 3 | 4 | namespace cpv { 5 | /** 6 | * Uri for http request 7 | * Notice: this collection may contains string_view that it's storage is hold in HttpRequest 8 | */ 9 | class HttpRequestUri : public Uri { 10 | private: 11 | using Uri::Uri; 12 | using Uri::operator=; 13 | friend class HttpRequestData; 14 | }; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpResponse.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Allocators/StackAllocator.hpp" 3 | #include "../Stream/OutputStreamBase.hpp" 4 | #include "../Utility/Reusable.hpp" 5 | #include "../Utility/SharedString.hpp" 6 | #include "./HttpResponseHeaders.hpp" 7 | 8 | namespace cpv { 9 | /** Members of HttpResponse */ 10 | class HttpResponseData; 11 | 12 | /** 13 | * Contains headers, body and addition informations of a http response. 14 | * It should contains only data so it can be mock easily. 15 | */ 16 | class HttpResponse { 17 | public: 18 | /** Get the http version string, e.g. "HTTP/1.1" */ 19 | const SharedString& getVersion() const&; 20 | 21 | /** Set the http version string */ 22 | void setVersion(SharedString&& version); 23 | 24 | /** Get the status code, e.g. "404" */ 25 | const SharedString& getStatusCode() const&; 26 | 27 | /** Set the status code */ 28 | void setStatusCode(SharedString&& statusCode); 29 | 30 | /** Get the reason message of status code, e.g. "Not Found" */ 31 | const SharedString& getStatusMessage() const&; 32 | 33 | /** Set the reason message of status code */ 34 | void setStatusMessage(SharedString&& statusMessage); 35 | 36 | /** Get response headers */ 37 | HttpResponseHeaders& getHeaders() &; 38 | const HttpResponseHeaders& getHeaders() const&; 39 | 40 | /** Set response header */ 41 | void setHeader(SharedString&& key, SharedString&& value); 42 | 43 | /** Get response body output stream, must check whether is null before access */ 44 | const Reusable& getBodyStream() const&; 45 | 46 | /** Set response body output stream */ 47 | void setBodyStream(Reusable&& bodyStream); 48 | 49 | /** Constructor */ 50 | HttpResponse(); 51 | 52 | /** Constructor for null response */ 53 | explicit HttpResponse(nullptr_t); 54 | 55 | private: 56 | Reusable data_; 57 | }; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /include/CPVFramework/Http/HttpResponseExtensions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "../Stream/OutputStreamExtensions.hpp" 5 | #include "../Utility/StringUtils.hpp" 6 | #include "../Utility/SharedString.hpp" 7 | #include "./HttpResponse.hpp" 8 | 9 | namespace cpv::extensions { 10 | /** Reply text or binary contents to http response in once */ 11 | template 12 | seastar::future<> reply( 13 | HttpResponse& response, 14 | T&& text, 15 | SharedString&& mimeType, 16 | SharedString&& statusCode, 17 | SharedString&& statusMessage) { 18 | auto& headers = response.getHeaders(); 19 | response.setStatusCode(std::move(statusCode)); 20 | response.setStatusMessage(std::move(statusMessage)); 21 | headers.setContentType(std::move(mimeType)); 22 | headers.setContentLength(SharedString::fromInt(sizeofString(text))); 23 | return writeAll(response.getBodyStream(), std::forward(text)); 24 | } 25 | 26 | /** Reply text or binary contents to http response in once */ 27 | template 28 | seastar::future<> reply(HttpResponse& response, T&& text, SharedString&& mimeType) { 29 | return reply(response, std::forward(text), 30 | std::move(mimeType), constants::_200, constants::OK); 31 | } 32 | 33 | /** Reply text or binary contents to http response in once */ 34 | template 35 | seastar::future<> reply(HttpResponse& response, T&& text) { 36 | return reply(response, std::forward(text), constants::TextPlainUtf8); 37 | } 38 | 39 | /** Reply 302 Found with given location to http response */ 40 | seastar::future<> redirectTo(HttpResponse& response, SharedString&& location); 41 | 42 | /** Reply 301 Moved Permanently with give location to http response */ 43 | seastar::future<> redirectToPermanently(HttpResponse& response, SharedString&& location); 44 | 45 | /** Add or replace cookie on client side */ 46 | void setCookie( 47 | HttpResponse& response, 48 | const SharedString& key, 49 | const SharedString& value, 50 | const SharedString& path = "/", 51 | const SharedString& domain = "", 52 | std::optional expires = std::nullopt, 53 | bool httpOnly = false, 54 | bool secure = false, 55 | const SharedString& sameSite = ""); 56 | 57 | /** Remove cookie on client side */ 58 | void removeCookie( 59 | HttpResponse& response, 60 | const SharedString& key, 61 | const SharedString& path = "/", 62 | const SharedString& domain = ""); 63 | } 64 | 65 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/Handlers/HttpServerRequest404Handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./HttpServerRequestHandlerBase.hpp" 3 | 4 | namespace cpv { 5 | /** Request handler that returns 404 not found as page content (no redirect) */ 6 | class HttpServerRequest404Handler : public HttpServerRequestHandlerBase { 7 | public: 8 | /** Return 404 not found */ 9 | seastar::future<> handle( 10 | HttpContext& context, 11 | HttpServerRequestHandlerIterator) const override; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/Handlers/HttpServerRequest500Handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Logging/Logger.hpp" 3 | #include "./HttpServerRequestHandlerBase.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * Request handler that capture exceptions from next handlers and return 500. 8 | * The exception will associate with an unique uuid and log with 'Error' level, 9 | * and the uuid will display inside the response body. 10 | */ 11 | class HttpServerRequest500Handler : public HttpServerRequestHandlerBase { 12 | public: 13 | using DependencyTypes = std::tuple>; 14 | 15 | /** Return 500 internal server error */ 16 | seastar::future<> handle( 17 | HttpContext& context, 18 | HttpServerRequestHandlerIterator next) const override; 19 | 20 | /** Constructor */ 21 | explicit HttpServerRequest500Handler(seastar::shared_ptr logger); 22 | 23 | private: 24 | seastar::shared_ptr logger_; 25 | }; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/Handlers/HttpServerRequestFunctionHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./HttpServerRequestHandlerBase.hpp" 3 | 4 | namespace cpv { 5 | /** Request handler that use custom function object */ 6 | template , Func, HttpContext&>, int> = 0> 9 | class HttpServerRequestFunctionHandler : public HttpServerRequestHandlerBase { 10 | public: 11 | /** Invoke custom function object */ 12 | seastar::future<> handle( 13 | HttpContext& context, 14 | HttpServerRequestHandlerIterator) const override { 15 | return func_(context); 16 | } 17 | 18 | /** Constructor */ 19 | explicit HttpServerRequestFunctionHandler(Func func) : 20 | func_(std::move(func)) { } 21 | 22 | private: 23 | Func func_; 24 | }; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/Handlers/HttpServerRequestHandlerBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "../HttpContext.hpp" 6 | 7 | namespace cpv { 8 | /** The iterator type of HttpServerRequestHandler */ 9 | class HttpServerRequestHandlerBase; 10 | using HttpServerRequestHandlerCollection = std::vector>; 11 | using HttpServerRequestHandlerIterator = HttpServerRequestHandlerCollection::const_iterator; 12 | 13 | /** The interface of a http server request handler */ 14 | class HttpServerRequestHandlerBase { 15 | public: 16 | /** Virtual destructor */ 17 | virtual ~HttpServerRequestHandlerBase() = default; 18 | 19 | /** 20 | * Handle a http server request. 21 | * If the request is not handled in this handler, pass it to the next handler: 22 | * (*next)->handle(context, next + 1); 23 | */ 24 | virtual seastar::future<> handle( 25 | HttpContext& context, 26 | HttpServerRequestHandlerIterator next) const = 0; 27 | }; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/Handlers/HttpServerRequestParametersFunctionHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../HttpContextExtensions.hpp" 4 | #include "./HttpServerRequestHandlerBase.hpp" 5 | 6 | namespace cpv { 7 | /** Custom checker that checks whether Func invocable with given parameters */ 8 | template 9 | constexpr bool HttpServerRequestParametersFunctionHandlerTypeChecker(std::index_sequence) { 10 | return std::is_invocable_r_v, Func, HttpContext&, decltype( 11 | extensions::getParameter( 12 | std::declval(), 13 | std::get(std::declval())))...>; 14 | } 15 | 16 | /** 17 | * Request handler that use custom function object and invoke it with given parameters. 18 | * 19 | * Params should be a tuple contains types which supports 20 | * cpv::extensions::getParameter(const HttpContext&, const T&), 21 | * there are some built-in types under namespace cpv::extensions::http_context_parameters, 22 | * you can see them inside HttpContextExtensions.hpp. 23 | * 24 | * For example: 25 | * ``` 26 | * using namespace cpv::extensions::http_context_parameters; 27 | * HttpServerRequestParametersFunctionHandler handler( 28 | * std::make_tuple(PathFragment(1), Query("abc")), 29 | * [] (HttpContext& context, SharedString id, SharedString name) { 30 | * ... 31 | * }); 32 | * // will invoke func with: 33 | * // func(context, request.getUri().getPathFragment(1), request.getUri().getQueryParameter("abc")); 34 | * handler.handle(context, ...); 35 | * ``` 36 | */ 37 | template ( 40 | std::make_index_sequence>()), int> = 0> 41 | class HttpServerRequestParametersFunctionHandler : public HttpServerRequestHandlerBase { 42 | public: 43 | /** Invoke custom function object with given parameters */ 44 | seastar::future<> handle( 45 | HttpContext& context, 46 | HttpServerRequestHandlerIterator) const override { 47 | return handleImpl(context, 48 | std::make_index_sequence>()); 49 | } 50 | 51 | /** Constructor */ 52 | HttpServerRequestParametersFunctionHandler(Params params, Func func) : 53 | params_(std::move(params)), func_(std::move(func)) { } 54 | 55 | private: 56 | /** The implementation of handle */ 57 | template 58 | seastar::future<> handleImpl( 59 | HttpContext& context, std::index_sequence) const { 60 | return func_(context, 61 | extensions::getParameter(context, std::get(params_))...); 62 | } 63 | 64 | private: 65 | Params params_; 66 | Func func_; 67 | }; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/Handlers/HttpServerRequestStaticFileHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../Utility/SharedString.hpp" 3 | #include "./HttpServerRequestHandlerBase.hpp" 4 | 5 | namespace cpv { 6 | /** Members of HttpServerRequestStaticFileHandler */ 7 | class HttpServerRequestStaticFileHandlerData; 8 | 9 | /** 10 | * Request handler that returns content of files. 11 | * 12 | * It will check whether url is starts with urlBase, and check whether file path is exists. 13 | * For example: 14 | * urlBase is /static, pathBase is ./storage/static 15 | * When request url is /static/js/main.js, this handler will try to return ./storage/static/js/main.js. 16 | * Notice if path is not exists or contains invalid chars like '..' or '//' then it will pass to next handler. 17 | * 18 | * It can use lru cache to cache file content in memory (per cpu core), if file size is less than 19 | * maxCacheFileSize and maxCacheFileEntities is not 0, it will put file content to cache for next use. 20 | * You should disable file caching for local development environment by setting maxCacheFileEntities to 0. 21 | * 22 | * It supports pre-compressed gzip files, for example if file path is ./1.txt and client accept gzip 23 | * encoding, then it will also search for ./1.txt.gz and return it if exists. 24 | * 25 | * It supports Range header, but it won't use cache and gzip files when range header is presented, 26 | * because usually range header is used for downloading large pre-compressed files. 27 | * 28 | * It supports If-Modified-Since header, if file not change then it will return 304 response. 29 | */ 30 | class HttpServerRequestStaticFileHandler : public HttpServerRequestHandlerBase { 31 | public: 32 | static const std::size_t DefaultMaxCacheFileEntities = 16; 33 | static const std::size_t DefaultMaxCacheFileSize = 1048576; // 1mb 34 | 35 | /** Return content of request file */ 36 | seastar::future<> handle( 37 | HttpContext& context, 38 | HttpServerRequestHandlerIterator next) const override; 39 | 40 | /** Clear cached file contents */ 41 | void clearCache(); 42 | 43 | /** Constructor */ 44 | HttpServerRequestStaticFileHandler( 45 | // like "/static" 46 | SharedString&& urlBase, 47 | // like "./storage/static" 48 | SharedString&& pathBase, 49 | // like "max-age=84600, public" or "" (not sending Cache-Control) 50 | SharedString&& cacheControl = "", 51 | std::size_t maxCacheFileEntities = DefaultMaxCacheFileEntities, 52 | std::size_t maxCacheFileSize = DefaultMaxCacheFileSize); 53 | 54 | /** Move constructor (for incomplete member type) */ 55 | HttpServerRequestStaticFileHandler(HttpServerRequestStaticFileHandler&&); 56 | 57 | /** Move assign operator (for incomplete member type) */ 58 | HttpServerRequestStaticFileHandler& operator=(HttpServerRequestStaticFileHandler&&); 59 | 60 | /** Destructor (for incomplete member type) */ 61 | ~HttpServerRequestStaticFileHandler(); 62 | 63 | private: 64 | std::unique_ptr data_; 65 | }; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/HttpContext.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Container/Container.hpp" 4 | #include "../Container/Container.hpp" 5 | #include "../Container/ServiceStorage.hpp" 6 | #include "../Http/HttpRequest.hpp" 7 | #include "../Http/HttpResponse.hpp" 8 | 9 | namespace cpv { 10 | /** The context type for handling single http request on http server */ 11 | class HttpContext { 12 | public: 13 | /** Get the request received from http client */ 14 | HttpRequest& getRequest() & { return request_; } 15 | const HttpRequest& getRequest() const& { return request_; } 16 | 17 | /** Get the response reply to http client */ 18 | HttpResponse& getResponse() & { return response_; } 19 | const HttpResponse& getResponse() const& { return response_; } 20 | 21 | /** Get the socket address of http client */ 22 | const seastar::socket_address& getClientAddress() const& { return clientAddress_; } 23 | 24 | /** Get service instance with service storage associated with this context */ 25 | template 26 | TService getService() const { 27 | return container_.get(serviceStorage_); 28 | } 29 | 30 | /** Get service instances with service storage associated with this context */ 31 | template ::IsCollection, int> = 0> 32 | std::size_t getManyServices(T& collection) const { 33 | return container_.getMany(collection, serviceStorage_); 34 | } 35 | 36 | /** Update the request and the response in this context */ 37 | void setRequestResponse(HttpRequest&& request, HttpResponse&& response) { 38 | request_ = std::move(request); 39 | response_ = std::move(response); 40 | } 41 | 42 | /** Set the socket address of http client */ 43 | void setClientAddress(seastar::socket_address&& clientAddress) { 44 | clientAddress_ = std::move(clientAddress); 45 | } 46 | 47 | /** Update the container in this context */ 48 | void setContainer(const Container& container) { 49 | container_ = container; 50 | } 51 | 52 | /** Clear the service storage, usually you should call it after setRequestResponse */ 53 | void clearServiceStorage() { 54 | serviceStorage_.clear(); 55 | } 56 | 57 | /** Constructor */ 58 | HttpContext() : 59 | request_(), 60 | response_(), 61 | clientAddress_(seastar::make_ipv4_address(0, 0)), 62 | container_(), 63 | serviceStorage_() { } 64 | 65 | /** Constructor for null context, should set members later */ 66 | explicit HttpContext(nullptr_t) : 67 | request_(nullptr), 68 | response_(nullptr), 69 | clientAddress_(), 70 | container_(nullptr), 71 | serviceStorage_() { } 72 | 73 | private: 74 | HttpRequest request_; 75 | HttpResponse response_; 76 | seastar::socket_address clientAddress_; 77 | Container container_; 78 | mutable ServiceStorage serviceStorage_; 79 | }; 80 | } 81 | 82 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/HttpContextExtensions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Http/HttpRequestExtensions.hpp" 3 | #include "../Http/HttpResponseExtensions.hpp" 4 | #include "../Utility/ObjectTrait.hpp" 5 | #include "./HttpContext.hpp" 6 | 7 | namespace cpv::extensions { 8 | // namespace for getParameter(HttpContext, ...); 9 | // you can use `using namespace cpv::extensions::http_context_parameters;` for convenient 10 | namespace http_context_parameters { 11 | /** Index of path fragment */ 12 | enum class PathFragment : std::size_t { }; 13 | /** Key of query parameters */ 14 | class Query : public SharedString { using SharedString::SharedString; }; 15 | /** Service from container (you can use collection type to retrive multiple) */ 16 | template class Service { }; 17 | /** Json body and model type */ 18 | template class JsonModel { }; 19 | /** Form body and model type */ 20 | template class FormModel { }; 21 | } 22 | 23 | /** Get paramter by index of path fragment */ 24 | static inline SharedString getParameter( 25 | const HttpContext& context, 26 | http_context_parameters::PathFragment index) { 27 | return context.getRequest().getUri() 28 | .getPathFragment(static_cast(index)); 29 | } 30 | 31 | /** Get paramter by key of query parameters */ 32 | static inline SharedString getParameter( 33 | const HttpContext& context, 34 | const http_context_parameters::Query& key) { 35 | return context.getRequest().getUri().getQueryParameter(key); 36 | } 37 | 38 | /** Get service or services from container */ 39 | template 40 | static inline T getParameter( 41 | const HttpContext& context, 42 | http_context_parameters::Service) { 43 | if constexpr (ServiceTypeTrait::IsCollection) { 44 | T collection; // optional object will be empty 45 | context.getManyServices(collection); 46 | return collection; 47 | } else { 48 | return context.getService(); 49 | } 50 | } 51 | 52 | /** Read json from request body stream and convert to model */ 53 | template 54 | static inline seastar::future getParameter( 55 | const HttpContext& context, 56 | http_context_parameters::JsonModel) { 57 | return readBodyStreamAsJson(context.getRequest()); 58 | } 59 | 60 | /** Read form body from request body stream and convert to model */ 61 | template 62 | static inline seastar::future getParameter( 63 | const HttpContext& context, 64 | http_context_parameters::FormModel) { 65 | return readBodyStreamAsForm(context.getRequest()); 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /include/CPVFramework/HttpServer/HttpServer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "../Container/Container.hpp" 6 | #include "../Logging/Logger.hpp" 7 | #include "./Handlers/HttpServerRequestHandlerBase.hpp" 8 | #include "./HttpServerConfiguration.hpp" 9 | 10 | namespace cpv { 11 | /** Members of HttpServer */ 12 | class HttpServerData; 13 | 14 | /** 15 | * Http server object used to accept and handle http connections, 16 | * Should run on each core. 17 | */ 18 | class HttpServer { 19 | public: 20 | /** Start accept http connections */ 21 | seastar::future<> start(); 22 | 23 | /** Stop accept http connection and close all exists connections */ 24 | seastar::future<> stop(); 25 | 26 | /** 27 | * Constructor 28 | * 29 | * Container should contains following services: 30 | * - HttpServerConfiguration 31 | * - seastar::shared_ptr 32 | * - one or more std::shared_ptr, order is matter 33 | * 34 | * Rules about handler list: 35 | * The first handler should be a 500 handler. (e.g. HttpServerRequest500Handler) 36 | * The last handler should be a 404 handler. (e.g. HttpServerRequest404Handler) 37 | * The last handler must not call the next handler, there is a real last handler 38 | * but only returns exception future. 39 | */ 40 | explicit HttpServer(const Container& container); 41 | 42 | /** Move constructor (for incomplete member type) */ 43 | HttpServer(HttpServer&&); 44 | 45 | /** Move assign operator (for incomplete member type) */ 46 | HttpServer& operator=(HttpServer&&); 47 | 48 | /** Destructor (for incomplete member type) */ 49 | ~HttpServer(); 50 | 51 | private: 52 | std::unique_ptr data_; 53 | }; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /include/CPVFramework/Logging/Logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Utility/StringUtils.hpp" 4 | #include "../Utility/EnumUtils.hpp" 5 | #include "../Utility/Macros.hpp" 6 | 7 | namespace cpv { 8 | /** For logger */ 9 | enum class LogLevel { 10 | Emergency = 0, 11 | Alert = 1, 12 | Critical = 2, 13 | Error = 3, 14 | Warning = 4, 15 | Notice = 5, 16 | Info = 6, 17 | Debug = 7 18 | }; 19 | 20 | template <> 21 | struct EnumDescriptions { 22 | static const std::vector>& get(); 23 | }; 24 | 25 | /** Interface use to log messages */ 26 | class Logger { 27 | public: 28 | /** Get the log level */ 29 | LogLevel getLogLevel() const { return logLevel_; } 30 | 31 | /** Set the log level */ 32 | void setLogLevel(LogLevel logLevel) { logLevel_ = logLevel; } 33 | 34 | /** Check whether this log level is enabled */ 35 | bool isEnabled(LogLevel logLevel) const { return logLevel <= logLevel_; } 36 | 37 | /** Log message with specified log level, it may do nothing if the level is not enabled */ 38 | template 39 | void log(LogLevel logLevel, const Args&... args) { 40 | if (CPV_UNLIKELY(isEnabled(logLevel))) { 41 | logImpl(logLevel, joinString("", 42 | " ", 43 | joinString(" ", args...), '\n')); 44 | } 45 | } 46 | 47 | /** Constructor */ 48 | explicit Logger(LogLevel logLevel); 49 | 50 | /** Virtual destructor */ 51 | virtual ~Logger() = default; 52 | 53 | /** Create a console logger */ 54 | static seastar::shared_ptr createConsole(LogLevel logLevel); 55 | 56 | /** Create a noop logger */ 57 | static seastar::shared_ptr createNoop(); 58 | 59 | protected: 60 | /** The implmentation of log, may write to console or database */ 61 | virtual void logImpl(LogLevel logLevel, const std::string& message) = 0; 62 | 63 | /** Get thread id for logging */ 64 | static std::size_t getThreadId(); 65 | 66 | private: 67 | LogLevel logLevel_; 68 | }; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /include/CPVFramework/Serialize/FormDeserializer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Http/HttpForm.hpp" 3 | #include "../Utility/ObjectTrait.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * The class used to deserialize form to model. 8 | * The model should contains a public function named `loadForm` and 9 | * takes `const HttpForm&` as argument. 10 | */ 11 | template 12 | class FormDeserializer { 13 | public: 14 | /** Deserialize form to model */ 15 | static void deserialize(T& model, const SharedString& formBody) { 16 | using Trait = ObjectTrait; 17 | if constexpr (Trait::IsPointerLike) { 18 | // if model is pointer type, ensure it's not nullptr 19 | if (Trait::get(model) == nullptr) { 20 | model = Trait::create(); 21 | } 22 | } 23 | HttpForm form(formBody); 24 | if constexpr (Trait::IsPointerLike) { 25 | Trait::get(model)->loadForm(form); 26 | } else { 27 | Trait::get(model).loadForm(form); 28 | } 29 | } 30 | }; 31 | 32 | /** Convenient static function for FormDeserializer */ 33 | template 34 | static inline void deserializeForm(T& model, const SharedString& formBody) { 35 | return FormDeserializer::deserialize(model, formBody); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /include/CPVFramework/Serialize/FormSerializer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Http/HttpForm.hpp" 3 | #include "../Utility/ObjectTrait.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * The class used to serialize model to url encoded form packet. 8 | * The model should contains a public function named `dumpForm` and 9 | * takes `HttpForm&` as argument. 10 | */ 11 | template 12 | class FormSerializer { 13 | public: 14 | /** Serialize model to form packet */ 15 | static Packet serialize(const T& model) { 16 | using Trait = ObjectTrait; 17 | Packet p; 18 | if constexpr (Trait::IsPointerLike) { 19 | if (Trait::get(model) == nullptr) { 20 | return p; 21 | } 22 | } 23 | HttpForm form; 24 | if constexpr (Trait::IsPointerLike) { 25 | Trait::get(model)->dumpForm(form); 26 | } else { 27 | Trait::get(model).dumpForm(form); 28 | } 29 | form.buildUrlEncoded(p); 30 | return p; 31 | } 32 | }; 33 | 34 | /** Convenient static function for FormSerializer */ 35 | template 36 | static inline Packet serializeForm(const T& model) { 37 | return FormSerializer::serialize(model); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/InputStreamBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "../Utility/SharedString.hpp" 5 | 6 | namespace cpv { 7 | /** Contains data read from stream */ 8 | struct InputStreamReadResult { 9 | /** Data read from stream */ 10 | SharedString data; 11 | /** Whether data is the last part of stream */ 12 | bool isEnd; 13 | 14 | /** Constructor */ 15 | InputStreamReadResult(SharedString&& dataVal, bool isEndVal) : 16 | data(std::move(dataVal)), isEnd(isEndVal) { } 17 | 18 | /** Constructor */ 19 | InputStreamReadResult() : InputStreamReadResult({}, true) { } 20 | }; 21 | 22 | /** 23 | * Interface of simple input stream. 24 | * The read function will return a mutable buffer contains data. 25 | * The mutable buffer is useful for pass to an inplace (in-situ) parser. 26 | * Seek is not supported so it's easy to implement and with less overhead. 27 | */ 28 | class InputStreamBase { 29 | public: 30 | /** Virtual destructor */ 31 | virtual ~InputStreamBase() = default; 32 | 33 | /** Read data from stream */ 34 | virtual seastar::future read() = 0; 35 | 36 | /** 37 | * Get the hint of total size of stream, may return empty if not supported 38 | * Warning: 39 | * The actual read size may greater than this size because some 40 | * implementation allows client side to control it (e.g. Content-Length), 41 | * you can use this size to pre allocate buffer, but you must check 42 | * the actual size after each read to avoid buffer overflow. 43 | */ 44 | virtual std::optional sizeHint() const { return { }; } 45 | }; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/InputStreamExtensions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Utility/Macros.hpp" 4 | #include "../Utility/SharedStringBuilder.hpp" 5 | #include "./InputStreamBase.hpp" 6 | 7 | namespace cpv::extensions { 8 | /** 9 | * Read all data from stream and append to given string builder, 10 | * must keep stream and string builder alive until future resolved. 11 | */ 12 | seastar::future<> readAll(InputStreamBase& stream, SharedStringBuilder& builder); 13 | 14 | /** 15 | * Read all data from stream and append to given string builder, 16 | * must keep stream and string builder alive until future resolved. 17 | */ 18 | template ().get()), InputStreamBase*>>* = nullptr> 20 | seastar::future<> readAll(const T& stream, SharedStringBuilder& builder) { 21 | if (CPV_UNLIKELY(stream.get() == nullptr)) { 22 | return seastar::make_ready_future<>(); 23 | } 24 | return readAll(*stream.get(), builder); 25 | } 26 | 27 | /** 28 | * Read all data from stream and return it as string, 29 | * must keep stream live until future resolved. 30 | */ 31 | seastar::future readAll(InputStreamBase& stream); 32 | 33 | /** 34 | * Read all data from stream and return it as string, 35 | * must keep stream live until future resolved. (for pointer of stream) 36 | */ 37 | template ().get()), InputStreamBase*>>* = nullptr> 39 | seastar::future readAll(const T& stream) { 40 | if (CPV_UNLIKELY(stream.get() == nullptr)) { 41 | return seastar::make_ready_future(); 42 | } 43 | return readAll(*stream.get()); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/OutputStreamBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../Utility/Packet.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * Interface of simple output stream. 8 | * The write function will take a cpv::Packet (may contains multiple segments) and 9 | * write the data of segments to stream. 10 | */ 11 | class OutputStreamBase { 12 | public: 13 | /** Virtual destructor */ 14 | virtual ~OutputStreamBase() = default; 15 | 16 | /** Write data to stream */ 17 | virtual seastar::future<> write(Packet&& data) = 0; 18 | }; 19 | } 20 | 21 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/OutputStreamExtensions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Exceptions/LogicException.hpp" 3 | #include "../Utility/SharedString.hpp" 4 | #include "./OutputStreamBase.hpp" 5 | 6 | namespace cpv::extensions { 7 | /** Write string to stream, must keep stream alive until future resolved */ 8 | static inline seastar::future<> writeAll( 9 | OutputStreamBase& stream, SharedString&& str) { 10 | return stream.write(Packet(std::move(str))); 11 | } 12 | 13 | /** Write packet to stream, must keep stream alive until future resolved */ 14 | static inline seastar::future<> writeAll( 15 | OutputStreamBase& stream, Packet&& packet) { 16 | return stream.write(std::move(packet)); 17 | } 18 | 19 | /** Write data to stream, must keep stream alive until future resolved */ 20 | template ().get()), OutputStreamBase*>>* = nullptr> 23 | seastar::future<> writeAll(T& stream, TData&& data) { 24 | if (CPV_UNLIKELY(stream.get() == nullptr)) { 25 | return seastar::make_exception_future<>(LogicException( 26 | CPV_CODEINFO, "write to null stream")); 27 | } 28 | return writeAll(*stream.get(), std::forward(data)); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/PacketInputStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Utility/Packet.hpp" 3 | #include "./InputStreamBase.hpp" 4 | 5 | namespace cpv { 6 | /** Input stream that use given packet as data source */ 7 | class PacketInputStream : public InputStreamBase { 8 | public: 9 | /** Read data from stream */ 10 | seastar::future read() override; 11 | 12 | /** Get the hint of total size of stream */ 13 | std::optional sizeHint() const override; 14 | 15 | /** For Reusable<> */ 16 | void freeResources(); 17 | 18 | /** For Reusable<> */ 19 | void reset(Packet&& packet); 20 | 21 | /** Constructor */ 22 | PacketInputStream(); 23 | 24 | private: 25 | Packet packet_; 26 | std::size_t sizeHint_; 27 | std::size_t index_; 28 | }; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/PacketOutputStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Utility/Packet.hpp" 3 | #include "./OutputStreamBase.hpp" 4 | 5 | namespace cpv { 6 | /** Output stream that use given packet as data sink */ 7 | class PacketOutputStream : public OutputStreamBase { 8 | public: 9 | /** Write data to stream */ 10 | seastar::future<> write(Packet&& data) override; 11 | 12 | /** For Reusable<> */ 13 | void freeResources(); 14 | 15 | /** For Reusable<> */ 16 | void reset(const seastar::lw_shared_ptr& packet); 17 | 18 | /** Constructor */ 19 | PacketOutputStream(); 20 | 21 | private: 22 | seastar::lw_shared_ptr packet_; 23 | }; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/StringInputStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Utility/SharedString.hpp" 3 | #include "./InputStreamBase.hpp" 4 | 5 | namespace cpv { 6 | /** Input stream that use given string as data source */ 7 | class StringInputStream : public InputStreamBase { 8 | public: 9 | /** Read data from stream */ 10 | seastar::future read() override; 11 | 12 | /** Get the hint of total size of stream */ 13 | std::optional sizeHint() const override; 14 | 15 | /** For Reusable<> */ 16 | void freeResources(); 17 | 18 | /** For Reusable<> */ 19 | void reset(SharedString&& str); 20 | 21 | /** Constructor */ 22 | StringInputStream(); 23 | 24 | private: 25 | SharedString str_; 26 | std::size_t sizeHint_; 27 | }; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /include/CPVFramework/Stream/StringOutputStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../Utility/SharedStringBuilder.hpp" 3 | #include "./OutputStreamBase.hpp" 4 | 5 | namespace cpv { 6 | /** Output stream that use given string builder as data sink */ 7 | class StringOutputStream : public OutputStreamBase { 8 | public: 9 | /** Write data to stream */ 10 | seastar::future<> write(Packet&& data) override; 11 | 12 | /** For Reusable<> */ 13 | void freeResources(); 14 | 15 | /** For Reusable<> */ 16 | void reset(const seastar::lw_shared_ptr& builder); 17 | 18 | /** Constructor */ 19 | StringOutputStream(); 20 | 21 | private: 22 | seastar::lw_shared_ptr builder_; 23 | }; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /include/CPVFramework/Testing/GTestUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../Utility/Packet.hpp" 11 | 12 | #define TEST_FUTURE(caseName, testName) \ 13 | static seastar::future<> caseName##_##testName##_FutureTestBody(); \ 14 | TEST(caseName, testName) { \ 15 | seastar::future<> f(caseName##_##testName##_FutureTestBody());\ 16 | f.get(); \ 17 | } \ 18 | static seastar::future<> caseName##_##testName##_FutureTestBody() 19 | 20 | #define ASSERT_THROWS_CONTAINS(exception, expression, contains) \ 21 | do { \ 22 | try { expression; } \ 23 | catch (const exception& ex) { \ 24 | std::string message(ex.what()); \ 25 | if (message.find(contains) == std::string::npos) { \ 26 | FAIL() << "exception message didn't contains expected words: " << message; \ 27 | } \ 28 | break; \ 29 | } \ 30 | catch (...) { throw; } \ 31 | FAIL() << "No exception throws"; \ 32 | } while (0) 33 | 34 | #define ASSERT_CONTAINS(str, pattern) \ 35 | do { \ 36 | if ((str).find(pattern) == std::string::npos) { \ 37 | FAIL() << "string didn't contains expected words: " << str; \ 38 | } \ 39 | } while (0) 40 | 41 | #define ASSERT_THROWS(exception, expression) ASSERT_THROWS_CONTAINS(exception, expression, "") 42 | 43 | namespace cpv::gtest { 44 | /** the main function of test executable */ 45 | static inline int runAllTests(int argc, char** argv) { 46 | ::testing::InitGoogleTest(&argc, argv); 47 | seastar::app_template app; 48 | int returnValue(0); 49 | app.run(argc, argv, [&returnValue] { 50 | return seastar::async([] { 51 | return RUN_ALL_TESTS(); 52 | }).then([&returnValue] (int result) { 53 | returnValue = result; 54 | // wait for internal cleanup to make leak sanitizer happy 55 | return seastar::sleep(std::chrono::seconds(1)); 56 | }); 57 | }); 58 | return returnValue; 59 | } 60 | 61 | /** create tcp connection, send request then return received response as string */ 62 | seastar::future tcpSendRequest( 63 | const std::string& ip, std::size_t port, Packet&& p); 64 | 65 | /** create tcp connection, send request partially then return received response as string */ 66 | seastar::future tcpSendPartialRequest( 67 | const std::string& ip, 68 | std::size_t port, 69 | const std::vector& parts, 70 | std::chrono::milliseconds interval); 71 | } 72 | 73 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/CodeInfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "./StringUtils.hpp" 4 | #include "./Macros.hpp" 5 | 6 | namespace cpv { 7 | /** Code information wrapper class, see macro CPV_CODEINFO */ 8 | class CodeInfo { 9 | public: 10 | /** Get the string inside this class */ 11 | std::string str() const& { return str_; } 12 | std::string str() && { return std::move(str_); } 13 | 14 | /** Constructor */ 15 | explicit CodeInfo(std::string&& str) : 16 | str_(std::move(str)) { } 17 | 18 | private: 19 | std::string str_; 20 | }; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/ConstantStrings.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace cpv::constants { 7 | // server name 8 | static const constexpr char CPVFramework[] = "cpv-framework"; 9 | 10 | // common strings 11 | static const constexpr char Space[] = " "; 12 | static const constexpr char Tab[] = "\t"; 13 | static const constexpr char Colon[] = ":"; 14 | static const constexpr char SemiColon[] = ";"; 15 | static const constexpr char Slash[] = "/"; 16 | static const constexpr char Comma[] = ","; 17 | static const constexpr char QuestionMark[] = "?"; 18 | static const constexpr char EqualsSign[] = "="; 19 | static const constexpr char Ampersand[] = "&"; 20 | static const constexpr char Hyphen[] = "-"; 21 | static const constexpr char SingleQuote[] = "'"; 22 | static const constexpr char DoubleQuote[] = "\""; 23 | static const constexpr char RoundBacketStart[] = "("; 24 | static const constexpr char RoundBacketEnd[] = ")"; 25 | static const constexpr char CurlyBacketStart[] = "{"; 26 | static const constexpr char CurlyBacketEnd[] = "}"; 27 | static const constexpr char SquareBacketStart[] = "["; 28 | static const constexpr char SquareBacketEnd[] = "]"; 29 | static const constexpr char ColonSpace[] = ": "; 30 | static const constexpr char ColonSlashSlash[] = "://"; 31 | static const constexpr char LF[] = "\n"; 32 | static const constexpr char CRLF[] = "\r\n"; 33 | static const constexpr char CRLFCRLF[] = "\r\n\r\n"; 34 | static const constexpr char True[] = "true"; 35 | static const constexpr char False[] = "false"; 36 | static const constexpr char Null[] = "null"; 37 | 38 | // numbers 39 | static const std::size_t MaxConstantInteger = 65535; 40 | extern const std::array Integers; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/DateUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace cpv { 7 | /** The size of time string used for http header, not include tailing zero */ 8 | static const constexpr std::size_t HttpHeaderTimeStringSize = 29; 9 | 10 | /** The buffer type used to format time string for http header */ 11 | using HttpHeaderTimeStringBufferType = std::array; 12 | 13 | /** 14 | * Format time for http header. 15 | * e.g. "Thu, 31 May 2007 20:35:00 GMT" 16 | * The buffer size should >= HttpHeaderTimeStringSize + 1, 17 | * it will return true for success and false for buffer size not enough. 18 | */ 19 | bool formatTimeForHttpHeader(std::time_t time, char* buf, std::size_t size); 20 | 21 | /** Format time for http header, returns a thread local static string */ 22 | std::string_view formatTimeForHttpHeader(std::time_t time); 23 | 24 | /** 25 | * Format now for http header, returns a thread local static string 26 | * Notice: 27 | * The thread local storage of this function is distinct (not shared with 28 | * the other overload), so it can be safe to used directly for Date header. 29 | */ 30 | std::string_view formatNowForHttpHeader(); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/FileUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "./SharedString.hpp" 3 | 4 | namespace cpv { 5 | /** A shortcut function to read file contents */ 6 | SharedString readFile(std::string_view filename); 7 | 8 | /** A shortcut function to write file contents */ 9 | void writeFile(std::string_view filename, std::string_view contents); 10 | 11 | /** Check whether filename is safe (not contains ".." or "//") */ 12 | bool isSafePath(std::string_view filename); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/HashUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | /** Combine two hash value, same as boost implementation */ 7 | static inline std::size_t hashCombine(std::size_t a, std::size_t b) { 8 | // see this link for the magic number: 9 | // http://stackoverflow.com/questions/4948780 10 | return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2)); 11 | } 12 | 13 | /** Provide a generic hash function for tuple types (include pair) */ 14 | template class Hash = std::hash, 16 | std::enable_if_t<(std::tuple_size_v >= 0), int> = 0> 17 | class hash { 18 | private: 19 | template 20 | static std::size_t calculate(const Tuple& value) { 21 | auto hash = Hash>()(std::get(value)); 22 | if constexpr (Index == 0) { 23 | return hash; 24 | } else { 25 | return hashCombine(hash, calculate(value)); 26 | } 27 | } 28 | 29 | public: 30 | std::size_t operator()(const Tuple& value) const { 31 | if constexpr (std::tuple_size_v == 0) { 32 | return 0; 33 | } else { 34 | return calculate - 1>(value); 35 | } 36 | } 37 | }; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/HttpUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "./SharedString.hpp" 4 | 5 | namespace cpv { 6 | /** 7 | * Encode string for use in url, 8 | * may return original string if not changed. 9 | * Notice: space will not replace to '+'. 10 | */ 11 | SharedString urlEncode(SharedString&& str); 12 | 13 | /** 14 | * Decode string from url parts, 15 | * may return original string if not changed. 16 | * Notice: 17 | * it will ignore format errors. 18 | * '+' will replace to space for compatibility. 19 | * unicode characters such as "%u4e00" are unsupported. 20 | */ 21 | SharedString urlDecode(SharedString&& str); 22 | 23 | /** 24 | * Encode string for use in html, 25 | * may return original string if not changed. 26 | * Notice: 27 | * characters except '<', '>', '&', '"', '\'' are keep as is, 28 | * please use "Content-Type: text/html; charset=utf-8" to support unicode characters. 29 | */ 30 | SharedString htmlEncode(SharedString&& str); 31 | 32 | /** 33 | * Decode string from html content, 34 | * may return original string if not changed. 35 | * Notice: 36 | * it will ignore format errors. 37 | * unicode characters such as "一" and "𝕫" are supported. 38 | */ 39 | SharedString htmlDecode(SharedString&& str); 40 | 41 | /** Get mime type of file path (path can be extension only) */ 42 | SharedString getMimeType(std::string_view path); 43 | } 44 | 45 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/Macros.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Branch prediction hints 4 | #define CPV_LIKELY(expr) __builtin_expect(!!(expr), 1) 5 | #define CPV_UNLIKELY(expr) __builtin_expect(!!(expr), 0) 6 | 7 | // convenient macro to tell where is the line that includes this code 8 | // since __func__, __FUNC__, __PRETTY_FUNCTION isn't macro so a helper function is required 9 | #define CPV_CODEINFO cpv::CodeInfo(cpv::joinString("", \ 10 | "[", __FILE__, ":", __LINE__, ":", std::string_view(__PRETTY_FUNCTION__), "]")) 11 | 12 | // Attributes 13 | #if defined(NDEBUG) 14 | #define CPV_INLINE [[gnu::always_inline, gnu::hot]] 15 | #define CPV_HOT [[gnu::hot]] 16 | #else 17 | #define CPV_INLINE 18 | #define CPV_HOT 19 | #endif 20 | 21 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/NetworkUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | /** 7 | * Parse socket listen address. 8 | * Supported format: 9 | * - "ip:port", e.g. "0.0.0.0:80" 10 | * - ":port", e.g. ":80" 11 | * Hostname is unsupported. 12 | */ 13 | seastar::socket_address parseListenAddress(std::string_view address); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /include/CPVFramework/Utility/UUIDUtils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "./SharedString.hpp" 6 | 7 | namespace cpv { 8 | using UUIDDataType = std::pair; 9 | static const std::uint8_t EmptyUUIDVersion = 0; 10 | static const std::uint8_t TimeUUIDVersion = 1; 11 | static const std::uint8_t RandomUUIDVersion = 4; 12 | 13 | /** Set the uuid by it's string representation */ 14 | UUIDDataType strToUUID(std::string_view str); 15 | 16 | /** Get the string representation of uuid */ 17 | SharedString uuidToStr(const UUIDDataType& uuid); 18 | 19 | /** Make a empty uuid */ 20 | UUIDDataType makeEmptyUUID(); 21 | 22 | /** Make a version 4 (random) uuid */ 23 | UUIDDataType makeRandomUUID(); 24 | 25 | /** Make a version 1 (date-time and MAC address) uuid */ 26 | UUIDDataType makeTimeUUID(); 27 | 28 | /** Make a minimum version 1 uuid of the specific time point, useful for search query */ 29 | UUIDDataType makeMinTimeUUID(const std::chrono::system_clock::time_point& timePoint); 30 | 31 | /** Make a maximum version 1 uuid of the specific time point, useful for search query */ 32 | UUIDDataType makeMaxTimeUUID(const std::chrono::system_clock::time_point& timePoint); 33 | 34 | /** Extrat version from uuid */ 35 | std::uint8_t getVersionFromUUID(const UUIDDataType& uuid); 36 | 37 | /** Extract time stamp from verion 1 uuid */ 38 | std::chrono::system_clock::time_point getTimeFromUUID(const UUIDDataType& uuid); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/Application/ApplicationState.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** Enum descriptions of ApplicationState */ 5 | const std::vector>& 6 | EnumDescriptions::get() { 7 | static std::vector> staticNames({ 8 | { ApplicationState::StartInitialize, "StartInitialize" }, 9 | { ApplicationState::RegisterBasicServices, "RegisterBasicServices" }, 10 | { ApplicationState::BeforeCallCustomInitializeFunctions, "BeforeCallCustomInitializeFunctions" }, 11 | { ApplicationState::CallingCustomIntializeFunctions, "CallingCustomIntializeFunctions" }, 12 | { ApplicationState::AfterCustomInitializeFunctionsCalled, "AfterCustomInitializeFunctionsCalled" }, 13 | { ApplicationState::RegisterHeadServices, "RegisterHeadServices" }, 14 | { ApplicationState::RegisterServices, "RegisterServices" }, 15 | { ApplicationState::RegisterTailServices, "RegisterTailServices" }, 16 | { ApplicationState::PatchServices, "PatchServices" }, 17 | { ApplicationState::AfterServicesRegistered, "AfterServicesRegistered" }, 18 | { ApplicationState::AfterInitialized, "AfterInitialized" }, 19 | { ApplicationState::BeforeStart, "BeforeStart" }, 20 | { ApplicationState::Starting, "Starting" }, 21 | { ApplicationState::AfterStarted, "AfterStarted" }, 22 | { ApplicationState::BeforeTemporaryStop, "BeforeTemporaryStop" }, 23 | { ApplicationState::TemporaryStopping, "TemporaryStopping" }, 24 | { ApplicationState::AfterTemporaryStopped, "AfterTemporaryStopped" }, 25 | { ApplicationState::BeforeStop, "BeforeStop" }, 26 | { ApplicationState::Stopping, "Stopping" }, 27 | { ApplicationState::AfterStopped, "AfterStopped" } 28 | }); 29 | return staticNames; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/Application/Modules/HttpServerModule.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | /** Get the configuration */ 7 | HttpServerConfiguration& HttpServerModule::getConfig() & { 8 | return config_; 9 | } 10 | 11 | /** Set the request handler for page not found (usually be the last handler) */ 12 | void HttpServerModule::set404Handler( 13 | const seastar::shared_ptr& handler) { 14 | http404Handler_ = handler; 15 | } 16 | 17 | /** Set the request handler for exception (usually be the first handler) */ 18 | void HttpServerModule::set500Handler( 19 | const seastar::shared_ptr& handler) { 20 | http500Handler_ = handler; 21 | } 22 | 23 | /** Add custom handler between 404 and 500 handler */ 24 | void HttpServerModule::addCustomHandler( 25 | const seastar::shared_ptr& handler) { 26 | customHandlers_.emplace_back(handler); 27 | } 28 | 29 | /** Do some work for given application state */ 30 | seastar::future<> HttpServerModule::handle( 31 | Container& container, ApplicationState state) { 32 | if (state == ApplicationState::RegisterHeadServices) { 33 | if (http500Handler_) { 34 | container.add(http500Handler_); 35 | } else { 36 | container.add< 37 | seastar::shared_ptr, 38 | seastar::shared_ptr>( 39 | ServiceLifetime::Persistent); 40 | } 41 | } else if (state == ApplicationState::RegisterServices) { 42 | container.add(config_); 43 | for (auto& customHandler : customHandlers_) { 44 | container.add(customHandler); 45 | } 46 | } else if (state == ApplicationState::RegisterTailServices) { 47 | if (http404Handler_) { 48 | container.add(http404Handler_); 49 | } else { 50 | container.add< 51 | seastar::shared_ptr, 52 | seastar::shared_ptr>( 53 | ServiceLifetime::Persistent); 54 | } 55 | } else if (state == ApplicationState::AfterServicesRegistered) { 56 | server_ = HttpServer(container); 57 | } else if (state == ApplicationState::Starting) { 58 | return server_.value().start(); 59 | } else if (state == ApplicationState::Stopping) { 60 | return server_.value().stop(); 61 | } 62 | return seastar::make_ready_future<>(); 63 | } 64 | 65 | /** Constructor */ 66 | HttpServerModule::HttpServerModule() : 67 | config_(), 68 | http404Handler_(), 69 | http500Handler_(), 70 | customHandlers_(), 71 | server_() { 72 | config_.setListenAddresses({ "127.0.0.1:8000" }); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/Application/Modules/HttpServerRoutingModule.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** Do some work for given application state */ 5 | seastar::future<> HttpServerRoutingModule::handle(Container& container, ApplicationState state) { 6 | if (state == ApplicationState::RegisterServices) { 7 | container.add>(routingHandler_); 8 | container.add>(routingHandler_); 9 | } 10 | return seastar::make_ready_future<>(); 11 | } 12 | 13 | /** Constructor */ 14 | HttpServerRoutingModule::HttpServerRoutingModule() : 15 | routingHandler_(seastar::make_shared()) { } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/Application/Modules/LoggingModule.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** Set custom logger */ 5 | void LoggingModule::setLogger(const seastar::shared_ptr& logger) { 6 | logger_ = logger; 7 | } 8 | 9 | /** Do some work for given application state */ 10 | seastar::future<> LoggingModule::handle(Container& container, ApplicationState state) { 11 | if (state == ApplicationState::RegisterServices) { 12 | container.add>(logger_); 13 | } 14 | return seastar::make_ready_future<>(); 15 | } 16 | 17 | /** Constructor */ 18 | LoggingModule::LoggingModule() : 19 | logger_(Logger::createConsole(LogLevel::Notice)) { } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | project (CPVFramework) 3 | 4 | include(FindPkgConfig) 5 | include(GNUInstallDirs) 6 | 7 | # add target and source files 8 | FILE(GLOB_RECURSE Files ./*.cpp) 9 | FILE(GLOB_RECURSE PublicHeaders ../include/*.hpp) 10 | FILE(GLOB_RECURSE InternalHeaders ../src/*.hpp) 11 | add_library(${PROJECT_NAME} SHARED ${Files} ${PublicHeaders} ${InternalHeaders}) 12 | 13 | # find dependencies 14 | find_package(PkgConfig REQUIRED) 15 | pkg_check_modules(SEASTAR REQUIRED seastar) 16 | pkg_check_modules(SEASTAR_DEBUG REQUIRED seastar-debug) 17 | 18 | # set compile options 19 | set(CMAKE_VERBOSE_MAKEFILE TRUE) 20 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) 21 | target_include_directories(${PROJECT_NAME} PRIVATE ../include) 22 | target_compile_options(${PROJECT_NAME} PRIVATE 23 | -Wall -Wextra 24 | -Wno-unused-variable -Wno-unused-function 25 | -ftls-model=initial-exec -fPIC -fvisibility=default) 26 | target_link_libraries(${PROJECT_NAME} PRIVATE) 27 | 28 | # set compile options dependent on build type 29 | if (CMAKE_BUILD_TYPE MATCHES Release OR 30 | CMAKE_BUILD_TYPE MATCHES RelWithDebInfo OR 31 | CMAKE_BUILD_TYPE MATCHES MinSizeRel) 32 | target_compile_options(${PROJECT_NAME} PRIVATE 33 | ${SEASTAR_CFLAGS} -O3) 34 | target_link_libraries(${PROJECT_NAME} PRIVATE 35 | ${SEASTAR_LDFLAGS} -flto -fuse-ld=gold) 36 | elseif (CMAKE_BUILD_TYPE MATCHES Debug) 37 | target_compile_options(${PROJECT_NAME} PRIVATE 38 | ${SEASTAR_DEBUG_CFLAGS}) 39 | target_link_libraries(${PROJECT_NAME} PRIVATE 40 | ${SEASTAR_DEBUG_LDFLAGS}) 41 | endif() 42 | 43 | # generate pkgconfig file for cpvframework 44 | execute_process(COMMAND 45 | sh ${CMAKE_SOURCE_DIR}/../debian/scripts/get_version.sh 46 | OUTPUT_VARIABLE VERSION_NUMBER 47 | OUTPUT_STRIP_TRAILING_WHITESPACE) 48 | string(TIMESTAMP BUILD_DATE) 49 | set(PC_PATH "${CMAKE_CURRENT_BINARY_DIR}/cpvframework.pc") 50 | file(REMOVE ${PC_PATH}) 51 | file(APPEND ${PC_PATH} "Name: CPVFramework\n") 52 | file(APPEND ${PC_PATH} "URL: https://github.com/cpv-project/cpv-framework\n") 53 | file(APPEND ${PC_PATH} "Description: C++ web framework, build date: ${BUILD_DATE}\n") 54 | file(APPEND ${PC_PATH} "Version: ${VERSION_NUMBER}\n") 55 | file(APPEND ${PC_PATH} "Cflags:\n") 56 | file(APPEND ${PC_PATH} "Libs: -lCPVFramework\n") 57 | 58 | # add install rules 59 | install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) 60 | install(DIRECTORY ../include/CPVFramework DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 61 | install(FILES ${PC_PATH} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 62 | install(FILES ../LICENSE DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/licenses/CPVFramework) 63 | 64 | # add predefined macros 65 | add_definitions(-DCPV_FRAMEWORK_VERSION_NUMBER="${VERSION_NUMBER}") 66 | 67 | -------------------------------------------------------------------------------- /src/Container/Container.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** Members of Container */ 5 | class ContainerData { 6 | public: 7 | /** { type: [ descriptor... ] } */ 8 | std::unordered_map descriptorsMap; 9 | /** Built-in service storage for service with ServiceLifetime::StoragePersistent */ 10 | ServiceStorage storage; 11 | 12 | ContainerData() : descriptorsMap(), storage() { } 13 | }; 14 | 15 | /** Constructor **/ 16 | Container::Container() : 17 | data_(seastar::make_shared()) { } 18 | 19 | /** Constructor for null container */ 20 | Container::Container(std::nullptr_t) : 21 | data_(nullptr) { } 22 | 23 | /** Associate a descriptor to given service type */ 24 | void Container::addDescriptor( 25 | const std::type_index& serviceType, ServiceDescriptorPtr&& serviceDescriptor) { 26 | auto& descriptors = getOrCreateEmptyDescriptors(serviceType); 27 | descriptors->emplace_back(std::move(serviceDescriptor)); 28 | } 29 | 30 | /** Get all descriptors associated to given service type, return null pointer if not registered */ 31 | const ServiceDescriptorCollection& Container::getDescriptors( 32 | const std::type_index& serviceType) const& { 33 | static thread_local ServiceDescriptorCollection empty; 34 | auto it = data_->descriptorsMap.find(serviceType); 35 | if (it != data_->descriptorsMap.end()) { 36 | return it->second; 37 | } 38 | return empty; 39 | } 40 | 41 | /** Get all descriptors associated to given service type, return empty list if not registered */ 42 | ServiceDescriptorCollection& Container::getOrCreateEmptyDescriptors( 43 | const std::type_index& serviceType) & { 44 | auto it = data_->descriptorsMap.find(serviceType); 45 | if (it == data_->descriptorsMap.end()) { 46 | it = data_->descriptorsMap.emplace(serviceType, 47 | seastar::make_shared()).first; 48 | } 49 | return it->second; 50 | } 51 | 52 | /** Get built-in service storage */ 53 | ServiceStorage& Container::getBuiltinStorage() const& { 54 | return data_->storage; 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/Container/ServiceStorage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** Get the service instance with associated key, may return empty object */ 5 | std::any ServiceStorage::get(std::uintptr_t key) const { 6 | auto it = instances_.find(key); 7 | if (CPV_UNLIKELY(it == instances_.end())) { 8 | return std::any(); 9 | } 10 | return it->second; 11 | } 12 | 13 | /** Set the service instance with associated key */ 14 | void ServiceStorage::set(std::uintptr_t key, std::any&& value) { 15 | instances_.insert_or_assign(key, std::move(value)); 16 | } 17 | 18 | /** Clear all instances store in this storage */ 19 | void ServiceStorage::clear() { 20 | instances_.clear(); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/Exceptions/Exception.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace cpv { 8 | namespace { 9 | /** Build full exception message with stack trace information */ 10 | std::string buildExceptionMessage(std::string&& codeInfoStr, std::string&& message) { 11 | static thread_local std::array frames; 12 | // append code location and original message 13 | std::string fullMessage; 14 | fullMessage.append(codeInfoStr).append(" ").append(message).append("\n"); 15 | // append backtrace information 16 | fullMessage.append("backtrace:\n"); 17 | int size = ::backtrace(frames.data(), frames.size()); 18 | if (size < 0 || static_cast(size) >= frames.size()) { 19 | fullMessage.append(" call backtrace failed\n"); 20 | return fullMessage; 21 | } 22 | std::unique_ptr> symbols( 23 | ::backtrace_symbols(frames.data(), size), std::free); 24 | if (symbols == nullptr) { 25 | fullMessage.append(" call backtrace_symbols failed\n"); 26 | return fullMessage; 27 | } 28 | for (int i = 0; i < size; ++i) { 29 | fullMessage.append(" ").append(symbols[i]).append("\n"); 30 | } 31 | return fullMessage; 32 | } 33 | } 34 | 35 | /** Constructor */ 36 | Exception::Exception(std::string&& codeInfoStr, std::string&& message) : 37 | std::runtime_error(buildExceptionMessage(std::move(codeInfoStr), std::move(message))) { } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/Http/HttpForm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | /** Static empty vector for getMany */ 7 | const HttpForm::ValuesType HttpForm::Empty; 8 | 9 | /** Parse url encoded form body */ 10 | void HttpForm::parseUrlEncoded(const SharedString& body) { 11 | const char* mark = body.begin(); 12 | const char* ptr = mark; 13 | const char* end = body.end(); 14 | SharedString formKey; 15 | for (; ptr < end; ++ptr) { 16 | const char c = *ptr; 17 | if (c == '=') { 18 | // end of key, start of value 19 | formKey = urlDecode(body.share( 20 | { mark, static_cast(ptr - mark) })); 21 | mark = ptr + 1; 22 | } else if (c == '&') { 23 | // end of value 24 | add(std::move(formKey), urlDecode(body.share( 25 | { mark, static_cast(ptr - mark) }))); 26 | mark = ptr + 1; 27 | } 28 | } 29 | if (ptr > mark || !formKey.empty()) { 30 | // end of value 31 | add(std::move(formKey), urlDecode(body.share( 32 | { mark, static_cast(ptr - mark) }))); 33 | } 34 | } 35 | 36 | /** Apend url encoded form body to packet */ 37 | void HttpForm::buildUrlEncoded(Packet& packet) { 38 | auto& fragments = packet.getOrConvertToMultiple(); 39 | fragments.reserveAddition(formParameters_.size() * 4); 40 | bool isFirst = true; 41 | for (const auto& parameter : formParameters_) { 42 | for (const auto& value : parameter.second) { 43 | if (isFirst) { 44 | isFirst = false; 45 | } else { 46 | fragments.append(constants::Ampersand); 47 | } 48 | fragments.append(urlEncode(parameter.first.share())); 49 | fragments.append(constants::EqualsSign); 50 | fragments.append(urlEncode(value.share())); 51 | } 52 | } 53 | } 54 | 55 | /** Constructor */ 56 | HttpForm::HttpForm() : formParameters_() { } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/Http/HttpRequestCookies.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace cpv { 5 | /** Get cookie value for given key, return empty string if key not exists */ 6 | SharedString HttpRequestCookies::get(const SharedString& key) const { 7 | auto it = cookies_.find(key); 8 | return it != cookies_.end() ? it->second.share() : SharedString(); 9 | } 10 | 11 | /** Get all cookies */ 12 | const HttpRequestCookies::CookiesType& HttpRequestCookies::getAll() const& { 13 | return cookies_; 14 | } 15 | 16 | /** Parse the value from Cookie header */ 17 | void HttpRequestCookies::parse(const SharedString& cookies) { 18 | // examples: 19 | // key 20 | // key=value 21 | // key=value; other-key=other-value; other-key-only 22 | const char* mark = cookies.begin(); 23 | const char* ptr = mark; 24 | const char* end = cookies.end(); 25 | SharedString key; 26 | SharedString value; 27 | for (; ptr < end; ++ptr) { 28 | const char c = *ptr; 29 | if (c == '=') { 30 | key = cookies.share(trimString( 31 | { mark, static_cast(ptr - mark) })); 32 | mark = ptr + 1; 33 | } else if (c == ';') { 34 | value = cookies.share(trimString( 35 | { mark, static_cast(ptr - mark) })); 36 | mark = ptr + 1; 37 | if (!key.empty()) { 38 | cookies_.insert_or_assign(std::move(key), std::move(value)); 39 | } else if (!value.empty()) { 40 | cookies_.insert_or_assign(std::move(value), ""); 41 | } 42 | key = {}; 43 | value = {}; 44 | } 45 | } 46 | if (mark < ptr) { 47 | value = cookies.share(trimString( 48 | { mark, static_cast(ptr - mark) })); 49 | if (!key.empty()) { 50 | cookies_.insert_or_assign(std::move(key), std::move(value)); 51 | } else if (!value.empty()) { 52 | cookies_.insert_or_assign(std::move(value), ""); 53 | } 54 | } 55 | } 56 | 57 | /** Clear all parsed cookies */ 58 | void HttpRequestCookies::clear() { 59 | cookies_.clear(); 60 | } 61 | 62 | /** Constructor */ 63 | HttpRequestCookies::HttpRequestCookies() : 64 | cookies_() { } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/Http/HttpRequestExtensions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | -------------------------------------------------------------------------------- /src/Http/HttpResponseExtensions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace cpv::extensions { 7 | /** Reply 302 Found with given location to http response */ 8 | seastar::future<> redirectTo(HttpResponse& response, SharedString&& location) { 9 | response.setStatusCode(constants::_302); 10 | response.setStatusMessage(constants::Found); 11 | response.setHeader(constants::Location, std::move(location)); 12 | return seastar::make_ready_future<>(); 13 | } 14 | 15 | /** Reply 301 Moved Permanently with give location to http response */ 16 | seastar::future<> redirectToPermanently(HttpResponse& response, SharedString&& location) { 17 | response.setStatusCode(constants::_301); 18 | response.setStatusMessage(constants::MovedPermanently); 19 | response.setHeader(constants::Location, std::move(location)); 20 | return seastar::make_ready_future<>(); 21 | } 22 | 23 | /** Add or replace cookie on client side */ 24 | void setCookie( 25 | HttpResponse& response, 26 | const SharedString& key, 27 | const SharedString& value, 28 | const SharedString& path, 29 | const SharedString& domain, 30 | std::optional expires, 31 | bool httpOnly, 32 | bool secure, 33 | const SharedString& sameSite) { 34 | // complete field example: 35 | // Set-Cookie: {}={}; Path={}; Domain={}; Expires={}; HttpOnly; Secure; SameSite={} 36 | SharedStringBuilder builder( 37 | key.size() + value.size() + 38 | path.size() + domain.size() + sameSite.size() + 39 | HttpHeaderTimeStringSize + 57); 40 | builder.append(key); 41 | if (!value.empty()) { 42 | builder.append("=").append(value); 43 | } 44 | if (!path.empty()) { 45 | builder.append("; Path=").append(path); 46 | } 47 | if (!domain.empty()) { 48 | builder.append("; Domain=").append(domain); 49 | } 50 | if (expires.has_value()) { 51 | builder.append("; Expires=").append(formatTimeForHttpHeader(*expires)); 52 | } 53 | if (httpOnly) { 54 | builder.append("; HttpOnly"); 55 | } 56 | if (secure) { 57 | builder.append("; Secure"); 58 | } 59 | if (!sameSite.empty()) { 60 | builder.append("; SameSite=").append(sameSite); 61 | } 62 | response.getHeaders().addAdditionHeader(constants::SetCookie, builder.build()); 63 | } 64 | 65 | /** Remove cookie on client side */ 66 | void removeCookie( 67 | HttpResponse& response, 68 | const SharedString& key, 69 | const SharedString& path, 70 | const SharedString& domain) { 71 | setCookie(response, key, "", path, domain, 0); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/HttpServer/Connections/Http11ServerConnectionRequestStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./Http11ServerConnectionRequestStream.hpp" 3 | #include "./Http11ServerConnection.hpp" 4 | 5 | namespace cpv { 6 | /** The storage of Http11ServerConnectionRequestStream */ 7 | template <> 8 | thread_local ReusableStorageType 9 | ReusableStorageInstance; 10 | 11 | /** Read data from stream */ 12 | seastar::future Http11ServerConnectionRequestStream::read() { 13 | if (connection_->replyLoopData_.requestBodyConsumed) { 14 | return seastar::make_ready_future(InputStreamReadResult()); 15 | } 16 | // optimize for fast path 17 | // because normal path won't make the result future available immediately 18 | auto handleBodyEntry = [this](auto bodyEntry) { 19 | if (CPV_UNLIKELY(bodyEntry.id != connection_->replyLoopData_.requestId)) { 20 | return seastar::make_exception_future( 21 | LogicException(CPV_CODEINFO, "request id not matched, body belongs to request", 22 | bodyEntry.id, "but processing request is", connection_->replyLoopData_.requestId)); 23 | } 24 | if (bodyEntry.isEnd) { 25 | connection_->replyLoopData_.requestBodyConsumed = true; 26 | } 27 | return seastar::make_ready_future(InputStreamReadResult( 28 | std::move(bodyEntry.buffer), bodyEntry.isEnd)); 29 | }; 30 | if (!connection_->requestBodyQueue_.empty()) { 31 | return handleBodyEntry(connection_->requestBodyQueue_.pop()); 32 | } 33 | // enter normal path 34 | return connection_->requestBodyQueue_.pop_eventually().then(handleBodyEntry); 35 | } 36 | 37 | /** Get the hint of total size of stream */ 38 | std::optional Http11ServerConnectionRequestStream::sizeHint() const { 39 | // malicious client may send a smaller content-length to cause buffer overflow, 40 | // but this is just a hint. 41 | std::size_t contentLength = connection_->parser_.content_length; 42 | if (CPV_LIKELY(contentLength > 0 && 43 | contentLength != std::numeric_limits::max())) { 44 | return contentLength; 45 | } else { 46 | return { }; 47 | } 48 | } 49 | 50 | /** For Reusable<> */ 51 | void Http11ServerConnectionRequestStream::freeResources() { 52 | connection_ = nullptr; 53 | } 54 | 55 | /** For Reusable<> */ 56 | void Http11ServerConnectionRequestStream::reset(Http11ServerConnection* connection) { 57 | connection_ = connection; 58 | } 59 | 60 | /** Constructor */ 61 | Http11ServerConnectionRequestStream::Http11ServerConnectionRequestStream() : 62 | connection_() { } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/HttpServer/Connections/Http11ServerConnectionRequestStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace cpv { 7 | /** Declare types */ 8 | class Http11ServerConnection; 9 | 10 | /** The input stream for http 1.0/1.1 request */ 11 | class Http11ServerConnectionRequestStream : public InputStreamBase { 12 | public: 13 | /** Read data from stream */ 14 | seastar::future read() override; 15 | 16 | /** Get the hint of total size of stream */ 17 | std::optional sizeHint() const override; 18 | 19 | /** For Reusable<> */ 20 | void freeResources(); 21 | 22 | /** For Reusable<> */ 23 | void reset(Http11ServerConnection* connection); 24 | 25 | /** Constructor */ 26 | Http11ServerConnectionRequestStream(); 27 | 28 | private: 29 | // the lifetime of stream is rely on the connection 30 | Http11ServerConnection* connection_; 31 | }; 32 | 33 | /** Increase free list size */ 34 | template <> 35 | const constexpr std::size_t ReusableStorageCapacity< 36 | Http11ServerConnectionRequestStream> = 28232; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/HttpServer/Connections/Http11ServerConnectionResponseStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./Http11ServerConnectionResponseStream.hpp" 3 | #include "./Http11ServerConnection.hpp" 4 | 5 | namespace cpv { 6 | /** The storage of Http11ServerConnectionRequestStream */ 7 | template <> 8 | thread_local ReusableStorageType 9 | ReusableStorageInstance; 10 | 11 | /** Write data to stream */ 12 | seastar::future<> Http11ServerConnectionResponseStream::write(Packet&& data) { 13 | // reset detect timeout flag 14 | connection_->resetDetectTimeoutFlag(); 15 | if (CPV_UNLIKELY(connection_->replyLoopData_.responseHeadersAppended)) { 16 | // headers are sent, just send data 17 | connection_->replyLoopData_.responseWrittenBytes += data.size(); 18 | return connection_->socket_.out() << std::move(data); 19 | } else { 20 | // send headers and data in single packet (it's very important to performance) 21 | Packet merged(connection_->getResponseHeadersFragmentsCount() + data.segments()); 22 | connection_->appendResponseHeaders(merged); 23 | connection_->replyLoopData_.responseWrittenBytes += data.size(); 24 | merged.append(std::move(data)); 25 | return connection_->socket_.out() << std::move(merged); 26 | } 27 | } 28 | 29 | /** For Reusable<> */ 30 | void Http11ServerConnectionResponseStream::freeResources() { 31 | connection_ = nullptr; 32 | } 33 | 34 | /** For Reusable<> */ 35 | void Http11ServerConnectionResponseStream::reset(Http11ServerConnection* connection) { 36 | connection_ = connection; 37 | } 38 | 39 | /** Constructor */ 40 | Http11ServerConnectionResponseStream::Http11ServerConnectionResponseStream() : 41 | connection_() { } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/HttpServer/Connections/Http11ServerConnectionResponseStream.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace cpv { 7 | /** Declare types */ 8 | class Http11ServerConnection; 9 | 10 | /** The output stream for http 1.0/1.1 response */ 11 | class Http11ServerConnectionResponseStream : public OutputStreamBase { 12 | public: 13 | /** 14 | * Write data to stream. 15 | * This function didn't handle chunked data encoding, 16 | * (for performance, add size and crlf to packet may trigger unnecessary allocation) 17 | * chunked data should be encoded from writer. 18 | */ 19 | seastar::future<> write(Packet&& data) override; 20 | 21 | /** For Reusable<> */ 22 | void freeResources(); 23 | 24 | /** For Reusable<> */ 25 | void reset(Http11ServerConnection* connection); 26 | 27 | /** Constructor */ 28 | Http11ServerConnectionResponseStream(); 29 | 30 | private: 31 | // the lifetime of stream is rely on the connection 32 | Http11ServerConnection* connection_; 33 | }; 34 | 35 | /** Increase free list size */ 36 | template <> 37 | const constexpr std::size_t ReusableStorageCapacity< 38 | Http11ServerConnectionResponseStream> = 28232; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/HttpServer/Connections/HttpServerConnectionBase.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cpv { 4 | /** Interface of http server connection */ 5 | class HttpServerConnectionBase { 6 | public: 7 | /** Virtual destructor */ 8 | virtual ~HttpServerConnectionBase() = default; 9 | 10 | /** Stop the connection immediately */ 11 | virtual seastar::future<> stop() = 0; 12 | 13 | /** Invoke when timeout is detected from HttpServer's timer */ 14 | virtual void onTimeout() = 0; 15 | 16 | /** Invoke when some progress made from connection */ 17 | void resetDetectTimeoutFlag() { detectTimeoutFlag_ = false; } 18 | 19 | private: 20 | friend class HttpServer; 21 | 22 | // used to detect connection timeout (apply for all operations) 23 | // false -> (after first tick) true -> (after second tick) invoke on_timeout 24 | // the connection should set this value to false after each time progress made 25 | bool detectTimeoutFlag_ = false; 26 | }; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/HttpServer/Handlers/HttpServerRequest404Handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace cpv { 5 | /** Return 404 not found */ 6 | seastar::future<> HttpServerRequest404Handler::handle( 7 | HttpContext& context, 8 | HttpServerRequestHandlerIterator) const { 9 | auto& response = context.getResponse(); 10 | return extensions::reply(response, constants::NotFound, 11 | constants::TextPlainUtf8, constants::_404, constants::NotFound); 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/HttpServer/Handlers/HttpServerRequest500Handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | namespace { 7 | seastar::future<> reply500Response( 8 | HttpContext& context, 9 | std::exception_ptr ex, 10 | const seastar::shared_ptr& logger) { 11 | auto& response = context.getResponse(); 12 | // generate a time uuid as error id 13 | SharedString uuidStr = uuidToStr(makeTimeUUID()); 14 | // log error 15 | logger->log(LogLevel::Error, "Http server request error, ID:", uuidStr, "\n", ex); 16 | // build response content 17 | Packet p; 18 | p.append(constants::InternalServerError).append("\nID: ").append(std::move(uuidStr)); 19 | // reply with 500 (headers may already write to client, 20 | // in this case the error message will append to content and the behavior is undefined, 21 | // most of the time user can see it in network tab of developer tool) 22 | return extensions::reply(response, std::move(p), 23 | constants::TextPlainUtf8, constants::_500, constants::InternalServerError); 24 | } 25 | } 26 | 27 | /** Return 500 internal server error */ 28 | seastar::future<> HttpServerRequest500Handler::handle( 29 | HttpContext& context, 30 | HttpServerRequestHandlerIterator next) const { 31 | // expand futurize_apply manually to reduce overhead 32 | try { 33 | auto f = (*next)->handle(context, next + 1); 34 | if (f.available() && !f.failed()) { 35 | return f; 36 | } 37 | return f.handle_exception([&context, this] (std::exception_ptr ex) { 38 | return reply500Response(context, ex, logger_); 39 | }); 40 | } catch (...) { 41 | return reply500Response(context, std::current_exception(), logger_); 42 | } 43 | } 44 | 45 | /** Constructor */ 46 | HttpServerRequest500Handler::HttpServerRequest500Handler( 47 | seastar::shared_ptr logger) : 48 | logger_(std::move(logger)) { } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/HttpServer/Handlers/HttpServerRequestRealLastHandler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./HttpServerRequestRealLastHandler.hpp" 3 | 4 | namespace cpv { 5 | /** Return exception future */ 6 | seastar::future<> HttpServerRequestRealLastHandler::handle( 7 | cpv::HttpContext&, 8 | cpv::HttpServerRequestHandlerIterator) const { 9 | return seastar::make_exception_future<>(LogicException(CPV_CODEINFO, 10 | "The last handler in handler list should not invoke next handler")); 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/HttpServer/Handlers/HttpServerRequestRealLastHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace cpv { 5 | /** The real last handler append from internal, it should never be invoked */ 6 | class HttpServerRequestRealLastHandler : public HttpServerRequestHandlerBase { 7 | public: 8 | /** Return exception future */ 9 | seastar::future<> handle( 10 | cpv::HttpContext&, 11 | cpv::HttpServerRequestHandlerIterator) const override; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/HttpServer/HttpServerSharedData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "./Connections/HttpServerConnectionBase.hpp" 10 | 11 | namespace cpv { 12 | /** Wrap http server connections to make it support weak reference */ 13 | class HttpServerConnectionsWrapper : 14 | public seastar::weakly_referencable { 15 | public: 16 | /** Constructor */ 17 | HttpServerConnectionsWrapper(); 18 | 19 | public: 20 | /** Store all active connections, connections will remove self from it when close */ 21 | std::unordered_set> value; 22 | }; 23 | 24 | /** Data shared between HttpServer and HttpServerConnections */ 25 | class HttpServerSharedData { 26 | public: 27 | /** Constructor */ 28 | HttpServerSharedData( 29 | const Container& containerVal, 30 | seastar::weak_ptr&& connectionsWrapperVal); 31 | 32 | public: 33 | const Container container; 34 | const HttpServerConfiguration configuration; 35 | const seastar::shared_ptr logger; 36 | const HttpServerRequestHandlerCollection handlers; 37 | const seastar::weak_ptr connectionsWrapper; 38 | 39 | public: 40 | /** Metric targets */ 41 | struct { 42 | /** The total number of connections accepted */ 43 | std::uint64_t total_connections = 0; 44 | /** The current number of open connections */ 45 | std::uint64_t current_connections = 0; 46 | /** The total number of http request received */ 47 | std::uint64_t request_received = 0; 48 | /** The total number of http request handled successfully */ 49 | std::uint64_t request_handled = 0; 50 | /** The total number of errors while handling http request */ 51 | std::uint64_t request_errors = 0; 52 | /** The total number of exception occurs while receving http request */ 53 | std::uint64_t request_receive_exception_occurs = 0; 54 | /** The total number of exception occurs while replying http request */ 55 | std::uint64_t request_reply_exception_occurs = 0; 56 | /** The total number of request timeout errors */ 57 | std::uint64_t request_timeout_errors = 0; 58 | /** The total number of initial size limitation errors */ 59 | std::uint64_t request_initial_size_errors = 0; 60 | /** The total number of invalid format errors */ 61 | std::uint64_t request_invalid_format_errors = 0; 62 | } metricData; 63 | 64 | private: 65 | seastar::metrics::metric_groups metricGroups_; 66 | }; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/Logging/ConsoleLogger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./ConsoleLogger.hpp" 3 | 4 | namespace cpv { 5 | namespace { 6 | /** Colorize log output */ 7 | static const char* getColorCodePrefix(LogLevel logLevel) { 8 | if (logLevel <= LogLevel::Error) { 9 | return "\e[31m"; // red 10 | } else if (logLevel <= LogLevel::Warning) { 11 | return "\e[33m"; // yellow 12 | } else if (logLevel<= LogLevel::Notice) { 13 | return "\e[32m"; // green 14 | } else if (logLevel >= LogLevel::Debug) { 15 | return "\e[90m"; // gray 16 | } else { 17 | return ""; 18 | } 19 | } 20 | 21 | /** Reset default color */ 22 | static const char* getColorCodeSuffix() { 23 | return "\e[0m"; 24 | } 25 | } 26 | 27 | /** The implmentation of log, writes to console */ 28 | void ConsoleLogger::logImpl(LogLevel logLevel, const std::string& message) { 29 | // build a single string first to avoid thread race 30 | if (logLevel <= LogLevel::Warning) { 31 | std::cerr << (getColorCodePrefix(logLevel) + message + getColorCodeSuffix()); 32 | } else { 33 | std::cout << (getColorCodePrefix(logLevel) + message + getColorCodeSuffix()); 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/Logging/ConsoleLogger.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** A logger writes messages to console */ 5 | class ConsoleLogger : public Logger { 6 | public: 7 | using Logger::Logger; 8 | 9 | protected: 10 | /** The implmentation of log, writes to console */ 11 | void logImpl(LogLevel logLevel, const std::string& message) override; 12 | }; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/Logging/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "./ConsoleLogger.hpp" 4 | #include "./NoopLogger.hpp" 5 | 6 | namespace cpv { 7 | const std::vector>& 8 | EnumDescriptions::get() { 9 | static std::vector> staticNames({ 10 | { LogLevel::Emergency, "Emergency" }, 11 | { LogLevel::Alert, "Alert" }, 12 | { LogLevel::Critical, "Critical" }, 13 | { LogLevel::Error, "Error" }, 14 | { LogLevel::Warning, "Warning" }, 15 | { LogLevel::Notice, "Notice" }, 16 | { LogLevel::Info, "Info" }, 17 | { LogLevel::Debug, "Debug" }, 18 | }); 19 | return staticNames; 20 | } 21 | 22 | /** Constructor */ 23 | Logger::Logger(LogLevel logLevel) : 24 | logLevel_(logLevel) { } 25 | 26 | /** Create a console logger */ 27 | seastar::shared_ptr Logger::createConsole(LogLevel logLevel) { 28 | return seastar::make_shared(logLevel); 29 | } 30 | 31 | /** Create a noop logger */ 32 | seastar::shared_ptr Logger::createNoop() { 33 | return seastar::make_shared(); 34 | } 35 | 36 | /** Get thread id for logging */ 37 | std::size_t Logger::getThreadId() { 38 | return seastar::engine().cpu_id(); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/Logging/NoopLogger.cpp: -------------------------------------------------------------------------------- 1 | #include "./NoopLogger.hpp" 2 | 3 | namespace cpv { 4 | /** Constructor */ 5 | NoopLogger::NoopLogger() : 6 | Logger(LogLevel::Emergency) { } 7 | 8 | /** The implmentation of log, do nothing */ 9 | void NoopLogger::logImpl(LogLevel, const std::string&) { } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/Logging/NoopLogger.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** A do-nothing logger */ 5 | class NoopLogger : public Logger { 6 | public: 7 | /** Constructor */ 8 | NoopLogger(); 9 | 10 | protected: 11 | /** The implmentation of log, do nothing */ 12 | void logImpl(LogLevel, const std::string&) override; 13 | }; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/Serialize/JsonSerializer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./JsonSerializer.JsonEncodeMapping.hpp" 3 | 4 | namespace cpv { 5 | /** Encode string for use in json */ 6 | SharedString jsonEncode(SharedString&& str) { 7 | std::size_t encodedSize = 0; 8 | for (char c : str) { 9 | encodedSize += JsonEncodeMapping[static_cast(c)].size(); 10 | } 11 | if (encodedSize == str.size()) { 12 | // no change, return original string 13 | return std::move(str); 14 | } 15 | SharedString buf(encodedSize); 16 | char* dst = buf.data(); 17 | for (char c : str) { 18 | auto mapped = JsonEncodeMapping[static_cast(c)]; 19 | if (CPV_LIKELY(mapped.size() == 1)) { 20 | *dst++ = c; 21 | } else { 22 | std::memcpy(dst, mapped.data(), mapped.size()); 23 | dst += mapped.size(); 24 | } 25 | } 26 | return buf; 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/Stream/InputStreamExtensions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace cpv::extensions { 6 | namespace { 7 | // avoid out-of-memory attack and handle overflow 8 | static const constexpr std::size_t MaxReservedCapacity = 1048576; 9 | } 10 | 11 | /** Read all data from stream and append to given string builder */ 12 | seastar::future<> readAll(InputStreamBase& stream, SharedStringBuilder& builder) { 13 | std::size_t sizeHint = stream.sizeHint().value_or(0); 14 | if (sizeHint > 0) { 15 | // reserve buffer if size hint of stream is available 16 | builder.reserve(std::min(builder.size() + sizeHint, MaxReservedCapacity)); 17 | } 18 | return seastar::repeat([&stream, &builder] { 19 | return stream.read().then([&builder] (auto&& result) { 20 | builder.append(result.data); 21 | return result.isEnd ? 22 | seastar::stop_iteration::yes : 23 | seastar::stop_iteration::no; 24 | }); 25 | }); 26 | } 27 | 28 | /** Read all data from stream and return it as string */ 29 | seastar::future readAll(InputStreamBase& stream) { 30 | // optimize for fast path 31 | auto f = stream.read(); 32 | if (f.available()) { 33 | if (CPV_LIKELY(!f.failed())) { 34 | InputStreamReadResult result = f.get0(); 35 | if (result.isEnd) { 36 | // fast path 37 | return seastar::make_ready_future(std::move(result.data)); 38 | } else { 39 | f = seastar::make_ready_future(std::move(result)); 40 | } 41 | } else { 42 | return seastar::make_exception_future(f.get_exception()); 43 | } 44 | } 45 | // enter normal path 46 | return f.then([&stream] (auto&& result) { 47 | if (result.isEnd) { 48 | // the first result is the last result 49 | return seastar::make_ready_future(std::move(result.data)); 50 | } 51 | SharedStringBuilder builder; 52 | std::size_t sizeHint = stream.sizeHint().value_or(0); 53 | if (sizeHint > 0) { 54 | // reserve buffer if size hint of stream is available 55 | builder.reserve(std::min(sizeHint - result.data.size(), MaxReservedCapacity)); 56 | } 57 | builder.append(result.data); 58 | return seastar::do_with(std::move(builder), [&stream] (auto& builder) { 59 | return seastar::repeat([&stream, &builder] { 60 | return stream.read().then([&builder] (auto&& result) { 61 | builder.append(result.data); 62 | return result.isEnd ? 63 | seastar::stop_iteration::yes : 64 | seastar::stop_iteration::no; 65 | }); 66 | }).then([&builder] { 67 | return builder.build(); 68 | }); 69 | }); 70 | }); 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/Stream/PacketInputStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace cpv { 5 | /** The storage of PacketInputStream */ 6 | template <> 7 | thread_local ReusableStorageType 8 | ReusableStorageInstance; 9 | 10 | /** Read data from stream */ 11 | seastar::future PacketInputStream::read() { 12 | if (auto ptr = packet_.getIfMultiple()) { 13 | while (index_ < ptr->fragments.size()) { 14 | auto& fragment = ptr->fragments[index_]; 15 | ++index_; 16 | if (fragment.size == 0) { 17 | continue; 18 | } 19 | return seastar::make_ready_future( 20 | InputStreamReadResult( 21 | SharedString(fragment.base, 22 | fragment.size, ptr->deleter.share()), 23 | index_ >= ptr->fragments.size())); 24 | } 25 | } else if (auto ptr = packet_.getIfSingle()) { 26 | if (index_ == 0) { 27 | index_ = 1; 28 | return seastar::make_ready_future( 29 | InputStreamReadResult( 30 | SharedString(ptr->fragment.base, 31 | ptr->fragment.size, ptr->deleter.share()), 32 | true)); 33 | } 34 | } 35 | return seastar::make_ready_future(); 36 | } 37 | 38 | /** Get the hint of total size of stream */ 39 | std::optional PacketInputStream::sizeHint() const { 40 | return sizeHint_; 41 | } 42 | 43 | /** For Reusable<> */ 44 | void PacketInputStream::freeResources() { 45 | packet_ = {}; 46 | } 47 | 48 | /** For Reusable<> */ 49 | void PacketInputStream::reset(Packet&& packet) { 50 | packet_ = std::move(packet); 51 | sizeHint_ = packet_.size(); 52 | index_ = 0; 53 | } 54 | 55 | /** Constructor */ 56 | PacketInputStream::PacketInputStream() : 57 | packet_(), 58 | sizeHint_(0), 59 | index_(0) { } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/Stream/PacketOutputStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | /** The storage of PacketOutputStream */ 7 | template <> 8 | thread_local ReusableStorageType 9 | ReusableStorageInstance; 10 | 11 | /** Write data to stream */ 12 | seastar::future<> PacketOutputStream::write(Packet&& data) { 13 | packet_->append(std::move(data)); 14 | return seastar::make_ready_future<>(); 15 | } 16 | 17 | /** For Reusable<> */ 18 | void PacketOutputStream::freeResources() { 19 | packet_ = {}; 20 | } 21 | 22 | /** For Reusable<> */ 23 | void PacketOutputStream::reset(const seastar::lw_shared_ptr& packet) { 24 | packet_ = packet; 25 | } 26 | 27 | /** Constructor */ 28 | PacketOutputStream::PacketOutputStream() : 29 | packet_() { } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/Stream/StringInputStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace cpv { 5 | /** The storage of StringInputStream */ 6 | template <> 7 | thread_local ReusableStorageType 8 | ReusableStorageInstance; 9 | 10 | /** Read data from stream */ 11 | seastar::future StringInputStream::read() { 12 | if (str_.empty()) { 13 | return seastar::make_ready_future(); 14 | } else { 15 | return seastar::make_ready_future( 16 | InputStreamReadResult(std::move(str_), true)); 17 | } 18 | } 19 | 20 | /** Get the hint of total size of stream */ 21 | std::optional StringInputStream::sizeHint() const { 22 | return sizeHint_; 23 | } 24 | 25 | /** For Reusable<> */ 26 | void StringInputStream::freeResources() { 27 | str_ = {}; 28 | } 29 | 30 | /** For Reusable<> */ 31 | void StringInputStream::reset(SharedString&& str) { 32 | str_ = std::move(str); 33 | sizeHint_ = str_.size(); 34 | } 35 | 36 | /** Constructor */ 37 | StringInputStream::StringInputStream() : 38 | str_(), 39 | sizeHint_(0) { } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/Stream/StringOutputStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace cpv { 6 | /** The storage of StringOutputStream */ 7 | template <> 8 | thread_local ReusableStorageType 9 | ReusableStorageInstance; 10 | 11 | /** Write data to stream */ 12 | seastar::future<> StringOutputStream::write(Packet&& data) { 13 | if (auto ptr = data.getIfSingle()) { 14 | auto& fragment = ptr->fragment; 15 | builder_->append({ fragment.base, fragment.size }); 16 | } else if (auto ptr = data.getIfMultiple()) { 17 | for (auto& fragment : ptr->fragments) { 18 | builder_->append({ fragment.base, fragment.size }); 19 | } 20 | } 21 | return seastar::make_ready_future<>(); 22 | } 23 | 24 | /** For Reusable<> */ 25 | void StringOutputStream::freeResources() { 26 | builder_ = {}; 27 | } 28 | 29 | /** For Reusable<> */ 30 | void StringOutputStream::reset( 31 | const seastar::lw_shared_ptr& builder) { 32 | builder_ = builder; 33 | } 34 | 35 | /** Constructor */ 36 | StringOutputStream::StringOutputStream() : 37 | builder_() { } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/Utility/ConstantStrings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv::constants { 4 | // numbers 5 | const std::array Integers(([] { 6 | std::array arr; 7 | for (std::size_t i = 0; i < arr.size(); ++i) { 8 | arr[i] = std::to_string(i); 9 | } 10 | return arr; 11 | })()); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/Utility/DateUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace cpv { 7 | /** Format time for http header */ 8 | bool formatTimeForHttpHeader(std::time_t time, char* buf, std::size_t size) { 9 | struct std::tm tmVal; 10 | ::gmtime_r(&time, &tmVal); 11 | std::size_t ret = std::strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", &tmVal); 12 | return ret != 0; 13 | } 14 | 15 | /** Format time for http header, returns a thread local static string */ 16 | std::string_view formatTimeForHttpHeader(std::time_t time) { 17 | static thread_local std::optional previousTime; 18 | static thread_local HttpHeaderTimeStringBufferType buf; 19 | if (CPV_UNLIKELY(!previousTime.has_value() || time != previousTime.value())) { 20 | previousTime = time; 21 | formatTimeForHttpHeader(time, buf.data(), buf.size()); 22 | } 23 | return std::string_view(buf.data(), HttpHeaderTimeStringSize); 24 | } 25 | 26 | /** Format now for http header, returns a thread local static string */ 27 | std::string_view formatNowForHttpHeader() { 28 | static thread_local std::optional previousTime; 29 | static thread_local HttpHeaderTimeStringBufferType buf; 30 | std::time_t time = std::time(nullptr); 31 | if (CPV_UNLIKELY(!previousTime.has_value() || time != previousTime.value())) { 32 | previousTime = time; 33 | formatTimeForHttpHeader(time, buf.data(), buf.size()); 34 | } 35 | return std::string_view(buf.data(), HttpHeaderTimeStringSize); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/Utility/FileUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cpv { 9 | /** A shortcut function to read file contents */ 10 | SharedString readFile(std::string_view filename) { 11 | std::ifstream file( 12 | std::string(filename), // replace to std::filesystem::u8path? 13 | std::ios::in | std::ios::binary); 14 | if (CPV_UNLIKELY(!file.is_open())) { 15 | throw FileSystemException(CPV_CODEINFO, 16 | "open file", filename, "failed:", std::strerror(errno)); 17 | } else { 18 | file.seekg(0, std::ios::end); 19 | std::size_t size = file.tellg(); 20 | SharedString str(size); 21 | file.seekg(0); 22 | file.read(str.data(), size); 23 | return str; 24 | } 25 | } 26 | 27 | /** A shortcut function to write file contents */ 28 | void writeFile(std::string_view filename, std::string_view contents) { 29 | std::ofstream file( 30 | std::string(filename), // replace to std::filesystem::u8path? 31 | std::ios::out | std::ios::binary); 32 | if (CPV_UNLIKELY(!file.is_open())) { 33 | throw FileSystemException(CPV_CODEINFO, 34 | "open file", filename, "failed:", std::strerror(errno)); 35 | } else { 36 | file.write(contents.data(), contents.size()); 37 | if (CPV_UNLIKELY(!file)) { 38 | throw FileSystemException(CPV_CODEINFO, 39 | "write file", filename, "failed:", std::strerror(errno)); 40 | } 41 | } 42 | } 43 | 44 | /** Check whether filename is safe (not contains ".." or "//") */ 45 | bool isSafePath(std::string_view filename) { 46 | char lastChar = 0; 47 | for (char c : filename) { 48 | if (CPV_UNLIKELY( 49 | c == '\r' || c == '\n' || c == '\x00' || 50 | (c == '.' && lastChar == '.') || 51 | (c == '/' && lastChar == '/') || 52 | (c == '\\' && lastChar == '\\'))) { 53 | return false; 54 | } 55 | lastChar = c; 56 | } 57 | return true; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/Utility/NetworkUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cpv { 9 | /** Parse socket listen address */ 10 | seastar::socket_address parseListenAddress(std::string_view address) { 11 | std::size_t index = address.find_first_of(':'); 12 | if (CPV_UNLIKELY(index == std::string::npos)) { 13 | throw FormatException(CPV_CODEINFO, "no ':' in listen address:", address); 14 | } 15 | seastar::net::inet_address inetAddress; 16 | try { 17 | inetAddress = seastar::net::inet_address( 18 | index > 0 ? seastar::sstring(address.data(), index) : "0.0.0.0"); 19 | } catch (const std::invalid_argument&) { 20 | throw FormatException(CPV_CODEINFO, "invalid listen ip address:", address); 21 | } 22 | if (CPV_UNLIKELY(inetAddress.in_family() != seastar::net::inet_address::family::INET)) { 23 | // seastar's socket_address only support ipv4 now, so throw an exception for ipv6 24 | throw NotImplementedException(CPV_CODEINFO, "ipv6 address is unsupported for now"); 25 | } 26 | char* endptr = nullptr; 27 | std::uint64_t port = std::strtoull(address.data() + index + 1, &endptr, 10); 28 | if (CPV_UNLIKELY(endptr != address.data() + address.size() || port < 1 || port > 0xffff)) { 29 | throw FormatException(CPV_CODEINFO, "invalid listen port:", address); 30 | } 31 | return seastar::socket_address(seastar::ipv4_addr(inetAddress, port)); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/Utility/StringUtils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace cpv { 4 | /** Compare two string case insensitive */ 5 | bool caseInsensitiveEquals(std::string_view a, std::string_view b) { 6 | std::size_t size = a.size(); 7 | if (size != b.size()) { 8 | return false; 9 | } 10 | for (std::size_t i = 0; i < size; ++i) { 11 | if (std::tolower(a[i]) != std::tolower(b[i])) { 12 | return false; 13 | } 14 | } 15 | return true; 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | project (CPVFrameworkTests) 3 | 4 | include(FindPkgConfig) 5 | 6 | # add subdirectory 7 | add_subdirectory(../src CPVFramework) 8 | add_subdirectory(${GTEST_SOURCE_DIR} GTest) 9 | 10 | # add target and source files 11 | FILE(GLOB_RECURSE Files ./*.cpp) 12 | FILE(GLOB_RECURSE PublicHeaders ../include/*.hpp) 13 | FILE(GLOB_RECURSE InternalHeaders ../src/*.hpp) 14 | add_executable(${PROJECT_NAME} ${Files} ${PublicHeaders} ${InternalHeaders}) 15 | 16 | # find dependencies 17 | find_package(PkgConfig REQUIRED) 18 | pkg_check_modules(SEASTAR REQUIRED seastar) 19 | pkg_check_modules(SEASTAR_DEBUG REQUIRED seastar-debug) 20 | 21 | # set compile options 22 | set(CMAKE_VERBOSE_MAKEFILE TRUE) 23 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) 24 | target_include_directories(${PROJECT_NAME} PRIVATE 25 | ../include ../src ./ ${GTEST_SOURCE_DIR}/include) 26 | target_compile_options(${PROJECT_NAME} PRIVATE 27 | -Wall -Wextra 28 | -Wno-unused-variable -Wno-unused-function) 29 | 30 | # set compile options dependent on build type 31 | if (CMAKE_BUILD_TYPE MATCHES Release OR 32 | CMAKE_BUILD_TYPE MATCHES RelWithDebInfo OR 33 | CMAKE_BUILD_TYPE MATCHES MinSizeRel) 34 | target_compile_options(${PROJECT_NAME} PRIVATE 35 | ${SEASTAR_CFLAGS}) 36 | target_link_libraries(${PROJECT_NAME} PRIVATE 37 | ${SEASTAR_LDFLAGS} gtest_main CPVFramework) 38 | elseif (CMAKE_BUILD_TYPE MATCHES Debug) 39 | target_compile_options(${PROJECT_NAME} PRIVATE 40 | ${SEASTAR_DEBUG_CFLAGS}) 41 | target_link_libraries(${PROJECT_NAME} PRIVATE 42 | asan ubsan ${SEASTAR_DEBUG_LDFLAGS} gtest_main CPVFramework) 43 | endif() 44 | 45 | # add predefined macros 46 | target_compile_definitions(${PROJECT_NAME} PRIVATE 47 | HTTP_SERVER_1_IP="${HTTP_SERVER_1_IP}" 48 | HTTP_SERVER_1_PORT=${HTTP_SERVER_1_PORT} 49 | HTTP_SERVER_2_IP="${HTTP_SERVER_2_IP}" 50 | HTTP_SERVER_2_PORT=${HTTP_SERVER_2_PORT}) 51 | 52 | -------------------------------------------------------------------------------- /tests/Cases/Allocators/TestStackAllocator.Vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace { 6 | template