├── .clang-format
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ └── CI.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── README.md
├── docs
├── README.md
├── assets
│ ├── project_banner.png
│ └── request-response-diagram.png
├── examples
│ ├── DIRECTORY_LISTING.md
│ ├── LOGGER.md
│ └── REDIRECTION.md
├── general
│ ├── CONTRIBUTING.md
│ ├── GETTING_STARTED.md
│ ├── MODULES.md
│ ├── STANDARDS.md
│ └── WHY_US.md
└── guides
│ ├── CONFIG.md
│ ├── IMPLEMENT_HANDLER.md
│ ├── IMPLEMENT_NETWORK.md
│ ├── IMPLEMENT_POSTPROCESSOR.md
│ ├── IMPLEMENT_PREPROCESSOR.md
│ ├── INSTALL_AND_BUILD.md
│ └── MODULES_101.md
├── examples
├── dylib
│ └── Module.cpp
└── modules
│ ├── compressor
│ └── CompressorModule.hpp
│ ├── decompressor
│ └── DecompressorModule.hpp
│ ├── directory-listing
│ └── DirectoryListingModule.hpp
│ ├── logger
│ └── LoggerModule.hpp
│ └── redirection
│ └── Module.hpp
├── include
└── ziapi
│ ├── Color.hpp
│ ├── Config.hpp
│ ├── Http.hpp
│ ├── HttpConstants.hpp
│ ├── Logger.hpp
│ ├── Module.hpp
│ └── Version.hpp
└── tests
├── Compressor.cpp
├── Config.cpp
├── Decompressor.cpp
├── Logger.cpp
├── Module.cpp
└── Version.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Google
2 | IndentWidth: 4
3 | AccessModifierOffset: -4
4 | BreakBeforeBraces: Custom
5 | BraceWrapping:
6 | AfterClass: false
7 | AfterFunction: true
8 | AfterControlStatement: false
9 | AllowShortIfStatementsOnASingleLine: Never
10 | ColumnLimit: 120
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: 'My Bug Report'
5 | labels: 'bug'
6 |
7 | ---
8 |
9 | ### Description
10 |
11 | Describe your issue in as much detail as possible here.
12 |
13 | ### Your environment
14 |
15 | * OS and version
16 | * branch that causes this issue
17 |
18 | ### Steps to reproduce
19 |
20 | * Tell us how to reproduce this issue
21 | * Where the issue is, if you know
22 | * Which commands triggered the issue, if any
23 |
24 | ### Expected behaviour
25 |
26 | Tell us what should happen
27 |
28 | ### Actual behaviour
29 |
30 | Tell us what happens instead
31 |
32 | ### Logs
33 |
34 | Please paste any logs here that demonstrate the issue, if they exist
35 |
36 | ### Proposed solution
37 |
38 | If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: 'My Feature'
5 | labels: 'enhancement'
6 |
7 | ---
8 |
9 | ### Description
10 |
11 | Describe your issue in as much detail as possible here.
12 |
13 | ### Is your feature request related to a problem? Please describe.
14 |
15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
16 |
17 | ### Describe the solution you'd like
18 |
19 | A clear and concise description of what you want to happen.
20 |
21 | ### Additional context
22 |
23 | Add any other context or screenshots about the feature request here.
24 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Please provide a detailed description of what was done in this PR.
4 |
5 | # Breaking changes
6 |
7 | Please complete this section if any breaking changes have been made, otherwise delete it.
8 |
9 | # Additional Changes
10 |
11 | # Checklist
12 |
13 | - [ ] I have assigned this PR to myself
14 | - [ ] I have added at least 1 reviewer
15 | - [ ] I have tested this code
16 | - [ ] I have added sufficient documentation and/or updated documentation to reflect my changes
17 |
18 | # Additional comments
19 |
20 | Please post additional comments in this section if you have them, otherwise delete it.
21 |
--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | defaults:
6 | run:
7 | shell: bash
8 |
9 | jobs:
10 | build_and_test:
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | os: [windows-latest, ubuntu-latest, macos-latest]
15 | include:
16 | - os: windows-latest
17 | bin: unit_tests.exe
18 | dir: ./Debug/
19 | - os: ubuntu-latest
20 | bin: unit_tests
21 | dir: ./
22 | - os: macos-latest
23 | bin: unit_tests
24 | dir: ./
25 |
26 | name: ${{ matrix.os }}
27 | runs-on: ${{ matrix.os }}
28 |
29 | steps:
30 | - name: Checkout repository
31 | uses: actions/checkout@v2
32 |
33 | - name: Checkout submodules
34 | run: git submodule update --init --recursive
35 |
36 | - name: Generate Tests Build File
37 | run: cmake . -B build -DUNIT_TESTS=ON
38 |
39 | - name: Build Unit Tests
40 | run: cmake --build build
41 |
42 | - name: Run Unit Tests
43 | run: ${{ matrix.dir }}${{ matrix.bin }}
44 |
45 | memory_check:
46 | name: memory check
47 | runs-on: ubuntu-latest
48 |
49 | steps:
50 | - uses: actions/checkout@v2
51 |
52 | - name: Update Packages
53 | run: sudo apt update
54 |
55 | - name: Install Valgrind
56 | run: sudo apt install -y valgrind
57 |
58 | - name: Checkout submodules
59 | run: git submodule update --init --recursive
60 |
61 | - name: Generate Tests Build File
62 | run: cmake . -B build_tests -DUNIT_TESTS=ON -DCMAKE_BUILD_TYPE=Debug
63 |
64 | - name: Build Unit Tests
65 | run: cmake --build build_tests
66 |
67 | - name: Run Unit Tests with Valgrind
68 | run: valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ./unit_tests
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Editor Files and Folders
2 |
3 | .idea/
4 | .vscode/
5 | .DS_Store
6 | *~
7 | \#*#
8 |
9 | # Build Files and Binaries
10 |
11 | *.log
12 | *.o
13 | *.so
14 | *.dll
15 | *.dylib
16 | cmake-build-*/
17 | *build/
18 | unit_tests
19 |
20 | # Documentation Generation Files
21 |
22 | doc/latex/
23 | doc/html/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "include/dylib"]
2 | path = include/dylib
3 | url = https://github.com/martin-olivier/dylib
4 | branch = v1.8.1
5 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.17)
2 |
3 | # The name of the CMake project
4 | project(ziapi)
5 |
6 | # The C++ standard you want to use for your project
7 | set(CMAKE_CXX_STANDARD 17)
8 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
9 |
10 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
11 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR})
12 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR})
13 |
14 | option(UNIT_TESTS "When sets to ON, build the unit tests" OFF)
15 |
16 | if(UNIT_TESTS)
17 |
18 | # Fetch GoogleTest
19 | find_package(googletest QUIET)
20 | include(FetchContent)
21 | FetchContent_Declare(
22 | googletest
23 | URL https://github.com/google/googletest/archive/refs/tags/release-1.11.0.zip
24 | )
25 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
26 | FetchContent_MakeAvailable(googletest)
27 |
28 | # The bin name for your unit tests
29 | set(BIN unit_tests)
30 |
31 | # The build flags
32 | if(UNIX)
33 | add_compile_options(-Wall -Wextra -Weffc++)
34 | elseif(WIN32)
35 | add_compile_options(/W4)
36 | endif()
37 |
38 | # The list of tests source files
39 | set(TEST_SOURCES
40 | tests/Logger.cpp
41 | tests/Compressor.cpp
42 | tests/Decompressor.cpp
43 | tests/Config.cpp
44 | tests/Module.cpp
45 | tests/Version.cpp
46 | )
47 |
48 | add_executable(${BIN}
49 | ${SOURCES}
50 | ${TEST_SOURCES}
51 | )
52 | enable_testing()
53 | include(GoogleTest)
54 | gtest_discover_tests(${BIN})
55 | target_link_libraries(${BIN} PRIVATE gtest_main)
56 |
57 | if(UNIX)
58 | target_link_libraries(${BIN} PRIVATE dl)
59 | endif()
60 |
61 | # The include path
62 | include_directories(
63 | ${PROJECT_SOURCE_DIR}/include
64 | ${PROJECT_SOURCE_DIR}/examples/modules
65 | ${PROJECT_SOURCE_DIR}/examples/utils
66 | )
67 |
68 | add_library(module SHARED examples/dylib/Module.cpp)
69 | set_target_properties(module PROPERTIES PREFIX "")
70 |
71 | add_dependencies(${BIN} module)
72 |
73 | endif()
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ZIAPI
2 |
3 | 
4 |
5 | [](https://github.com/martin-olivier/ZiAPI/releases/tag/v5.0.0)
6 | [](https://isocpp.org/)
7 | [](https://discord.gg/CzKv6dGXmf)
8 | [](https://github.com/martin-olivier/ZiAPI/actions/workflows/CI.yml)
9 |
10 | Welcome to the **ZIAPI** repository which contains the interfaces and concrete implementations that make up our Epitech Zia project API proposal.
11 |
12 | **ZIAPI** was elected as the city-wide API for the **Paris and Marseille Regions** in **2022**.
13 |
14 | [**ZIAPI Official Website**](https://ziapi.vercel.app)
15 | [**ZIAPI Official Discord**](https://discord.gg/CzKv6dGXmf)
16 |
17 | ## Documentation
18 |
19 | You can find the documentation for the **ZIAPI** in our [documentation section](docs/README.md).
20 |
21 | Here are the most useful doc pages for starting out:
22 | - [Getting started](docs/general/GETTING_STARTED.md)
23 | - [Install and build guide](docs/guides/INSTALL_AND_BUILD.md)
24 | - [Module interfaces](docs/general/MODULES.md)
25 |
26 | Here is a quick overview of how the request / response cycle is handled with the ZIAPI.
27 |
28 | 
29 |
30 | ## Usage
31 |
32 | ### Fetch ZiAPI using CMake
33 |
34 | Add the following content to your CMakeLists to fetch the API and include its header files in your project:
35 | ```cmake
36 | include(ExternalProject)
37 |
38 | ExternalProject_Add(
39 | ziapi
40 | GIT_REPOSITORY https://github.com/martin-olivier/ZiAPI.git
41 | GIT_TAG v5.0.0
42 | INSTALL_COMMAND ""
43 | TEST_COMMAND ""
44 | )
45 |
46 | add_dependencies(zia ziapi)
47 | ExternalProject_Get_Property(ziapi SOURCE_DIR)
48 | include_directories(${SOURCE_DIR}/include)
49 | ```
50 |
51 | > :bulb: Don't forget to link with `libdl` on unix if you use `dylib`:
52 | ```cmake
53 | if(UNIX)
54 | target_link_libraries(zia PRIVATE dl)
55 | endif()
56 | ```
57 |
58 | ## Contact
59 |
60 | Feel free to submit any issues on our GitHub repository or ask questions on our [Discord](https://discord.gg/CzKv6dGXmf)
61 |
62 | ## Authors
63 |
64 | - [Martin Olivier](https://github.com/martin-olivier)
65 | - [Diego Rojas](https://github.com/rojasdiegopro)
66 | - [Edouard Sengeissen](https://github.com/edouard-sn)
67 | - [Nicolas Allain](https://github.com/Nirasak)
68 | - [Romain Minguet](https://github.com/Romain-1)
69 | - [Allan Debeve](https://github.com/Gfaim)
70 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # ZIAPI Documentation
2 |
3 | Welcome to the main documentation page of the **ZIAPI**. The **ZIAPI** is a set of interfaces and concrete implementations to help you manage module implementation for the Zia project.
4 |
5 | This document lists all the documentation pages for the API separated by category.
6 |
7 | ## General Documentation
8 |
9 | General documentation pages will help you understand the general concepts of the ZIAPI better.
10 |
11 | - [Getting started](general/GETTING_STARTED.md)
12 | - [Why choose ZIAPI](general/WHY_US.md)
13 | - [Modules](general/MODULES.md)
14 | - [ZIAPI Standards](general/STANDARDS.md)
15 | - [Contributing](general/CONTRIBUTING.md)
16 |
17 | ## Guides
18 |
19 | Guides are step by step instructions on how to do pretty much anything with the ZIAPI.
20 |
21 | - [Install and build](guides/INSTALL_AND_BUILD.md)
22 | - [Module 101](guides/MODULES_101.md)
23 | - [The config object](guides/CONFIG.md)
24 | - [Implementing a handler module](guides/IMPLEMENT_HANDLER.md)
25 | - [Implementing a pre-processor module](guides/IMPLEMENT_PREPROCESSOR.md)
26 | - [Implementing a post-processor module](guides/IMPLEMENT_POSTPROCESSOR.md)
27 | - [Implementing a network module](guides/IMPLEMENT_NETWORK.md)
28 |
29 | ## Module Examples
30 |
31 | Module examples are code tutorials on how to build a variety of modules.
32 |
33 | - [Logger Module Tutorial](examples/LOGGER.md)
34 | - [Directory Listing Module Tutorial](examples/DIRECTORY_LISTING.md)
35 | - [Redirection Module Tutorial](examples/REDIRECTION.md)
36 |
--------------------------------------------------------------------------------
/docs/assets/project_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martin-olivier/ZiAPI/838e46d1674c3e2d67a2eda790bcc3f15ff98906/docs/assets/project_banner.png
--------------------------------------------------------------------------------
/docs/assets/request-response-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/martin-olivier/ZiAPI/838e46d1674c3e2d67a2eda790bcc3f15ff98906/docs/assets/request-response-diagram.png
--------------------------------------------------------------------------------
/docs/examples/DIRECTORY_LISTING.md:
--------------------------------------------------------------------------------
1 | # Directory Listing Module Tutorial
2 |
3 | Let's implement a simple directory listing module.
4 |
5 | ## Purpose
6 |
7 | This module will handle incoming http requests and serve the file contained in the request's target field.
8 |
9 | For example, for the following request:
10 | ```
11 | GET /index.html HTTP/1.1
12 | ```
13 |
14 | Our module will open the `index.html` and serve it back to the user.
15 |
16 | ## Tutorial
17 |
18 | First, let's implement the `IHandlerModule` interface. We give it a basic priority and we specify that it only handles GET requests.
19 |
20 | ```c++
21 | #include
22 |
23 | #include "ziapi/Module.hpp"
24 |
25 | class DirectoryListingModule : public ziapi::IHandlerModule {
26 | public:
27 | void Init(const ziapi::config::Node &cfg) override {}
28 |
29 | [[nodiscard]] ziapi::Version GetVersion() const noexcept override { return {4, 0, 0}; }
30 |
31 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept override { return {4, 0, 0}; }
32 |
33 | [[nodiscard]] const char *GetName() const noexcept override { return "DirectoryListing"; }
34 |
35 | [[nodiscard]] const char *GetDescription() const noexcept override { return "Give access to a filesystem over HTTP"; }
36 |
37 | [[nodiscard]] double GetHandlerPriority() const noexcept override
38 | {
39 | /// Our module doesn't have any specific priority requirements.
40 | return 0.5f;
41 | }
42 |
43 | [[nodiscard]] bool ShouldHandle(const ziapi::http::Context &ctx, const ziapi::http::Request &req) const override
44 | {
45 | /// We only want to handle GET requests.
46 | return req.method == ziapi::http::method::kGet;
47 | }
48 |
49 | void Handle(ziapi::http::Context &ctx, const ziapi::http::Request &req, ziapi::http::Response &res) override {}
50 | };
51 | ```
52 |
53 | First thing we want to do is make our module configurable. Imagine we want to serve a specific directory on our computer. Like let's say we want to make the contents of the `/var/www` directory available.
54 |
55 | Well, we can add the path to this directory as a variable of our config file and fetch it in the `Init()` function. We will store it in a `root_` member variable of our class.
56 |
57 | ```c++
58 | ...
59 |
60 | void Init(const ziapi::config::Node &cfg) override
61 | {
62 | /// In our config, we can specify which folder our module serves.
63 | root_ = cfg["modules"]["directoryListing"]["root"].AsString();
64 | }
65 |
66 | ...
67 | ```
68 |
69 | Let's now code the `Handle()` method. We first obtain the path of the file we want to serve, then we check if it's a directory or a file and we either send back the contents of the file or the contents of the directory using `std::filesystem`.
70 |
71 | ```c++
72 | ...
73 |
74 | void Handle(ziapi::http::Context &ctx, const ziapi::http::Request &req, ziapi::http::Response &res) override
75 | {
76 | auto filepath = std::filesystem::path(root_) / std::filesystem::path(req.target);
77 | std::error_code ec;
78 |
79 | res.status_code = ziapi::http::code::OK;
80 | res.reason = ziapi::http::reason::OK;
81 | if (std::filesystem::is_directory(filepath, ec)) {
82 | std::ostringstream ss;
83 | for (const auto &entry : std::filesystem::directory_iterator(filepath)) {
84 | ss << entry.path().string() << "\n";
85 | }
86 | res.body = ss.str();
87 | return;
88 | } else {
89 | std::ifstream file_stream(filepath.filename());
90 | std::ostringstream ss;
91 | ss << file_stream.rdbuf();
92 | res.body = ss.str();
93 | return;
94 | }
95 | res.status_code = ziapi::http::code::NOT_FOUND;
96 | res.reason = ziapi::http::reason::NOT_FOUND;
97 | }
98 |
99 | ...
100 | ```
101 |
102 | You can check the full source code for this example [here](/examples/modules/directory-listing/DirectoryListingModule.hpp).
103 |
--------------------------------------------------------------------------------
/docs/examples/LOGGER.md:
--------------------------------------------------------------------------------
1 | # Logger Module Tutorial
2 |
3 | Let's implement a simple logger module.
4 |
5 | ## Purpose
6 |
7 | This module will log every requests that goes through our API and their state.
8 |
9 | For example, for the following request:
10 | ```
11 | GET /users/notfound HTTP/1.1
12 | ```
13 |
14 | Our module will log the status code, the time the request took, the reason and the target, so we could have an output like:
15 | ```
16 | [X] 404: Not found exception (GET /users/notfound HTTP/1.1, 4.02s)
17 | ```
18 |
19 | ## Tutorial
20 |
21 | This module is going to be a little special. It's going to have **two types**, it's going to be a post-processor **and** a pre-processor. We'll use diamond heritage for this!
22 |
23 | Let's implement the `IPreProcessorModule` and the `IPostProcessorModule` interfaces.
24 | We want it to log every request, so we'll return `true` in `ShouldPreProcess` and `ShouldPostProcess`.
25 | We set the pre-processing priority to 0 so it is the first module called.
26 | We set the post-processing priority to 1 so it is also the last module called.
27 | ```cpp
28 |
29 | class LoggerModule : virtual public ziapi::IPreProcessorModule, public ziapi::IPostProcessorModule {
30 | public:
31 | void Init(const ziapi::config::Node &cfg) {}
32 |
33 | [[nodiscard]] ziapi::Version GetVersion() const noexcept override { return {1, 0}; }
34 |
35 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept override { return {1, 0}; }
36 |
37 | [[nodiscard]] const char *GetName() const noexcept override { return "LoggerModule"; }
38 |
39 | [[nodiscard]] const char *GetDescription() const noexcept override
40 | {
41 | return "Log all responses from HTTP requests";
42 | }
43 |
44 | [[nodiscard]] double GetPostProcessorPriority() const noexcept override { return 1; }
45 |
46 | [[nodiscard]] bool ShouldPostProcess(const ziapi::http::Context &ctx, const ziapi::http::Request &req, const ziapi::http::Response &res) const override
47 | {
48 | return true;
49 | }
50 |
51 | [[nodiscard]] double GetPreProcessorPriority() const noexcept override { return 0; }
52 |
53 | [[nodiscard]] bool ShouldPreProcess(const ziapi::http::Context &ctx, const ziapi::http::Request &req) const override
54 | {
55 | return true;
56 | }
57 |
58 | // ...
59 | }
60 | ```
61 | Then, first thing we want is to store the timestamp when the request is received so we can time it. We'll store that timestamp in the request context to fetch it later.
62 |
63 | ```cpp
64 | ...
65 |
66 | void PreProcess(ziapi::http::Context &ctx, ziapi::http::Request &req) override
67 | {
68 | ctx["timestamp"] = std::time(nullptr);
69 | // This example is deprecated. The `PostProcess` method has access to the request since version 5.0.0.
70 | ctx["target"] = req.target;
71 | ctx["method"] = req.method;
72 | }
73 |
74 | ...
75 | ```
76 | And now in the post-process we can just simply access our variables and use them :smile:
77 | ```cpp
78 | ...
79 |
80 | void PostProcess(ziapi::http::Context &ctx, const ziapi::http::Request &, ziapi::http::Response &res) override
81 | {
82 | std::stringstream ss;
83 |
84 | // Example: ` [X] 404: Not found (GET /test, 2.02s)`
85 | ss << std::to_string(res.status_code) << ": " << res.reason << " (" << std::any_cast(ctx["method"])
86 | << " " << std::any_cast(ctx["target"]) << ", " << std::setprecision(2)
87 | << difftime(std::time(nullptr), std::any_cast(ctx["timestamp"])) << "s)";
88 | if (res.status_code < 300) {
89 | ziapi::Logger::Info(ss.str());
90 | } else if (res.status_code < 400) {
91 | ziapi::Logger::Warning(ss.str());
92 | } else {
93 | ziapi::Logger::Error(ss.str());
94 | }
95 | }
96 |
97 | ...
98 | ```
99 | You can check the full source code for this example [here](/examples/modules/logger/LoggerModule.hpp).
100 |
--------------------------------------------------------------------------------
/docs/examples/REDIRECTION.md:
--------------------------------------------------------------------------------
1 | # Redirection Module Tutorial
2 |
3 | Let's implement a simple redirection module.
4 |
5 | ## Purpose
6 |
7 | This module will handle incoming http requests and send back a redirection response.
8 | For example we might want to redirect all requests to `google.com`.
9 |
10 | So for the following incoming request:
11 | ```
12 | GET /google HTTP/1.1
13 | ```
14 |
15 | We'll then get the following response:
16 | ```
17 | HTTP/1.1 301 Moved Permanently
18 | Location: www.google.com
19 | ```
20 |
21 | ## Tutorial
22 |
23 | First, let's implement the `IHandlerModule` interface base.
24 |
25 | ```c++
26 | #include "ziapi/Module.hpp"
27 |
28 | class PhpCgiModule : public ziapi::IHandlerModule {
29 | public:
30 | void Init(const ziapi::config::Node &cfg) override {}
31 |
32 | [[nodiscard]] Version GetVersion() const noexcept { return {4, 0, 0}; }
33 |
34 | [[nodiscard]] Version GetCompatibleApiVersion() const noexcept { return {4, 0, 0}; }
35 |
36 | [[nodiscard]] const char *GetName() const noexcept { return "Redirection Module"; }
37 |
38 | [[nodiscard]] const char *GetDescription() const noexcept { return "Redirects the request to another location."; }
39 |
40 | [[nodiscard]] double GetHandlerPriority() const noexcept {
41 | // Directions should be treated in priority
42 | return 0.9;
43 | }
44 |
45 | void Handle(ziapi::http::Context &ctx, const ziapi::http::Request &req, ziapi::http::Response &res) override {}
46 | };
47 | ```
48 |
49 | Let's give our module a configuration.
50 | Let's load from the config the route to which we will redirect requests. We'll store the value in a member variable of our module called `redirection_route_`.
51 |
52 | ```c++
53 | ...
54 |
55 | void Init(const Config &cfg)
56 | {
57 | /// We'll load from the configuration where to redirect to!
58 | redirection_route_ = cfg["modules"]["redirection"]["route"];
59 | }
60 |
61 | ...
62 | ```
63 |
64 | We want to redirect all requests so we just return `true` in our `ShouldHandle()`.
65 |
66 | ```cpp
67 | ...
68 |
69 | [[nodiscard]] bool ShouldHandle(const http::Context &, const http::Request &req) const
70 | {
71 | return true;
72 | }
73 |
74 | ...
75 | ```
76 |
77 | Let's now implement the `Handle()` method. We simply redirect each request to the `redirection_route_` by changing the `Location` header on the response. We also set the appropriate status code.
78 |
79 | ```c++
80 | ...
81 |
82 | void Handle(http::Context &, const http::Request &, http::Response &res)
83 | {
84 | res.headers[ziapi::http::header::kLocation] = redirection_route_;
85 | res.status_code = ziapi::http::code::kMovedPermanently;
86 | }
87 |
88 | ...
89 | ```
90 |
91 | You can check the full source code for this example [here](/examples/modules/redirection/Module.hpp).
92 |
--------------------------------------------------------------------------------
/docs/general/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to ZIAPI
2 |
3 | ## Issues
4 |
5 | Issues are more than welcome. When submitting an issue make you sure to select the right issue template for your need.
6 |
7 | ### Bug Reports
8 |
9 | Submit a bug report when an implemented feature isn't working properly (e.g. the CMake intergation refuses to build).
10 |
11 | ### Feature Requests
12 |
13 | If you feel like something is missing from the ZIAPI, like a new feature or a tweak to an existing one, submit a feature request describing your feature and some justification on why it should be implemented.
14 |
15 | ## Pull Requests
16 |
17 | Pull requests are more than welcome. Usually it's best to start by opening an issue and to follow it up with a pull request once everyone agrees on how to fix the bug / implement the feature.
18 |
19 | ## Conventions
20 |
21 | **Git Commits**
22 |
23 | We use the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for git commits.
24 |
25 | **Git Branches**
26 |
27 | We use the following syntax for naming git branches
28 |
29 | ```
30 | /-
31 |
32 | feat/billing-module-76
33 | fix/http-header-parser-167
34 | fix/module-loader-12
35 | ```
36 |
37 | Some branches like `main`, `dev`, `hotfix` do not follow that convention.
38 |
39 | **C++**
40 |
41 | We use the [Google C++ style guide](https://google.github.io/styleguide/cppguide.html).
42 |
43 | **Issues and Pull Requests**
44 |
45 | Make sure your pull request and issue names are human readable titles such as `Missing const qualifier on method` for an issue or `Support for HTTP 3` for a pull request.
46 |
--------------------------------------------------------------------------------
/docs/general/GETTING_STARTED.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | Welcome to the main documentation page of the **ZIAPI**. The **ZIAPI** is a set of interfaces and concrete implementations to help you manage module implementation for the Zia project.
4 |
5 | ## Introduction
6 |
7 | **ZIAPI** aims at making your life easier as a developper. It features different type of modules that you can implement to customize parts of your Zia project.
8 |
9 | Here is an overview of what you can do using the **ZIAPI** module interfaces:
10 | - Switch between HTTP versions 1.0, 1.1, 2 and 3
11 | - Change the underlying network implementation (UDP, TCP, Quic, ...)
12 | - Change the behaviour of your HTTP server (Directory Listing, PHP CGI)
13 | - Manage multiple sites and conditional module execution (e.g. only execute a module on a request if it is a `GET` request)
14 |
15 | ## Architecture
16 |
17 | To understand how the ZiAPI works, lets take a look at how requests are handled from start to finish.
18 |
19 | ### Request handling process
20 |
21 | The request / response cycle is handled in 5 steps. Before we go into detail about how each of the steps work let's look at a quick illustration to help us understand better.
22 |
23 | 
24 |
25 | Okay now, let's study each step in detail.
26 |
27 | #### Step 1. Receive
28 |
29 | Like any HTTP message, the request is first handled through the network layer (TCP sockets, etc...). This is where you'll setup your sockets and listeners, your hypothetical SSL / TLS encryption logic, your HTTP parser and any additional serialization / deserialization code. When a request arrives, the networking layer must **read it, decrypt it, parse it** and forward it to the next layer: the pre-processing layer!
30 |
31 | Check the module associated with this step [here](../guides/IMPLEMENT_NETWORK.md)
32 |
33 | #### Step 2. Pre-process
34 |
35 | Okay, so our request has been received and parsed by the network layer. Once it lands in the pre-processing layer, a pipeline of request pre-processing modules will be invoked. This is where you can route the request, log it, rewrite its URL, add / remove some headers, etc.
36 |
37 | Check the module associated with this step [here](../guides/IMPLEMENT_PREPROCESSOR.md)
38 |
39 | #### Step 3. Handle
40 |
41 | Once the whole pre-processing pipeline has been applied on the request, the corresponding handler for this request is invoked. A handler will generate the HTTP response for a request. So that might be the contents of a file (Directory Listing module), the output of a PHP script (PHP CGI) or some JSON output gathered from a database, you choose!
42 |
43 | Check the module associated with this step [here](../guides/IMPLEMENT_HANDLER.md)
44 |
45 | #### Step 4. Post-process
46 |
47 | Once the HTTP response has been generated by the handling layer, it is forwarded to the post-processing layer. This is where a pipeline of response post-processing modules will be invoked. This is where you can serialize the contents of your response body, log the response, add additional headers such as CORS, etc.
48 |
49 | Check the module associated with this step [here](../guides/IMPLEMENT_POSTPROCESSOR.md)
50 |
51 | #### Step 5. Send
52 |
53 | Once the post-processing pipeline has been applied on the response, it is forwarded back to the network layer to be sent back over the network. This is where you will serialize your HTTP message, and possibly encode it using TLS before writing it to your socket.
54 |
55 | Check the module associated with this step [here](../guides/IMPLEMENT_NETWORK.md)
56 |
57 | And boom! Request handled! Using 5 easy steps: **Receive**, **pre-process**, **handle**, **post process** and **send**.
58 |
59 | For more information checkout the [documentation entrypoint](../README.md).
60 |
--------------------------------------------------------------------------------
/docs/general/MODULES.md:
--------------------------------------------------------------------------------
1 | # Modules Documentation
2 |
3 | The ZIAPI features 4 different types of modules:
4 | - `INetworkModule`
5 | - `IPreProcessorModule`
6 | - `IHandlerModule`
7 | - `IPostProcessorModule`
8 |
9 | Each module is invoked at a different stage in the request lifecycle. If you remember from the [Getting started](GETTING_STARTED.md) page of the documentation, a request goes through 5 different steps when received by our HTTP server. Let's see which steps correspond to which module.
10 | - **1. Receive** step is handled by `INetworkModule`.
11 | - **2. Pre-process** step is handled by `IPreProcessorModule`.
12 | - **3. Handle** step is handled by `IHandlerModule`.
13 | - **4. Post-process** step is handled by `IPostProcessorModule`.
14 | - **5. Send** step is handled by `INetworkModule`.
15 |
16 | We'll see each of these modules types detail, but first, let's look at the `IModule` interface.
17 |
18 | ## The `IModule` interface
19 |
20 | Each **ZIAPI** module type inherits from `IModule`. It features very basic methods to make it easier to manage modules.
21 |
22 | ```c++
23 | virtual void Init(const Config &cfg) = 0;
24 |
25 | [[nodiscard]] virtual Version GetVersion() const noexcept = 0;
26 |
27 | [[nodiscard]] virtual Version GetCompatibleApiVersion() const noexcept = 0;
28 |
29 | [[nodiscard]] virtual const char *GetName() const noexcept = 0;
30 |
31 | [[nodiscard]] virtual const char *GetDescription() const noexcept = 0;
32 | ```
33 |
34 | There's not much to say about this interface. Every module must implement it so we can have access to its version, name, description...
35 |
36 | ## `INetworkModule`
37 |
38 | The `Run()` method starts the module, providing it with an output queue in which it shall push incoming requests and an input queue from which it should receive incoming responses and send them over the network. So basically, inside the run method, you should create your sockets, spin up your http parser and wait for incoming requests! This method is intended to run forever!
39 |
40 | ```c++
41 | virtual void Run(http::IRequestOutputQueue &requests, http::IResponseInputQueue &responses);
42 | ```
43 |
44 | The `Terminate()` method will be invoked upon reloading or termination of the server, it notifies the module that it needs to stops running altogether and release every resource it has created. So this is the time where you should **close** all your sockets and return the flow of control to the caller of your `Run` method.
45 |
46 | ```c++
47 | virtual void Terminate() = 0;
48 | ```
49 |
50 | ## `IPreProcessorModule`
51 |
52 | The `PreProcess()` is the method invoked on a request before the handler is called. You can use pre-processor to modify the request's data (add / remove headers, etc...)
53 |
54 | ```c++
55 | virtual void PreProcess(http::Context &ctx, http::Request &req) = 0;
56 | ```
57 |
58 | The `GetPreProcessorPriority()` method returns the priority of the module between zero and one. Pre-processors with a higher priority are executed first!
59 |
60 | ```c++
61 | [[nodiscard]] virtual double GetPreProcessorPriority() const noexcept = 0;
62 | ```
63 |
64 | The `ShouldPreProcess()` method returns `true` if this module's PreProcess method should be called on the request. For example you can choose that your module will only pre-process requests with a `Content-Type: application/json` header.
65 |
66 | ```c++
67 | [[nodiscard]] virtual bool ShouldPreProcess(const http::Context &ctx, const http::Request &req) const = 0;
68 | ```
69 |
70 | ## `IPostProcesserModule`
71 |
72 | The `PostProcess()` is the method invoked on a response after the handler is called. You can use post-processors to modify the response after it is generated (serializing the body, logging the response, etc...).
73 |
74 | ```c++
75 | virtual void PostProcess(http::Context &ctx, const http::Request &, http::Response &res) = 0;
76 | ```
77 |
78 | The `GetPostProcessorPriority()` method returns the priority of the module between zero and one. Post-processors with a higher priority are executed first!
79 |
80 | ```c++
81 | [[nodiscard]] virtual double GetPostProcessorPriority() const noexcept = 0;
82 | ```
83 |
84 | The `ShouldPostProcess()` method returns `true` if this module's PostProcess should be called on the response. For example you can choose that your module will only post-process responses with a status code over `399` to log them to the console!
85 |
86 | ```c++
87 | [[nodiscard]] virtual bool ShouldPostProcess(const http::Context &ctx, const http::Request &req, const http::Response &res) const = 0;
88 | ```
89 |
90 | ## `IHandlerModule`
91 |
92 | The `Handle()` method is invoked on a request in order to generate a response. It's like any typical web framework you can find: you get access to the incoming request and you're in charge of generating a response!
93 |
94 | ```c++
95 | virtual void Handle(http::Context &ctx, const http::Request &req, http::Response &res) = 0;
96 | ```
97 |
98 | The `GetHandlerPriority()` sets the module's priority of execution. If you have two handlers in your API which both want to handle a particular request, only the handler with the highest priority will be called.
99 |
100 | ```c++
101 | [[nodiscard]] virtual double GetHandlerPriority() const noexcept = 0;
102 | ```
103 |
104 | The `ShouldHandle()` is used to determin if your module **wants** to handle a specific request. For example, you may have a module which only handles `GET` requests, or requests with a specific header, etc...
105 |
106 | ```c++
107 | [[nodiscard]] virtual bool ShouldHandle(const http::Context &ctx, const http::Request &req) const = 0;
108 | ```
109 |
--------------------------------------------------------------------------------
/docs/general/STANDARDS.md:
--------------------------------------------------------------------------------
1 | # ZIAPI Standards
2 |
3 | A set of rules that define a standard implementation of a ZIAPI module to make it easier to share modules between groups.
4 |
5 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119).
6 |
7 | ## `Z0` - Module Documentation
8 |
9 | Each module **MUST** be shipped with a `README.md` providing basic documentation for the module.
10 |
11 | ## `Z1` - Module Configuration Documentation
12 |
13 | ZIAPI doesn't impose a norm for configuration files. However modules **MUST** be shipped with sufficient documentation regarding the configuration fields they expect.
14 |
15 | #### Example
16 |
17 | If the module `tls` depends on the following configuration:
18 |
19 | ```c++
20 | void Init(const ziapi::config::Node &cfg) override {
21 | auto enable_tls = cfg["modules"]["tls"]["enableTls"].AsBool();
22 | auto certificate_path = cfg["modules"]["tls"]["certficiatePath"].AsString();
23 | }
24 | ```
25 |
26 | Then the `tls` module's documentation should explicitly state that the module expects:
27 | - `modules.tls.enableTls` to be defined as a boolean
28 | - `modules.tls.certificatePath` to be defined as a string
29 |
30 | ## `Z2` - Configuration File Layout
31 |
32 | To limit friction when sharing modules, all groups **MAY** implement the following configuration file layout where:
33 | - The top level node is a `ziapi::config::Dict`
34 | - It contains a `modules` key which is a `ziapi::config::Dict` too.
35 | - Each module which requires a configuration fetches its configuration information from `modules.*`
36 |
37 | #### Example
38 |
39 | To illustrate this, let's look at a YAML configuration file example.
40 |
41 | Let's say we have two modules respectively named `tls` and `directoryListing`. By applying rule Z2 we get the following configuration.
42 |
43 | ```yaml
44 | modules:
45 | tls:
46 | foo: bar
47 | directoryListing:
48 | foo: bar
49 | ```
50 |
51 | Therefore it is **NOT RECOMMENDED** to depend on configuration fields outside of the `modules.` scope like so:
52 |
53 | ```c++
54 | void MyModule::Init(const ziapi::config::Node &cfg) override {
55 | /// Not C2 compliant, the module depends on a node outside of "modules.".
56 | auto enable_tls = cfg["enableTls"].AsBool();
57 | auto certificate_path = cfg["certficiatePath"].AsString();
58 | }
59 | ```
60 |
61 | ## `Z3` - Request/Response Context Documentation
62 |
63 | ZIAPI doesn't impose a norm for the request/response context. However, modules **MUST** be shipped with sufficient documentation regarding the context fields they write to and read from.
64 |
65 | Modules **SHOULD** try to populate the context with primitive types only or basic `std` types (Like `std::string`).
66 |
67 | #### Example
68 |
69 | If the `MyPreProcessor` module depends on the following context:
70 |
71 | ```c++
72 | void MyPreProcessor::PreProcess(http::Context &ctx, http::Request &req)
73 | {
74 | auto client_address = std::any_cast(ctx["client.socket.address"]);
75 | ctx["php_cgi.user.is_authenticated"] = (client_address == "127.0.0.1");
76 | }
77 | ```
78 |
79 | Then the `MyPreProcessor` module's documentation must explicitly state that it expects:
80 | - `ctx["client.socket.address"]` to be defined as a `std::string`
81 |
82 | The documentation must also state that the module plans to:
83 | - Write a boolean value inside `ctx["user.is_authenticated"]`
84 |
85 | ## `Z4` - Standard Request/Response Context Fields
86 |
87 | Each request/response is associated with a `ziapi::http::Context` to allow modules to communicate data.
88 |
89 | The following fields are standard fields and **SHOULD** be populated according to the following specification.
90 |
91 | | Key | Type | Description |
92 | |-------------------------|-----------------|----------------------------------------------------------------------------|
93 | | `client.socket.address` | `std::string` | The IP address of the client. May only be mutated by `INetworkModule`s |
94 | | `client.socket.port` | `std::uint16_t` | The port of the client's socket. May only be mutated by `INetworkModule`s |
95 |
--------------------------------------------------------------------------------
/docs/general/WHY_US.md:
--------------------------------------------------------------------------------
1 | # Why choose ZIAPI
2 |
3 | ## Modules
4 |
5 | First of all **ZIAPI** offers a great deal of simplicity. Modules are separated into clearly defined groups (**network**, **pre-processor**, **post-processor**, **network**) and feature a wide variety of use cases.
6 |
7 | Our best selling point is the long list of **example modules** that we have already implemented to help you understand our API better.
8 |
9 | Here is a list of non-hexaustive list of modules you can very easily implement using **ZIAPI**.
10 | - [Directory Listing](../examples/DIRECTORY_LISTING.md)
11 | - Authentication
12 | - Automatic MIME type detection
13 | - Routing
14 | - [Redirection](../examples/REDIRECTION.md)
15 | - Secure connection module
16 | - PHP CGI
17 | - SSL / TLS
18 | - HTTP Handler
19 | - Database
20 | - Benchmark
21 | - Dashboard (frequency of requests, CPU load)
22 | - [Logger](../examples/LOGGER.md)
23 | - Compression / decompression
24 |
25 | ## Documentation
26 |
27 | We have documented each and every one of our source files and added additional info on how to
28 | - [Build and compile the project](../guides/INSTALL_AND_BUILD.md)
29 | - Create modules (Numerous [code examples](../examples) of different common module and one [in-depth guide per module type](../guides))
30 | - [Contributing](CONTRIBUTING.md)
31 |
32 | ## Additional tools
33 |
34 | We've built an amazing [website](https://ziapi.vercel.app) for you to navigate the docs more easily.
35 |
36 | With the API, we also included our own Open-Source and unit-tested [dynamic library loader](https://github.com/martin-olivier/dylib) developped by [Martin Olivier](https://github.com/martin-olivier).
37 |
--------------------------------------------------------------------------------
/docs/guides/CONFIG.md:
--------------------------------------------------------------------------------
1 | # Managing your configuration
2 |
3 | In this guide we will learn how to use our configuration, to setup all of your modules easily.
4 |
5 | ## The `Node` object
6 |
7 | First, let's take a look at the definition of `ziapi::config::Node`.
8 |
9 | ```cpp
10 | using NodeVariant = std::variant;
11 |
12 | struct Node : public NodeVariant { /* ... */ }
13 | ```
14 |
15 | So here we can see that our `struct Node` inherits from a `std::variant` of a lot of types.
16 | The `std::variant` declines all the possible data types available in JSON, YAML, XML, etc.
17 |
18 | Let's look at a simple example:
19 |
20 | Imagine you have a configuration file of the following form:
21 |
22 | ```json
23 | {
24 | "sources": ["/bin/ls", 10, null, 10.5],
25 | "modules": {
26 | "directoryListing": {
27 | "root": "/var/www"
28 | }
29 | }
30 | }
31 | ```
32 |
33 | Or its YAML equivalent
34 |
35 | ```yaml
36 | sources:
37 | - "/bin/ls"
38 | - 10
39 | - null
40 | - 10.5
41 | modules:
42 | directoryListing:
43 | root: /var/www
44 | ```
45 |
46 | You could represent it using a `ziapi::config::Node` like so
47 |
48 | ```cpp
49 | using Node = ziapi::config::Node;
50 | using Dict = ziapi::config::Dict;
51 | using Array = ziapi::config::Array;
52 |
53 | Node obj(
54 | {
55 | {"sources", std::make_shared({
56 | std::make_shared("/bin/ls"),
57 | std::make_shared(10),
58 | std::make_shared(nullptr),
59 | std::make_shared(10.5)
60 | })
61 | },
62 | {"modules", std::make_shared({
63 | {"directoryListing", std::make_shared({
64 | {"root", std::make_shared("/var/www")}
65 | })}
66 | })}
67 | }
68 | );
69 |
70 | // Or using helper static methods
71 |
72 | auto obj = Node::MakeDict({
73 | {"sources", Node::MakeArray({ "/bin/ls", 10, nullptr, 10.5 })},
74 | {"modules", Node::MakeDict({
75 | {"directoryListing", Node::MakeDict({
76 | {"root", "/var/www"}
77 | })}
78 | })}
79 | })
80 | ```
81 | ### More informations about Dict and Array helper functions
82 | ```cpp
83 | // You can also give the helper methods a vector representing your array or dictionnary
84 | // The helper functions will automatically transform your contained Nodes into shared_ptr
85 |
86 | std::unordered_map dictionnary_vector;
87 | // ... Fill the dictionnary vector
88 | Node dictionnary_node = Node::MakeDict(dictionnary_vector)
89 |
90 | std::vector array_vector;
91 | // ... Fill the array vector
92 | Node array_node = Node::MakeArray(array_vector)
93 |
94 |
95 | // You can of course build them from the Node base types
96 | // In this case, you're responsible of giving the values as shared_ptr and converting the final variable as a Node
97 |
98 | Dict dictionnary;
99 | dictionnary["modules_count"] = std::make_shared(10);
100 | Node dictionnary_node(dictionnary);
101 |
102 | Array arr;
103 | arr.emplace_back(std::make_shared("/bin/ls"));
104 | Node array_node(arr);
105 | ```
106 |
107 | ## Data Types
108 |
109 | ### `Undefined`
110 |
111 | The `Undefined` data type represents an empty configuration, much like an empty file.
112 |
113 | ```cpp
114 | ziapi::config::Node(ziapi::config::Undefined{})
115 | ```
116 |
117 | ### `Null`
118 |
119 | The `Null` data type represents a boolean value. So the following JSON:
120 |
121 | ```json
122 | null
123 | ```
124 |
125 | Would translate to
126 |
127 | ```cpp
128 | ziapi::config::Node(nullptr)
129 | ```
130 |
131 | ### `bool`
132 |
133 | The `bool` data type represents a boolean value. So the following JSON:
134 |
135 | ```json
136 | true
137 | ```
138 |
139 | Would translate to
140 |
141 | ```cpp
142 | ziapi::config::Node(true)
143 | ```
144 |
145 | ### `int`
146 |
147 | The `int` data type represents an integer value. So the following JSON:
148 |
149 | ```json
150 | 5
151 | ```
152 |
153 | Would translate to
154 |
155 | ```cpp
156 | ziapi::config::Node(5)
157 | ```
158 |
159 | ### `double`
160 |
161 | The `double` data type represents a floating point value. So the following JSON:
162 |
163 | ```json
164 | 4.2
165 | ```
166 |
167 | Would translate to
168 |
169 | ```cpp
170 | ziapi::config::Node(4.2)
171 | ```
172 |
173 | ### `String`
174 |
175 | The `String` data type represents a string value. So the following JSON:
176 |
177 | ```json
178 | "Hello World!"
179 | ```
180 |
181 | Would translate to
182 |
183 | ```cpp
184 | ziapi::config::Node("Hello World!")
185 | ```
186 |
187 |
188 | ### `Array`
189 |
190 | The `Array` data type represents an array of values. So the following JSON:
191 |
192 | ```json
193 | ["Hello", "World", "!"]
194 | ```
195 |
196 | Would translate to
197 |
198 | ```cpp
199 | ziapi::config::Array arr({
200 | std::make_shared("Hello"),
201 | std::make_shared("World"),
202 | std::make_shared("!")
203 | });
204 |
205 | ziapi::config::Node arr_node(arr);
206 | ```
207 |
208 | Which is equivalent to
209 |
210 | ```c++
211 | auto array_node = ziapi::config::Node::MakeArray({ "Hello", "World", "!" });
212 | // vector can also be passed as a parameter
213 | ```
214 |
215 | ### `Dict`
216 |
217 | The `Dict` data type represents a dictionary of values. So the following JSON:
218 |
219 | ```json
220 | {
221 | "age": 20,
222 | "first_name": "Charlie",
223 | "last_name": "Chou",
224 | "is_sexy": true
225 | }
226 | ```
227 |
228 | Would translate to
229 |
230 | ```cpp
231 | ziapi::config::Dict dict({
232 | {"age", std::make_shared(20)},
233 | {"first_name", std::make_shared("Charlie")},
234 | {"last_name", std::make_shared("Chou")},
235 | {"is_sexy", std::make_shared(true)}
236 | })
237 |
238 | ziapi::config::Node node_dict(dict);
239 | ```
240 |
241 | Which is equivalent to
242 |
243 | ```c++
244 | auto node_dict = ziapi::config::Node::MakeDict({
245 | {"age", 20},
246 | {"first_name", "Charlie"},
247 | {"last_name", "Chou"},
248 | {"is_sexy", true},
249 | }) // vector can also be passed as a parameter
250 | ```
--------------------------------------------------------------------------------
/docs/guides/IMPLEMENT_HANDLER.md:
--------------------------------------------------------------------------------
1 | # Implement a Handler module
2 |
3 | In this guide we will learn how to implement a Handler module which is a type of module that allows us to generate an HTTP response from a request.
4 |
5 | ## `IHandlerModule` interface
6 |
7 | Let's look at the definition of the `IHandlerModule` interface.
8 |
9 | ```c++
10 | class IHandlerModule : public IModule {
11 | public:
12 | virtual void Handle(http::Request &req, http::Response &res) = 0;
13 |
14 | [[nodiscard]] virtual double GetHandlerPriority() const noexcept = 0;
15 |
16 | [[nodiscard]] virtual bool ShouldHandle(const http::Context &ctx, const http::Request &req) const = 0;
17 | };
18 | ```
19 |
20 | We have three methods to implement, let's go!
21 |
22 | > :exclamation: Don't forget to implement the `IModule` methods
23 |
24 | ## Create a handler
25 |
26 | Okay, let's create our own class which inherits from `IHandlerModule`.
27 |
28 | ```c++
29 | #include "ziapi/Module.hpp"
30 |
31 | class MyHandler : public ziapi::IHandlerModule {
32 | public:
33 | void Handle(http::Context &ctx, const http::Request &req, http::Response &res) override;
34 |
35 | [[nodiscard]] double GetHandlerPriority() const noexcept override;
36 |
37 | [[nodiscard]] bool ShouldHandle(const http::Context &ctx, const http::Request &req) const override;
38 | };
39 | ```
40 |
41 | Then, let's implement the `GetHandlerPriority()`. Our module doesn't have specific priority requirements so we'll put it at `0.5f`.
42 |
43 | ```c++
44 | [[nodiscard]] double MyHandler::GetHandlerPriority() const noexcept
45 | {
46 | return 0.5f;
47 | }
48 | ```
49 |
50 | Then, let's implement the `ShouldHandle()`. This method is invoked to know if our handler should be called for a specific request. We can return `true` if we want all requests to go through this handler but let's just say our handler only handles `GET` request for the sake of the example.
51 |
52 | ```c++
53 | [[nodiscard]] bool MyHandler::ShouldHandle(const http::Context &ctx, const http::Request &req) const
54 | {
55 | return req.method == http::method::kGet;
56 | }
57 | ```
58 |
59 | Great! Now our handler will be called on all GET requests! Now let's add the `Handle()`.
60 |
61 | ```c++
62 | void MyHandler::Handle(http::Context &ctx, const http::Request &req, http::Response &res)
63 | {
64 | res.status_code = 200;
65 | res.body = "Hello guys!";
66 | }
67 | ```
68 |
69 | And that's it! You now have a module which displays `"Hello guys!"` in your browser upon all `GET` requests.
70 |
--------------------------------------------------------------------------------
/docs/guides/IMPLEMENT_NETWORK.md:
--------------------------------------------------------------------------------
1 | # Implement a Network module
2 |
3 | In this guide we will learn how to implement a Network module which is a type of module that allows us to handle incoming requests and send back responses.
4 |
5 | ## `INetworkModule` interface
6 |
7 | Let's look at the definition of the `INetworkModule` interface.
8 |
9 | ```c++
10 | class INetworkModule {
11 | public:
12 | virtual void Run(http::IRequestOutputQueue &requests, http::IResponseInputQueue &responses) = 0;
13 |
14 | virtual void Terminate() = 0;
15 | };
16 | ```
17 |
18 | We have two methods to implement, let's go!
19 |
20 | ## Create a network module
21 |
22 | Okay, let's create our own class which inherits from `INetworkModule`.
23 |
24 | ```c++
25 | #include "ziapi/Module.hpp"
26 |
27 | class MyNetwork : public ziapi::INetworkModule {
28 | public:
29 | void Run(http::IRequestOutputQueue &requests, http::IResponseInputQueue &responses) override;
30 |
31 | void Terminate() override;
32 |
33 | private:
34 | Array clients;
35 | bool mustStop = false; // do a constructor, it's pseudo-code
36 | };
37 | ```
38 |
39 | First, let's implement the `Run()` method. It should contain the main loop on all the clients and handle incoming data, and / or send back responses back. It's pseudo-code, just to understand the purpose of the method.
40 |
41 | ```c++
42 | void MyNetwork::Run(http::IRequestOutputQueue &requests, http::IResponseInputQueue &responses)
43 | {
44 | Socket mainSocket = CreateSocket();
45 |
46 | while (true) {
47 | // Check for a new client
48 | if (mainSocket.HasNewConnection()) {
49 | clients.append(mainSocket.Accept());
50 | }
51 | if (mustStop) {
52 | mainSocket.destroy();
53 | return;
54 | }
55 | // For each client, read incoming data
56 | for (int i = 0; i < clients.Size(); ++i) {
57 | if (clients[i].HasData()) {
58 | requests.Push(clients[i].GetData());
59 | }
60 | }
61 | // For each response, send data
62 | for (int i = 0; i < responses.Size(); ++i) {
63 | auto response = responses.Pop();
64 |
65 | // send the response to the right client
66 | }
67 | sleep(1);
68 | }
69 | }
70 | ```
71 |
72 | To end, let's implement `Terminate`. It should stop the running loop and may be useful in order to join threads.
73 |
74 | ```c++
75 | void MyNetwork::Terminate()
76 | {
77 | shouldStop = true;
78 | // join threads if there is
79 | }
80 | ```
81 |
82 | The network module is now complete !
83 |
--------------------------------------------------------------------------------
/docs/guides/IMPLEMENT_POSTPROCESSOR.md:
--------------------------------------------------------------------------------
1 | # Implement a PostProcessor module
2 |
3 | In this guide we will learn how to implement a PostProcessor module which is a type of module that allows us to modify an HTTP response after it was handled.
4 |
5 | ## `IPostProcessorModule` interface
6 |
7 | Let's look at the definition of the `IPostProcessorModule` interface.
8 |
9 | ```c++
10 | class IPostProcessorModule : public IModule {
11 | public:
12 | virtual void PostProcess(http::Context &ctx, http::Response &res) = 0;
13 |
14 | [[nodiscard]] virtual double GetPostProcessorPriority() const noexcept = 0;
15 |
16 | [[nodiscard]] virtual bool ShouldPostProcess(const http::Context &ctx, const http::Request &req, const http::Response &res) const = 0;
17 | };
18 | ```
19 |
20 | We have three methods to implement, let's go!
21 |
22 | > :exclamation: Don't forget to implement the `IModule` methods
23 |
24 | ## Create a post-processor
25 |
26 | Okay, let's create our own class which inherits from `IPostProcessorModule`.
27 |
28 | ```c++
29 | #include "ziapi/Module.hpp"
30 |
31 | class MyPostProcessor : public ziapi::IPostProcessorModule {
32 | public:
33 | void PostProcess(http::Context &ctx, http::Response &res) override;
34 |
35 | [[nodiscard]] double GetPostProcessorPriority() const noexcept override;
36 |
37 | [[nodiscard]] bool ShouldPostProcess(const http::Context &ctx, const http::Request &req, const http::Response &res) const override;
38 | };
39 | ```
40 |
41 | Then, let's implement the `GetPostProcessorPriority()`. Our module doesn't have specific priority requirements so we'll put it at `0.5f`.
42 |
43 | ```c++
44 | [[nodiscard]] double MyPostProcessor::GetPostProcessorPriority() const noexcept
45 | {
46 | return 0.5f;
47 | }
48 | ```
49 |
50 | Then, let's implement the `ShouldPostProcess()`. This method is invoked to know if our post-processor should be called for a specific request. We can return `true` if we want all requests to go through this post-processor but let's just say our post-processor only handles `status code` inferior to 400 for the sake of the example.
51 |
52 | ```c++
53 | [[nodiscard]] bool ShouldPostProcess(const http::Context &ctx, const http::Request &req, const http::Response &res) const
54 | {
55 | return res.status_code < 400;
56 | }
57 | ```
58 |
59 | Great! Now our post-processor will be called on all responses with a status code lower than `400`! Now let's add the `PostProcess()`.
60 |
61 | ```c++
62 | void MyPostProcessor::PostProcess(http::Context &ctx, http::Response &res)
63 | {
64 | std::cout << "New " << res.status_code << " response!" << std::endl;
65 | }
66 | ```
67 |
68 | And that's it! You now have a module which logs all responses with a status code < `400`.
69 |
--------------------------------------------------------------------------------
/docs/guides/IMPLEMENT_PREPROCESSOR.md:
--------------------------------------------------------------------------------
1 | # Implement a PreProcessor module
2 |
3 | In this guide we will learn how to implement a PreProcessor module which is a type of module that allows us to modify an HTTP request before it is handled.
4 |
5 | ## `IPreProcessorModule` interface
6 |
7 | Let's look at the definition of the `IPreProcessorModule` interface.
8 |
9 | ```c++
10 | class IPreProcessorModule : public IModule {
11 | public:
12 | virtual void PreProcess(http::Context &ctx, http::Request &req) = 0;
13 |
14 | [[nodiscard]] virtual double GetPreProcessorPriority() const noexcept = 0;
15 |
16 | [[nodiscard]] virtual bool ShouldPreProcess(const http::Context &ctx, const http::Request &req) const = 0;
17 | };
18 | ```
19 |
20 | We have three methods to implement, let's go!
21 |
22 | > :exclamation: Don't forget to implement the `IModule` methods
23 |
24 | ## Create a pre-processor
25 |
26 | Okay, let's create our own class which inherits from `IPreProcessorModule`.
27 |
28 | ```c++
29 | #include "ziapi/Module.hpp"
30 |
31 | class MyPreProcessor : public ziapi::IPreProcessorModule {
32 | public:
33 | void PreProcess(http::Context &ctx, http::Request &req) override;
34 |
35 | [[nodiscard]] double GetPreProcessorPriority() const noexcept override;
36 |
37 | [[nodiscard]] bool ShouldPreProcess(const http::Context &ctx, const http::Request &req) const override;
38 | };
39 | ```
40 |
41 | Then, let's implement the `GetPreProcessorPriority()`. Our module doesn't have specific priority requirements so we'll put it at `0.5f`.
42 |
43 | ```c++
44 | [[nodiscard]] double MyPreProcessor::GetPreProcessorPriority() const noexcept
45 | {
46 | return 0.5f;
47 | }
48 | ```
49 |
50 | Then, let's implement the `ShouldPreProcess()`. This method is invoked to know if our pre-processor should be called for a specific request. We can return `true` if we want all requests to go through this pre-processor but let's just say our pre-processor only handles `GET` request for the sake of the example.
51 |
52 | ```c++
53 | [[nodiscard]] bool ShouldPreProcess(const http::Context &ctx, const http::Request &req) const
54 | {
55 | return req.method == http::method::kGet;
56 | }
57 | ```
58 |
59 | Great! Now our pre-processor will be called on all GET requests! Now let's add the `PreProcess()`.
60 |
61 | ```c++
62 | void MyPreProcessor::PreProcess(http::Context &ctx, http::Request &req)
63 | {
64 | req.method = http::method::kPost;
65 | }
66 | ```
67 |
68 | And that's it! You now have a module which transforms all `GET` requests to `POST` requests in order to completely break your API ^^.
69 |
--------------------------------------------------------------------------------
/docs/guides/INSTALL_AND_BUILD.md:
--------------------------------------------------------------------------------
1 | # Build and install guide
2 |
3 | Let's take a look at how to build the project.
4 |
5 | ## Fetch ZiAPI using CMake
6 |
7 | Add the following content to your `CMakeLists` to fetch the `ZiAPI` and include its header files in your project:
8 | ```cmake
9 | include(ExternalProject)
10 |
11 | ExternalProject_Add(
12 | ziapi
13 | GIT_REPOSITORY https://github.com/martin-olivier/ZiAPI.git
14 | GIT_TAG v5.0.0
15 | INSTALL_COMMAND ""
16 | TEST_COMMAND ""
17 | )
18 |
19 | add_dependencies(zia ziapi)
20 | ExternalProject_Get_Property(ziapi SOURCE_DIR)
21 | include_directories(${SOURCE_DIR}/include)
22 | ```
23 |
24 | > :bulb: Don't forget to link with `libdl` on unix if you use `dylib`:
25 | ```cmake
26 | if(UNIX)
27 | target_link_libraries(zia PRIVATE dl)
28 | endif()
29 | ```
30 |
31 | ## Build and run unit tests
32 |
33 | If you want to run unit tests on `ZiAPI`, do the following steps:
34 | - Clone the ZiAPI repository
35 | - Execute the following commands:
36 | ```
37 | cmake . -B build -DUNIT_TESTS=ON
38 | cmake --build build
39 | ./unit_tests
40 | ```
41 |
--------------------------------------------------------------------------------
/docs/guides/MODULES_101.md:
--------------------------------------------------------------------------------
1 | # Modules 101
2 |
3 | The goal of this guide is to build a module as a dynamic library and load it at runtime
4 |
5 | ## Documentation
6 |
7 | - [modules](https://github.com/martin-olivier/ZiAPI/blob/main/docs/general/MODULES.md)
8 | - [dylib](https://github.com/martin-olivier/dylib)
9 |
10 | ## Setup Build
11 |
12 | Here is the repo architecture of this example:
13 | ```
14 | .
15 | ├── CMakeLists.txt
16 | └── src
17 | ├── main.cpp
18 | └── module.cpp
19 | ```
20 |
21 | Let's create a `CMakeLists` to build our binary `zia` and our dynamic library `module`:
22 |
23 | ```cmake
24 | cmake_minimum_required(VERSION 3.17)
25 |
26 | # The name of the CMake project
27 | project(TestZia)
28 |
29 | # The C++ standard you want to use for your project
30 | set(CMAKE_CXX_STANDARD 17)
31 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
32 |
33 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
34 | set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_BINARY_DIR})
35 |
36 | add_executable(zia src/main.cpp)
37 |
38 | # Fetch ZiAPI
39 |
40 | include(ExternalProject)
41 |
42 | ExternalProject_Add(
43 | ziapi
44 | GIT_REPOSITORY https://github.com/martin-olivier/ZiAPI.git
45 | GIT_TAG v5.0.0
46 | INSTALL_COMMAND ""
47 | TEST_COMMAND ""
48 | )
49 |
50 | add_dependencies(zia ziapi)
51 | ExternalProject_Get_Property(ziapi SOURCE_DIR)
52 | include_directories(${SOURCE_DIR}/include)
53 |
54 | if(UNIX)
55 | target_link_libraries(zia PRIVATE dl)
56 | endif()
57 |
58 | # Build Our Dynamic Lib
59 |
60 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR})
61 |
62 | add_library(module SHARED src/module.cpp)
63 |
64 | # running the below CMake rule will allow you to remove the prefix `lib` for macOS and linux, ensuring
65 | # that the library shares the same name on all the different OS:
66 | # https://github.com/martin-olivier/dylib#remove-the-lib-prefix
67 | set_target_properties(module PROPERTIES PREFIX "")
68 |
69 | add_dependencies(module ziapi)
70 | ```
71 |
72 | After that lets implement `module.cpp` that will be build into a dynamic lib:
73 | ```c++
74 | #include "dylib/dylib.hpp"
75 | #include "ziapi/Module.hpp"
76 |
77 | class Module : public ziapi::IModule {
78 | public:
79 | Module() = default;
80 |
81 | ~Module() override = default;
82 |
83 | void Init(const ziapi::config::Node &) override {}
84 |
85 | ziapi::Version GetVersion() const noexcept override { return {4, 0, 0}; }
86 |
87 | ziapi::Version GetCompatibleApiVersion() const noexcept override { return {4, 0, 0}; }
88 |
89 | [[nodiscard]] virtual const char *GetName() const noexcept override { return "module_name"; }
90 |
91 | [[nodiscard]] virtual const char *GetDescription() const noexcept override
92 | {
93 | return "A module implementation example";
94 | }
95 | };
96 |
97 | DYLIB_API ziapi::IModule *LoadZiaModule() { return new Module; }
98 | ```
99 |
100 | > :warning: The function that returns a new module from a dynamic library **MUST** have this prototype:
101 | ```c++
102 | DYLIB_API ziapi::IModule *LoadZiaModule()
103 | ```
104 |
105 | And then let's implement our `main.cpp` that will load the dynamic lib:
106 |
107 | ```c++
108 | #include "ziapi/Logger.hpp"
109 | #include "ziapi/Module.hpp"
110 | #include "dylib/dylib.hpp"
111 |
112 | int main()
113 | {
114 | try {
115 | // Create a dynamic lib object that will load the module
116 | dylib lib("./module", dylib::extension);
117 | // Get the function that will generate our module when called
118 | auto entry_point_fn = lib.get_function("LoadZiaModule");
119 | // Call the function to get a module instance
120 | std::unique_ptr mod(entry_point_fn());
121 | // Print information about the module using the logger
122 | ziapi::Logger::Info("Module loaded: ", mod->GetName(), " - ", mod->GetDescription());
123 | }
124 | catch (const dylib::exception &e) {
125 | // Catch exceptions around a dynamic lib (handle or symbol errors) and print them using the logger
126 | ziapi::Logger::Error(e.what());
127 | }
128 | return 0;
129 | }
130 | ```
131 |
132 | Let's run our binary:
133 | ```sh
134 | > ./zia
135 | Sun Jan 23 16:07:41 2022 [i] Module loaded: module_name - A module implementation example
136 | ```
137 |
--------------------------------------------------------------------------------
/examples/dylib/Module.cpp:
--------------------------------------------------------------------------------
1 | #include "ziapi/Module.hpp"
2 |
3 | #include "dylib/dylib.hpp"
4 |
5 | class Module : public ziapi::IModule {
6 | public:
7 | Module() = default;
8 |
9 | ~Module() override = default;
10 |
11 | void Init(const ziapi::config::Node &) override {}
12 |
13 | ziapi::Version GetVersion() const noexcept override { return {4, 0, 0}; }
14 |
15 | ziapi::Version GetCompatibleApiVersion() const noexcept override { return {4, 0, 0}; }
16 |
17 | [[nodiscard]] virtual const char *GetName() const noexcept override { return "module_name"; }
18 |
19 | [[nodiscard]] virtual const char *GetDescription() const noexcept override
20 | {
21 | return "A module implementation example";
22 | }
23 | };
24 |
25 | DYLIB_API ziapi::IModule *LoadZiaModule() { return new Module; }
26 |
--------------------------------------------------------------------------------
/examples/modules/compressor/CompressorModule.hpp:
--------------------------------------------------------------------------------
1 | #include "ziapi/Module.hpp"
2 |
3 | class CompressorModule : public ziapi::IPostProcessorModule {
4 | public:
5 | void Init(const ziapi::config::Node &) override
6 | {
7 | // Don't need anything to configure in this implementation
8 | }
9 |
10 | [[nodiscard]] ziapi::Version GetVersion() const noexcept override { return ziapi::Version{4, 0, 0}; }
11 |
12 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept override { return ziapi::Version{4, 0, 0}; }
13 |
14 | [[nodiscard]] const char *GetName() const noexcept override { return "CompressorModule"; }
15 |
16 | [[nodiscard]] const char *GetDescription() const noexcept override
17 | {
18 | return "Compress the response body before sending it back to the network";
19 | }
20 |
21 | void PostProcess(ziapi::http::Context &, const ziapi::http::Request &, ziapi::http::Response &res) override
22 | {
23 | res.body = CompressBody(res.body);
24 | }
25 |
26 | [[nodiscard]] double GetPostProcessorPriority() const noexcept override
27 | {
28 | // Compressor needs to be ran last, just before sending data back
29 | return 1.0f;
30 | }
31 |
32 | [[nodiscard]] bool ShouldPostProcess(const ziapi::http::Context &, const ziapi::http::Request &,
33 | const ziapi::http::Response &) const override
34 | {
35 | // Compressor will always be used as it's always useful
36 | return true;
37 | }
38 |
39 | private:
40 | std::string CompressBody(const std::string &body)
41 | {
42 | // Algorithm of the hell
43 | return body.substr(0, body.length() / 2);
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/examples/modules/decompressor/DecompressorModule.hpp:
--------------------------------------------------------------------------------
1 | #include "ziapi/Module.hpp"
2 |
3 | class DecompressorModule : public ziapi::IPreProcessorModule {
4 | public:
5 | ~DecompressorModule() = default;
6 |
7 | void Init(const ziapi::config::Node &) override
8 | {
9 | // Don't need anything to configure in this implementation
10 | }
11 |
12 | [[nodiscard]] ziapi::Version GetVersion() const noexcept override { return ziapi::Version{4, 0, 0}; }
13 |
14 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept override { return ziapi::Version{4, 0, 0}; }
15 |
16 | [[nodiscard]] const char *GetName() const noexcept override { return "DecompressorModule"; }
17 |
18 | [[nodiscard]] const char *GetDescription() const noexcept override
19 | {
20 | return "Decompress the response body before sending it to the module pipeline";
21 | }
22 |
23 | void PreProcess(ziapi::http::Context &, ziapi::http::Request &req) override { req.body = DecompressBody(req.body); }
24 |
25 | [[nodiscard]] double GetPreProcessorPriority() const noexcept override
26 | {
27 | // Decompressor needs to be ran first, before any pre-processor.
28 | return 0.0f;
29 | }
30 |
31 | [[nodiscard]] bool ShouldPreProcess(const ziapi::http::Context &, const ziapi::http::Request &req) const override
32 | {
33 | // Only use if compressed header is set
34 | return req.headers.at("compressed") == std::string("true");
35 | }
36 |
37 | private:
38 | std::string DecompressBody(const std::string &body)
39 | {
40 | // algorithm of the hell
41 | return std::string(body + std::string(" omg i am now decompressed thx algorithm"));
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/examples/modules/directory-listing/DirectoryListingModule.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "ziapi/Config.hpp"
5 | #include "ziapi/Module.hpp"
6 |
7 | class DirectoryListingModule : public ziapi::IHandlerModule {
8 | public:
9 | void Init(const ziapi::config::Node &cfg) override
10 | {
11 | /// In our config, we can specify which folder our module serves.
12 | /// We fetch the "modules.directoryListing.root" variable from the config
13 | /// as a string.
14 | root_ = cfg["modules"]["directoryListing"]["root"].AsString();
15 | }
16 |
17 | [[nodiscard]] ziapi::Version GetVersion() const noexcept override { return {4, 0, 0}; }
18 |
19 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept override { return {4, 0, 0}; }
20 |
21 | [[nodiscard]] const char *GetName() const noexcept override { return "DirectoryListing"; }
22 |
23 | [[nodiscard]] const char *GetDescription() const noexcept override
24 | {
25 | return "Give access to a filesystem over HTTP";
26 | }
27 |
28 | [[nodiscard]] double GetHandlerPriority() const noexcept override
29 | {
30 | /// Our module doesn't have any specific priority requirements.
31 | return 0.5f;
32 | }
33 |
34 | [[nodiscard]] bool ShouldHandle(const ziapi::http::Context &ctx, const ziapi::http::Request &req) const override
35 | {
36 | /// We only want to handle GET requests.
37 | return req.method == ziapi::http::method::kGet;
38 | }
39 |
40 | void Handle(ziapi::http::Context &ctx, const ziapi::http::Request &req, ziapi::http::Response &res) override
41 | {
42 | /// Here we concat the root path from our config (e.g /var/www/) with the target from our request (e.g.
43 | /// index.html) to get the full path of our file.
44 | auto filepath = std::filesystem::path(root_) / std::filesystem::path(req.target);
45 | std::error_code ec;
46 |
47 | /// If the file is a directory let's provide a list of all the files as a response.
48 | if (std::filesystem::is_directory(filepath, ec)) {
49 | std::ostringstream ss;
50 | for (const auto &entry : std::filesystem::directory_iterator(filepath)) {
51 | ss << entry.path().string() << "\n";
52 | }
53 | return;
54 | } else {
55 | /// If the file is not a directory, we assume it's a regular file and we just send its contents with the
56 | /// response.
57 | std::ifstream file_stream(filepath.filename());
58 | std::ostringstream ss;
59 | ss << file_stream.rdbuf();
60 | res.body = ss.str();
61 | return;
62 | }
63 | res.status_code = ziapi::http::Code::kNotFound;
64 | res.reason = ziapi::http::reason::kNotFound;
65 | }
66 |
67 | private:
68 | std::string root_;
69 | };
70 |
--------------------------------------------------------------------------------
/examples/modules/logger/LoggerModule.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "ziapi/Logger.hpp"
5 | #include "ziapi/Module.hpp"
6 |
7 | class LoggerModule : virtual public ziapi::IPreProcessorModule, public ziapi::IPostProcessorModule {
8 | public:
9 | void Init(const ziapi::config::Node &config) {}
10 |
11 | [[nodiscard]] ziapi::Version GetVersion() const noexcept override { return {4, 0, 0}; }
12 |
13 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept override { return {4, 0, 0}; }
14 |
15 | [[nodiscard]] const char *GetName() const noexcept override { return "LoggerModule"; }
16 |
17 | [[nodiscard]] const char *GetDescription() const noexcept override
18 | {
19 | return "Log all responses from HTTP requests";
20 | }
21 |
22 | [[nodiscard]] double GetPostProcessorPriority() const noexcept override { return 1; }
23 |
24 | [[nodiscard]] bool ShouldPostProcess(const ziapi::http::Context &ctx, const ziapi::http::Request &req,
25 | const ziapi::http::Response &res) const override
26 | {
27 | return true;
28 | }
29 |
30 | [[nodiscard]] double GetPreProcessorPriority() const noexcept override { return 0; }
31 |
32 | [[nodiscard]] bool ShouldPreProcess(const ziapi::http::Context &ctx, const ziapi::http::Request &req) const override
33 | {
34 | return true;
35 | }
36 |
37 | void PostProcess(ziapi::http::Context &ctx, const ziapi::http::Request &, ziapi::http::Response &res) override
38 | {
39 | std::stringstream ss;
40 |
41 | // Exemple: ` [X] 404: Not found (GET /test, 2s)`
42 | ss << std::to_string((int)res.status_code) << ": " << res.reason << " ("
43 | << std::any_cast(ctx["method"]) << " " << std::any_cast(ctx["target"]) << ", "
44 | << std::setprecision(2) << difftime(std::time(nullptr), std::any_cast(ctx["timestamp"])) << "s)";
45 | if ((int)res.status_code < 300) {
46 | ziapi::Logger::Info(ss.str());
47 | } else if ((int)res.status_code < 400) {
48 | ziapi::Logger::Warning(ss.str());
49 | } else {
50 | ziapi::Logger::Error(ss.str());
51 | }
52 | }
53 |
54 | void PreProcess(ziapi::http::Context &ctx, ziapi::http::Request &req) override
55 | {
56 | ctx["timestamp"] = std::time(nullptr);
57 | /// This example is deprecrated. Storing the target and method is useless because the `PostProcess` method now
58 | /// has access to the request object.
59 | ctx["target"] = req.target;
60 | ctx["method"] = req.method;
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/examples/modules/redirection/Module.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include "ziapi/Module.hpp"
8 |
9 | class RedirectionModule : public ziapi::IHandlerModule {
10 | public:
11 | void Init(const ziapi::config::Node &cfg) override
12 | {
13 | /// We'll load from the configuration where to redirect to!
14 | redirection_route_ = cfg["modules"]["redirection"]["route"];
15 | }
16 |
17 | void Handle(ziapi::http::Context &ctx, const ziapi::http::Request &req, ziapi::http::Response &res) override
18 | {
19 | /// Now we just say that the resource was moved permenatly and we indicate the new
20 | /// location with the redirection route.
21 | res.headers[ziapi::http::header::kLocation] = redirection_route_;
22 | res.status_code = ziapi::http::Code::kMovedPermanently;
23 | }
24 |
25 | [[nodiscard]] bool ShouldHandle(const ziapi::http::Request &req)
26 | {
27 | /// We wish to handle all requests.
28 | return true;
29 | }
30 |
31 | [[nodiscard]] double GetHandlerPriority() const noexcept
32 | {
33 | /// Our handler is really important!
34 | return 0.9f;
35 | }
36 |
37 | [[nodiscard]] ziapi::Version GetVersion() const noexcept { return {4, 0, 0}; }
38 |
39 | [[nodiscard]] ziapi::Version GetCompatibleApiVersion() const noexcept { return {4, 0, 0}; }
40 |
41 | [[nodiscard]] const char *GetName() const noexcept { return "Redirection Module"; }
42 |
43 | [[nodiscard]] const char *GetDescription() const noexcept { return "Redirects the request to another location."; }
44 |
45 | private:
46 | std::string redirection_route_;
47 | };
48 |
--------------------------------------------------------------------------------
/include/ziapi/Color.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | /**
4 | * This namespace contains every color that you can display on the shell
5 | */
6 | namespace ziapi::color {
7 |
8 | constexpr auto DEFAULT = "\x1B[0m";
9 | constexpr auto RED = "\x1B[31m";
10 | constexpr auto GREEN = "\x1B[32m";
11 | constexpr auto YELLOW = "\x1B[33m";
12 | constexpr auto BLUE = "\x1B[34m";
13 | constexpr auto MAGENTA = "\x1B[35m";
14 | constexpr auto CYAN = "\x1B[36m";
15 |
16 | } // namespace ziapi::color
17 |
--------------------------------------------------------------------------------
/include/ziapi/Config.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | namespace ziapi::config {
11 |
12 | enum Type {
13 | kUndefined = 0,
14 | kNull,
15 | kBool,
16 | kInt,
17 | kDouble,
18 | kString,
19 | kArray,
20 | kDict,
21 | };
22 |
23 | struct Node;
24 |
25 | struct Undefined {
26 | };
27 |
28 | using Null = std::nullptr_t;
29 |
30 | using String = std::string;
31 |
32 | using Array = std::vector>;
33 |
34 | /// Datatype for json/yaml-like data
35 | using Dict = std::unordered_map>;
36 |
37 | using NodeVariant = std::variant;
38 |
39 | struct Node : public NodeVariant {
40 | public:
41 | using NodeVariant::NodeVariant;
42 |
43 | /// Simpler way to construct an Array node, it instantiates an std::shared_ptr for each value
44 | static Node MakeArray(const std::initializer_list &values)
45 | {
46 | Array arr;
47 |
48 | for (auto &value : values) {
49 | arr.push_back(std::make_shared(value));
50 | }
51 | return arr;
52 | }
53 |
54 | /// Constructs an Array node from a vector, it instantiates an std::shared_ptr for each value
55 | static Node MakeArray(const std::vector &values)
56 | {
57 | Array arr;
58 |
59 | for (auto &value : values) {
60 | arr.push_back(std::make_shared(value));
61 | }
62 | return arr;
63 | }
64 |
65 | /// Simpler way to construct a Dict node, it instantiates an std::shared_ptr for each value
66 | static Node MakeDict(const std::initializer_list> &values)
67 | {
68 | Dict dict;
69 |
70 | for (auto &value : values) {
71 | dict[value.first] = std::make_shared(value.second);
72 | }
73 | return dict;
74 | }
75 |
76 | /// Constructs a Dict node from a vector, it instantiates an std::shared_ptr for each value
77 | static Node MakeDict(const std::unordered_map &values)
78 | {
79 | Dict dict;
80 |
81 | for (auto &[key, value] : values) {
82 | dict[key] = std::make_shared(value);
83 | }
84 | return dict;
85 | }
86 |
87 | /// Used to construct a Node from a string
88 | Node(const char *str) : NodeVariant(std::string(str)){};
89 |
90 | /// Shorthand, `node->AsDict()["something"]` become `node["something"]`
91 | Node &operator[](const char *key) const { return *AsDict().at(key); }
92 |
93 | /// Shorthand, `node->AsDict()["something"]` become `node["something"]`
94 | Node &operator[](const std::string &key) const { return *AsDict().at(key); }
95 |
96 | /// Shorthand, `node->AsArray()[5]` become `node[5]`
97 | Node &operator[](std::size_t index) const { return *AsArray().at(index); }
98 |
99 | /// Casts the variant as a bool. Will throw if actual type differs.
100 | bool AsBool() const { return std::get(*this); }
101 |
102 | /// Casts the variant as a Dict. Will throw if actual type differs.
103 | const Dict &AsDict() const { return std::get(*this); }
104 |
105 | /// Casts the variant as a String. Will throw if actual type differs.
106 | const String &AsString() const { return std::get(*this); }
107 |
108 | /// Casts the variant as a Array. Will throw if actual type differs.
109 | const Array &AsArray() const { return std::get(*this); }
110 |
111 | /// Casts the variant as a int. Will throw if actual type differs.
112 | int AsInt() const { return std::get(*this); }
113 |
114 | /// Casts the variant as a double. Will throw if actual type differs.
115 | double AsDouble() const { return std::get(*this); }
116 |
117 | /// Check if your Node value is null
118 | bool IsNull() const { return index() == kNull; }
119 |
120 | /// Check if your Node value is undefined
121 | bool IsUndefined() const { return index() == kUndefined; }
122 |
123 | /// Check if your Node is not empty
124 | operator bool() const { return !IsNull() && !IsUndefined(); }
125 | };
126 |
127 | } // namespace ziapi::config
128 |
--------------------------------------------------------------------------------
/include/ziapi/Http.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include