├── .gitignore ├── TinyWebServer ├── src │ ├── util │ │ ├── event.cpp │ │ ├── backtrace.h │ │ ├── sharedfilepool.h │ │ ├── headermap.h │ │ ├── sharedfilepool.cpp │ │ ├── event.h │ │ ├── threadpool.h │ │ ├── backtrace.cpp │ │ ├── util.h │ │ └── timermanager.h │ ├── core │ │ ├── tcpsocket.h │ │ ├── sslsocket.h │ │ ├── epoll.h │ │ ├── eventloop.h │ │ ├── eventloop.cpp │ │ ├── tcpsocket.cpp │ │ ├── epoll.cpp │ │ └── sslsocket.cpp │ ├── define.h │ ├── abstract │ │ ├── abstractservices.h │ │ ├── abstractsocket.h │ │ └── abstractsocket.cpp │ ├── http │ │ ├── url.h │ │ ├── httprequest.h │ │ ├── httpservices.h │ │ ├── httpresponse.h │ │ ├── httprequest.cpp │ │ ├── httpservices.cpp │ │ └── httpresponse.cpp │ ├── webserver.h │ ├── webserver.cpp │ └── main.cpp ├── screenshot │ ├── close.png │ └── keep-alive.png ├── CMakeLists.txt └── README.md ├── CMakeLists.txt ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── Linux.yml │ ├── Windows.yml │ └── codeql-analysis.yml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vs/ 3 | *user* 4 | CMakeSettings.json 5 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/event.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/28 4 | */ 5 | 6 | #include "event.h" 7 | -------------------------------------------------------------------------------- /TinyWebServer/screenshot/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/Network-Learn/HEAD/TinyWebServer/screenshot/close.png -------------------------------------------------------------------------------- /TinyWebServer/screenshot/keep-alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ho-229/Network-Learn/HEAD/TinyWebServer/screenshot/keep-alive.png -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Network-Learn 2 | 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | project(Network-Learn) 6 | 7 | set(CXX_FLAGS 8 | -j4 9 | # -fsanitize=address 10 | # -fsanitize=undefined 11 | # -fsanitize=leak 12 | # -fsanitize-recover=all 13 | # -fno-omit-frame-pointer 14 | # -fno-stack-protector 15 | # -fsanitize=leak 16 | ) 17 | 18 | set(CMAKE_CXX_STANDARD 17) 19 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 20 | 21 | # Subprojects 22 | add_subdirectory(TinyWebServer) 23 | -------------------------------------------------------------------------------- /TinyWebServer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) 2 | 3 | set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) 4 | 5 | add_executable(TinyWebServer ${ALL_CLANG_FORMAT_SOURCE_FILES}) 6 | 7 | set(DEP_LIBS 8 | OpenSSL::SSL # modern 9 | #${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY} # old style 10 | ) 11 | 12 | find_package(OpenSSL 1.1 REQUIRED) 13 | 14 | if(WIN32) 15 | target_link_libraries(TinyWebServer wsock32 ws2_32 ${DEP_LIBS}) 16 | else() 17 | find_package(Threads) 18 | target_link_libraries(TinyWebServer #[[profiler]] ${CMAKE_THREAD_LIBS_INIT} ${DEP_LIBS}) 19 | endif() 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/backtrace.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKTRACE_H 2 | #define BACKTRACE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | # include 10 | #endif 11 | 12 | #define ADDR_MAX_NUM 100 13 | 14 | class BackTrace 15 | { 16 | public: 17 | using Handler = std::function; 18 | 19 | explicit BackTrace(); 20 | 21 | static void installHandler(const Handler &handler); 22 | 23 | private: 24 | static BackTrace &instance(); 25 | 26 | Handler m_handler; 27 | 28 | #ifdef _WIN32 29 | static LONG WINAPI exceptionHandler(struct _EXCEPTION_POINTERS *exp); 30 | #else 31 | static void signalHandler(int signum); 32 | #endif 33 | }; 34 | 35 | #endif // BACKTRACE_H 36 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/tcpsocket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/6 4 | */ 5 | 6 | #ifndef TCPSOCKET_H 7 | #define TCPSOCKET_H 8 | 9 | #include "../abstract/abstractsocket.h" 10 | 11 | class TcpSocket : public AbstractSocket 12 | { 13 | public: 14 | explicit TcpSocket(const Socket socket = INVALID_SOCKET); 15 | ~TcpSocket() override; 16 | 17 | ssize_t read(char *buf, size_t count) override; 18 | ssize_t write(const char* buf, size_t count) override; 19 | 20 | void close() override; 21 | 22 | bool sslEnable() const override { return false; } 23 | 24 | bool isValid() const override { return AbstractSocket::isValid(m_descriptor); } 25 | 26 | ssize_t sendFile(File file, off_t offset, size_t count) override; 27 | }; 28 | 29 | #endif // TCPSOCKET_H 30 | -------------------------------------------------------------------------------- /.github/workflows/Linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*/README.md' 7 | - 'LICENSE' 8 | 9 | pull_request: 10 | paths-ignore: 11 | - '*/README.md' 12 | - 'LICENSE' 13 | 14 | env: 15 | # Path to the solution file relative to the root of the project. 16 | SOLUTION_FILE_PATH: . 17 | 18 | # Configuration type to build. 19 | # You can convert this to a build matrix if you need coverage of multiple configuration types. 20 | # https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 21 | BUILD_CONFIGURATION: Release 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | 30 | - name: Build 31 | shell: pwsh 32 | run: | 33 | mkdir build && cd build 34 | cmake .. 35 | cmake --build . --config Release 36 | -------------------------------------------------------------------------------- /TinyWebServer/src/define.h: -------------------------------------------------------------------------------- 1 | #ifndef DEFINE_H 2 | #define DEFINE_H 3 | 4 | #define ANY_HOST "0.0.0.0" 5 | #define LISTENQ 1024 6 | 7 | #ifdef _WIN32 8 | typedef unsigned int Socket; // Socket handle 9 | typedef void* File; // File handle 10 | # define ssize_t SSIZE_T 11 | # define OS_WINDOWS 12 | #else // *nix 13 | typedef int Socket; // Socket descriptor 14 | typedef int File; // File descriptor 15 | 16 | # define INVALID_SOCKET -1 17 | # ifdef __linux 18 | # define OS_LINUX 19 | # else 20 | # define OS_UNIX 21 | # endif 22 | #endif 23 | 24 | /********************** 25 | * User definitions * 26 | **********************/ 27 | #define SOCKET_BUF_SIZE 4096 28 | #define SOCKET_INFO_ENABLE 0 29 | 30 | #define EPOLL_WAIT_TIMEOUT 500 31 | #define EPOLL_MAX_EVENTS 256 32 | 33 | #define TCP_CORK_ENABLE 0 // Only for Linux 34 | /********************** 35 | * User definitions * 36 | **********************/ 37 | 38 | #endif // DEFINE_H 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /TinyWebServer/src/abstract/abstractservices.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/10/5 4 | */ 5 | 6 | #ifndef ABSTRACTSERVICES_H 7 | #define ABSTRACTSERVICES_H 8 | 9 | #include 10 | #include 11 | 12 | class AbstractSocket; 13 | 14 | class AbstractServices 15 | { 16 | public: 17 | virtual ~AbstractServices() = default; 18 | 19 | virtual bool process(AbstractSocket *const socket) const = 0; 20 | 21 | /** 22 | * @brief Maximum number of requests per connection 23 | * @note If num == 0 does not limit the number of requests 24 | * @default 0 25 | */ 26 | void setMaxTimes(size_t num) { m_maxTimes = num; } 27 | size_t maxTimes() const { return m_maxTimes; } 28 | 29 | protected: 30 | size_t m_maxTimes = 0; 31 | 32 | explicit AbstractServices() = default; 33 | 34 | // Disable copy 35 | AbstractServices(AbstractServices& other) = delete; 36 | AbstractServices& operator=(const AbstractServices& other) = delete; 37 | }; 38 | 39 | #endif // ABSTRACTSERVICES_H 40 | -------------------------------------------------------------------------------- /.github/workflows/Windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*/README.md' 7 | - 'LICENSE' 8 | 9 | pull_request: 10 | paths-ignore: 11 | - '*/README.md' 12 | - 'LICENSE' 13 | 14 | env: 15 | # Path to the solution file relative to the root of the project. 16 | SOLUTION_FILE_PATH: . 17 | 18 | # Configuration type to build. 19 | # You can convert this to a build matrix if you need coverage of multiple configuration types. 20 | # https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 21 | BUILD_CONFIGURATION: Release 22 | 23 | jobs: 24 | build: 25 | runs-on: windows-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | 30 | - name: Install OpenSSL 31 | shell: pwsh 32 | run: | 33 | choco install openssl 34 | 35 | - name: Add MSBuild to PATH 36 | uses: microsoft/setup-msbuild@v1.0.2 37 | 38 | - name: Build 39 | shell: pwsh 40 | run: | 41 | mkdir build && cd build 42 | cmake .. 43 | cmake --build . --config Release 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ho 229 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Network-Learn 2 | 3 | ![license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square) 4 | ![lines](https://tokei.rs/b1/github/ho-229/Network-Learn) 5 | ![windows](https://github.com/ho229v3666/Network-Learn/workflows/Windows/badge.svg?style=flat-square) 6 | ![linux](https://github.com/ho229v3666/Network-Learn/workflows/Linux/badge.svg?style=flat-square) 7 | A cross-platform network learning ~~demos~~(toys). And I try ~~not~~ to use 3rd-party libraries. 8 | Welcome to try it out and leave your comments. 9 | 10 | | Name | Description | 11 | | ---- | ----------- | 12 | | [TinyWebServer](./TinyWebServer) | A tiny `Http/Https` web server | 13 | 14 | ## Build 15 | 16 | Use `cmake` to build your project. 17 | Require `C++17` support and `OpenSSL`. 18 | 19 | * Install OpenSSL 1.1. 20 | 21 | * Windows(run in `PowerShell`) 22 | 23 | ```shell 24 | choco install openssl 25 | ``` 26 | 27 | * MacOS 28 | 29 | ```shell 30 | brew install openssl@1.1 31 | ``` 32 | 33 | * Configure and build 34 | 35 | ```shell 36 | mkdir build && cd build 37 | cmake .. 38 | cmake --build . --config Release 39 | ``` 40 | 41 | Of course, you can also use IDE to config and build. 42 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/sslsocket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/7 4 | */ 5 | 6 | #ifndef SSLSOCKET_H 7 | #define SSLSOCKET_H 8 | 9 | #include "../abstract/abstractsocket.h" 10 | 11 | #include 12 | 13 | class SslSocket : public AbstractSocket 14 | { 15 | public: 16 | explicit SslSocket(const Socket socket = INVALID_SOCKET); 17 | ~SslSocket() override; 18 | 19 | ssize_t read(char *buf, size_t count) override; 20 | ssize_t write(const char* buf, size_t count) override; 21 | 22 | void close() override; 23 | 24 | bool sslEnable() const override { return true; } 25 | 26 | bool isValid() const override { return AbstractSocket::isValid(m_descriptor) 27 | && m_ssl; } 28 | 29 | ssize_t sendFile(File file, off_t offset, size_t count) override; 30 | 31 | static bool initializatSsl(const std::string& certFile, 32 | const std::string& privateKey); 33 | static void cleanUpSsl(); 34 | static std::string sslVersion(); 35 | static bool isSslAvailable() { return sslContext; } 36 | 37 | private: 38 | static SSL_CTX *sslContext; 39 | 40 | SSL *m_ssl = nullptr; 41 | }; 42 | 43 | #endif // SSLSOCKET_H 44 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/sharedfilepool.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/11/28 4 | */ 5 | 6 | #ifndef SHAREDFILEPOOL_H 7 | #define SHAREDFILEPOOL_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "../define.h" 17 | 18 | #define MAX_SHARED_FILE 128 19 | 20 | namespace fs = std::filesystem; 21 | 22 | struct FileInfo 23 | { 24 | File file; 25 | size_t fileSize; 26 | std::string extension; 27 | std::string lastModified; 28 | }; 29 | 30 | class SharedFilePool 31 | { 32 | public: 33 | explicit SharedFilePool(const std::string &root); 34 | ~SharedFilePool(); 35 | 36 | std::string root() const { return m_root; } 37 | 38 | std::optional get(const std::string &fileName); 39 | 40 | private: 41 | std::unordered_map m_pool; 42 | std::unordered_set m_invalidPaths; 43 | 44 | const std::string m_root; 45 | 46 | std::shared_mutex m_mutex; 47 | 48 | typedef std::shared_lock ReadLock; 49 | typedef std::lock_guard WriteLock; 50 | }; 51 | 52 | #endif // SHAREDFILEPOOL_H 53 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/headermap.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/11/21 4 | */ 5 | 6 | #ifndef HEADERMAP_H 7 | #define HEADERMAP_H 8 | 9 | #ifndef _WIN32 10 | extern "C" 11 | { 12 | # include 13 | } 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "util.h" 21 | 22 | #define HASH_MAP 1 23 | 24 | #if HASH_MAP 25 | template 26 | struct NocaseCompare 27 | { 28 | inline bool operator()(const T &left, 29 | const T &right) const 30 | { return !Util::strcasecmp(left, right); } 31 | }; 32 | 33 | template 34 | struct NocaseHash 35 | { 36 | size_t operator()(const T &str) const 37 | { 38 | if(str.empty()) 39 | return 0; 40 | 41 | char first = str.front(); 42 | 43 | if(first >= 'A' && first <= 'Z') // to lower 44 | first += 'a' - 'A'; 45 | else if(first < 'a' || first > 'z') // other charactor 46 | return 0; 47 | 48 | return size_t(first - 'a'); 49 | } 50 | }; 51 | 52 | template 53 | using HeaderMap = std::unordered_map, // Hash 55 | NocaseCompare>; // Compare; 56 | 57 | #else 58 | template 59 | struct NocaseCompare 60 | { 61 | inline bool operator()(const T &left, 62 | const T &right) const 63 | { return Util::strcasecmp(left, right) < 0; } 64 | }; 65 | 66 | template 67 | using HeaderMap = std::map>; 69 | #endif 70 | 71 | #endif // HEADERMAP_H 72 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/epoll.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/20 4 | */ 5 | 6 | #ifndef EPOLL_H 7 | #define EPOLL_H 8 | 9 | #include "../define.h" 10 | #include "../abstract/abstractsocket.h" 11 | 12 | #include 13 | 14 | #define EPOLL_THREAD_SAFE 0 15 | 16 | #if defined (OS_WINDOWS) 17 | # include 18 | # include 19 | typedef std::vector EventList; 20 | #else 21 | # if defined (OS_LINUX) 22 | # include 23 | # else 24 | # include 25 | # include 26 | # include 27 | # endif 28 | # include 29 | # include 30 | #endif 31 | 32 | class Epoll 33 | { 34 | public: 35 | explicit Epoll(); 36 | ~Epoll(); 37 | 38 | void insert(AbstractSocket *const socket, bool exclusive = false); 39 | void erase(AbstractSocket *const socket); 40 | 41 | void epoll(std::vector &events, 42 | std::vector &errorEvents); 43 | 44 | size_t count() const 45 | { 46 | #ifdef _WIN32 47 | return m_events.size(); 48 | #else 49 | return m_count; 50 | #endif 51 | } 52 | 53 | private: 54 | #if defined (OS_WINDOWS) // Windows poll 55 | std::unordered_map m_connections; 56 | 57 | using ConnectionItem = decltype (m_connections)::value_type; 58 | 59 | std::vector m_events; 60 | 61 | # if EPOLL_THREAD_SAFE 62 | std::mutex m_mutex; 63 | # endif 64 | #else 65 | std::atomic_uint m_count = 0; 66 | # if defined (OS_LINUX) // Linux epoll 67 | int m_epoll = 0; 68 | epoll_event m_eventBuf[EPOLL_MAX_EVENTS]; 69 | # else // Unix kqueue 70 | int m_kqueue = 0; 71 | struct kevent m_eventBuf[EPOLL_MAX_EVENTS]; 72 | # endif 73 | #endif 74 | }; 75 | 76 | #endif // EPOLL_H 77 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/eventloop.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/10/3 4 | */ 5 | 6 | #ifndef EVENTLOOP_H 7 | #define EVENTLOOP_H 8 | 9 | #include "epoll.h" 10 | #include "../util/event.h" 11 | #include "../util/timermanager.h" 12 | #include "../abstract/abstractsocket.h" 13 | 14 | #include 15 | #include 16 | 17 | class AbstractServices; 18 | 19 | class EventLoop 20 | { 21 | public: 22 | explicit EventLoop(const volatile bool &runnable, 23 | const std::chrono::milliseconds &timeout, 24 | AbstractServices *const services, 25 | const EventHandler &handler); 26 | ~EventLoop(); 27 | 28 | template 29 | void registerListeners(const Iter begin, const Iter end) 30 | { 31 | for(auto it = begin; it < end; ++it) 32 | m_epoll.insert(it->get(), true); 33 | } 34 | 35 | template 36 | void unregisterListeners(const Iter begin, const Iter end) 37 | { 38 | for(auto it = begin; it < end; ++it) 39 | m_epoll.erase(it->get()); 40 | } 41 | 42 | inline void start() 43 | { m_thread = std::thread(std::bind(&EventLoop::exec, this)); } 44 | 45 | inline void waitForFinished() 46 | { 47 | if(m_thread.joinable()) 48 | m_thread.join(); 49 | } 50 | 51 | protected: 52 | void exec(); 53 | 54 | private: 55 | void processQueue(); 56 | void processErrorQueue(); 57 | void processTimeoutQueue(); 58 | 59 | Epoll m_epoll; 60 | TimerManager m_manager; 61 | 62 | std::vector m_queue; 63 | std::vector m_errorQueue; 64 | 65 | const volatile bool &m_runnable; 66 | const std::chrono::milliseconds &m_timeout; 67 | 68 | AbstractServices *const m_services; 69 | 70 | EventHandler m_handler; 71 | 72 | std::thread m_thread; 73 | }; 74 | 75 | #endif // EVENTLOOP_H 76 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/url.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @ref https://github.com/ithewei/libhv/blob/6cf0ce0eb09caf779d5524c154d2166d9aab7299/cpputil/hurl.cpp 3 | */ 4 | 5 | #ifndef URL_H 6 | #define URL_H 7 | 8 | #include 9 | 10 | #define IS_ALPHA(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) 11 | #define IS_NUM(c) ((c) >= '0' && (c) <= '9') 12 | #define IS_HEX(c) (IS_NUM(c) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) 13 | #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) 14 | 15 | static inline bool isUnambiguous(const uint8_t c) 16 | { 17 | return IS_ALPHANUM(c) || 18 | c == '-' || 19 | c == '_' || 20 | c == '.' || 21 | c == '~'; 22 | } 23 | 24 | static inline unsigned char hex2i(const uint8_t hex) 25 | { 26 | return hex <= '9' ? hex - '0' : 27 | hex <= 'F' ? hex - 'A' + 10 : hex - 'a' + 10; 28 | } 29 | 30 | template 31 | std::string uriEscape(const String &src) 32 | { 33 | std::string ostr; 34 | 35 | static const char tab[] = "0123456789ABCDEF"; 36 | const auto it = src.cbegin(); 37 | 38 | char szHex[4] = "%00"; 39 | 40 | while(it != src.cend()) 41 | { 42 | if(isUnambiguous(*it)) 43 | ostr += char(*it); 44 | else 45 | { 46 | szHex[1] = tab[*it >> 4]; 47 | szHex[2] = tab[*it & 0xF]; 48 | ostr += szHex; 49 | } 50 | ++it; 51 | } 52 | 53 | return ostr; 54 | } 55 | 56 | template 57 | std::string uriUnescape(const String &src) 58 | { 59 | std::string ostr; 60 | auto it = src.cbegin(); 61 | 62 | while(it != src.cend()) 63 | { 64 | if(*it == '%' && IS_HEX(it[1]) && IS_HEX(it[2])) 65 | { 66 | ostr += char((hex2i(it[1]) << 4) | hex2i(it[2])); 67 | it += 3; 68 | } 69 | else 70 | { 71 | ostr += char(*it); 72 | ++it; 73 | } 74 | } 75 | 76 | return ostr; 77 | } 78 | 79 | #endif // URL_H 80 | -------------------------------------------------------------------------------- /TinyWebServer/src/webserver.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #ifndef WEBSERVER_H 7 | #define WEBSERVER_H 8 | 9 | #include "util/event.h" 10 | #include "abstract/abstractservices.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std::chrono_literals; 18 | 19 | class EventLoop; 20 | class AbstractSocket; 21 | class AbstractServices; 22 | 23 | class WebServer 24 | { 25 | public: 26 | explicit WebServer(); 27 | virtual ~WebServer(); 28 | 29 | void setServices(AbstractServices *services) { m_services.reset(services); } 30 | AbstractServices *services() const { return m_services.get(); } 31 | 32 | /** 33 | * @brief Keep alive timeout 34 | */ 35 | void setTimeout(const std::chrono::milliseconds &ms) { m_timeout = ms; } 36 | std::chrono::milliseconds timeout() const { return m_timeout; } 37 | 38 | void setLoopCount(size_t count) 39 | { m_loopCount = count > 0 ? count : std::thread::hardware_concurrency(); } 40 | size_t loopCount() const { return m_loopCount; } 41 | 42 | /** 43 | * @note should run before WebServer::exec 44 | */ 45 | bool listen(const std::string& hostName, const std::string& port, 46 | bool sslEnable = false, std::pair linger = {false, 0}); 47 | 48 | int start(); 49 | void quit() { m_runnable = false; } 50 | void requestQuit(); 51 | 52 | void waitForFinished(); 53 | 54 | inline int exec() 55 | { 56 | int ret = 0; 57 | if((ret = this->start()) == 0) 58 | this->waitForFinished(); 59 | 60 | return ret; 61 | } 62 | 63 | template 64 | void installEventHandler(const Func& handler) { m_handler = handler; } 65 | 66 | private: 67 | volatile bool m_runnable = true; 68 | 69 | size_t m_loopCount = std::thread::hardware_concurrency(); 70 | 71 | std::chrono::milliseconds m_timeout = 30s; 72 | 73 | std::vector> m_loops; 74 | std::vector> m_listeners; 75 | 76 | std::unique_ptr m_services; 77 | 78 | EventHandler m_handler = [](Event *){}; 79 | }; 80 | 81 | #endif // WEBSERVER_H 82 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/sharedfilepool.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/11/28 4 | */ 5 | 6 | #include "sharedfilepool.h" 7 | 8 | #include "util.h" 9 | 10 | #ifdef _WIN32 11 | # include 12 | #else 13 | # include 14 | # include 15 | #endif 16 | 17 | SharedFilePool::SharedFilePool(const std::string &root) 18 | : m_root(fs::path(root).lexically_normal().string()) 19 | { 20 | 21 | } 22 | 23 | std::optional SharedFilePool::get(const std::string &fileName) 24 | { 25 | decltype (m_pool)::const_iterator it; 26 | { 27 | ReadLock lock(m_mutex); 28 | it = m_pool.find(fileName); 29 | } 30 | 31 | if(it == m_pool.end()) // Open file 32 | { 33 | File file = {}; 34 | fs::path filePath = fs::u8path(m_root + fileName); 35 | 36 | { 37 | ReadLock lock(m_mutex); 38 | if(m_invalidPaths.find(filePath.string()) != m_invalidPaths.end()) 39 | return {}; 40 | } 41 | 42 | #ifdef _WIN32 43 | if((file = CreateFileW(filePath.c_str(), 44 | GENERIC_READ, FILE_SHARE_READ, nullptr, 45 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 46 | nullptr)) == INVALID_HANDLE_VALUE) 47 | #else 48 | if(!fs::is_regular_file(filePath) || (file = open(filePath.c_str(), O_RDONLY)) < 0) 49 | #endif 50 | { 51 | WriteLock lock(m_mutex); 52 | 53 | if(m_invalidPaths.size() >= MAX_SHARED_FILE) 54 | m_invalidPaths.erase(m_invalidPaths.begin()); 55 | 56 | m_invalidPaths.insert(filePath.string()); 57 | return {}; 58 | } 59 | 60 | WriteLock lock(m_mutex); 61 | const FileInfo ret{file, static_cast(fs::file_size(filePath)), 62 | filePath.extension().string(), 63 | Util::toGmtFormat(Util::to_time_t(fs::last_write_time(filePath)))}; 64 | 65 | if(m_pool.size() >= MAX_SHARED_FILE) 66 | m_pool.erase(m_pool.begin()); 67 | 68 | return {m_pool.emplace(fileName, ret).first->second}; 69 | } 70 | 71 | return {it->second}; 72 | } 73 | 74 | SharedFilePool::~SharedFilePool() 75 | { 76 | for(const auto &[key, value] : m_pool) 77 | #ifdef _WIN32 78 | CloseHandle(value.file); 79 | #else // Unix 80 | ::close(value.file); 81 | #endif 82 | } 83 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/event.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/28 4 | */ 5 | 6 | #ifndef EVENT_H 7 | #define EVENT_H 8 | 9 | #include 10 | #include 11 | 12 | #define STATIC_ALLOCATOR(Class) \ 13 | static void* operator new (size_t) \ 14 | { \ 15 | static thread_local char space[sizeof(Class)]; \ 16 | return space; \ 17 | } \ 18 | static void operator delete (void *object, size_t) \ 19 | { \ 20 | static_cast(object)->~Class(); \ 21 | } 22 | 23 | /** 24 | * @brief Abstract Event 25 | */ 26 | class Event 27 | { 28 | public: 29 | enum Type 30 | { 31 | ConnectEvent, 32 | ExceptionEvent 33 | }; 34 | 35 | virtual ~Event() = default; 36 | 37 | virtual Type type() const = 0; 38 | 39 | protected: 40 | explicit Event() = default; 41 | }; 42 | 43 | typedef std::function EventHandler; 44 | 45 | /** 46 | * @brief Exception Event 47 | */ 48 | class ExceptionEvent : public Event 49 | { 50 | public: 51 | enum Error 52 | { 53 | UnknownError, 54 | ListenerError 55 | }; 56 | 57 | explicit ExceptionEvent(const Error err, const std::string_view& message = {}) 58 | : m_error(err), m_message(message) {} 59 | 60 | virtual Type type() const override { return Event::ExceptionEvent; } 61 | 62 | Error error() const { return m_error; } 63 | 64 | std::string_view message() const { return m_message; } 65 | 66 | STATIC_ALLOCATOR(ExceptionEvent) 67 | 68 | private: 69 | const Error m_error; 70 | const std::string_view m_message; 71 | }; 72 | 73 | 74 | /** 75 | * @brief Accept Event 76 | */ 77 | 78 | class AbstractSocket; 79 | 80 | class ConnectEvent : public Event 81 | { 82 | public: 83 | enum State 84 | { 85 | Accpet, 86 | Close 87 | }; 88 | 89 | explicit ConnectEvent(const AbstractSocket *socket, const State& state) 90 | : m_socket(socket), m_state(state) 91 | {} 92 | 93 | virtual Type type() const override { return Event::ConnectEvent; } 94 | 95 | const AbstractSocket *socket() const { return m_socket; } 96 | 97 | State state() const { return m_state; } 98 | 99 | STATIC_ALLOCATOR(ConnectEvent) 100 | 101 | private: 102 | const AbstractSocket *m_socket = nullptr; 103 | const State m_state; 104 | }; 105 | 106 | #endif // EVENT_H 107 | -------------------------------------------------------------------------------- /TinyWebServer/src/webserver.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #include "webserver.h" 7 | 8 | #include "util/util.h" 9 | #include "core/tcpsocket.h" 10 | #include "core/sslsocket.h" 11 | #include "core/eventloop.h" 12 | 13 | #include 14 | 15 | WebServer::WebServer() 16 | { 17 | #ifdef _WIN32 18 | if(!AbstractSocket::initializatWsa()) 19 | throw std::runtime_error("WebServer: initializat WinSock2 failed."); 20 | #else // Unix 21 | // Ignore SIGPIPE 22 | struct sigaction sa; 23 | sa.sa_handler = SIG_IGN; 24 | sigaction(SIGPIPE, &sa, 0); 25 | #endif 26 | } 27 | 28 | WebServer::~WebServer() 29 | { 30 | #ifdef _WIN32 31 | TcpSocket::cleanUpWsa(); 32 | #endif 33 | SslSocket::cleanUpSsl(); 34 | 35 | m_runnable = false; 36 | } 37 | 38 | int WebServer::start() 39 | { 40 | if(m_listeners.empty() || !m_services || !m_loops.empty() || !m_loopCount) 41 | return -1; 42 | 43 | m_runnable = true; 44 | 45 | EventLoop *loop = nullptr; 46 | for(size_t i = 0; i < m_loopCount; ++i) 47 | { 48 | m_loops.emplace_back(loop = new EventLoop( 49 | m_runnable, m_timeout, m_services.get(), m_handler)); 50 | 51 | loop->registerListeners(m_listeners.begin(), m_listeners.end()); 52 | loop->start(); 53 | } 54 | 55 | return 0; 56 | } 57 | 58 | void WebServer::requestQuit() 59 | { 60 | for(auto &loop : m_loops) 61 | loop->unregisterListeners(m_listeners.begin(), m_listeners.end()); 62 | 63 | for(auto &listener : m_listeners) 64 | listener->close(); 65 | } 66 | 67 | void WebServer::waitForFinished() 68 | { 69 | for(auto &loop : m_loops) 70 | loop->waitForFinished(); 71 | 72 | m_listeners.clear(); 73 | m_loops.clear(); 74 | } 75 | 76 | bool WebServer::listen(const std::string &hostName, const std::string &port, 77 | bool sslEnable, std::pair linger) 78 | { 79 | if(sslEnable && !SslSocket::isSslAvailable()) 80 | return false; 81 | 82 | std::unique_ptr socket(sslEnable ? 83 | static_cast(new SslSocket()) : 84 | static_cast(new TcpSocket())); 85 | 86 | if(!socket->listen(hostName, port)) 87 | return false; 88 | 89 | socket->setOption(SOL_SOCKET, SO_LINGER, linger); 90 | 91 | m_listeners.emplace_back(std::move(socket)); 92 | 93 | return true; 94 | } 95 | -------------------------------------------------------------------------------- /TinyWebServer/README.md: -------------------------------------------------------------------------------- 1 | # Tiny Web Server 2 | 3 | This is a tiny and fast `HTTP/HTTPS` web server in `C++17`. 4 | 5 | The idea of doing this project originated from the TinyWebServer of CS:APP, I hope to learn network programming from this project. 6 | 7 | ## Features 8 | 9 | * Cross-platform: works on Windows, Linux and MacOS. 10 | * Support SSL/TLS. 11 | * Basic `HTTP 1.1` support and some other features like `keep-alive`, `range` and `cache-control`. 12 | * Support gracefully closing of connections. 13 | * Support slow close like `Nginx` smooth upgrade. 14 | * Use timer to close timeout connections. 15 | * Use `POSIX` thread-safety methods to reduce lock usage. 16 | * Using Epoll(Linux) edge-triggered IO multiplex, non-blocking IO, multithreading. 17 | 18 | See my chinese [blog](https://ho-229.github.io/code/network/server/tiny-web-server/) for more details. 19 | 20 | ## Usage 21 | 22 | * Terminal 23 | 24 | ```shell 25 | Usage: ./TinyWebServer [http-port] [https-port] [shared-directory] [certificate-file] [privateKey-file] 26 | 27 | example: ./TinyWebServer 80 443 ./shared_files 28 | ``` 29 | 30 | * After that, you can use `http(s)://localhost/` to access files in shared folder. BTW, your can also access the default services like `/hello` and `/adder`. 31 | 32 | ## Webbench 33 | 34 | > CPU: Intel i5-7500 3.40GHz x 4 35 | > RAM: 8GB 36 | > OS: Manjaro KDE 37 | 38 | * Short connection 39 | 40 | ![image](./screenshot/close.png) 41 | 42 | * Keep alive 43 | 44 | ![image](./screenshot/keep-alive.png) 45 | 46 | ## WebServer Demo 47 | 48 | ```cpp 49 | #include "webserver.h" 50 | #include "http/httpservices.h" 51 | 52 | int main() 53 | { 54 | auto server = std::make_unique(); 55 | auto services = new HttpServices(); 56 | 57 | server->setServices(services); 58 | 59 | // 'Adder' Service 60 | services->onGet("/adder", [](HttpRequest *req, HttpResponse *resp) { 61 | int sum = 0; 62 | for(const auto& arg : req->urlArguments()) 63 | sum += atoi(arg.second.c_str()); 64 | 65 | resp->setRawHeader("Content-type", "text/html; charset=utf-8"); 66 | *resp << "Tiny Web Server" 67 | "

Tiny Web Server / Adder

Result: " 68 | + std::to_string(sum) + "

\n"; 69 | }); 70 | 71 | server->listen("localhost", 80); // HTTP 72 | return server->exec(); 73 | } 74 | ``` 75 | 76 | For more complete example, see [main.cpp](./src/main.cpp). 77 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | paths-ignore: 17 | - '*/README.md' 18 | - 'LICENSE' 19 | 20 | pull_request: 21 | paths-ignore: 22 | - '*/README.md' 23 | - 'LICENSE' 24 | schedule: 25 | - cron: '27 0 * * 2' 26 | 27 | jobs: 28 | analyze: 29 | name: Analyze 30 | runs-on: ubuntu-latest 31 | permissions: 32 | actions: read 33 | contents: read 34 | security-events: write 35 | 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | language: [ 'cpp' ] 40 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 41 | # Learn more: 42 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 43 | 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v2 47 | 48 | # Initializes the CodeQL tools for scanning. 49 | - name: Initialize CodeQL 50 | uses: github/codeql-action/init@v1 51 | with: 52 | languages: ${{ matrix.language }} 53 | # If you wish to specify custom queries, you can do so here or in a config file. 54 | # By default, queries listed here will override any specified in a config file. 55 | # Prefix the list here with "+" to use these queries and those in the config file. 56 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v1 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 https://git.io/JvXDl 65 | 66 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 67 | # and modify them (or add more) to build your code if your project 68 | # uses a compiled language 69 | 70 | #- run: | 71 | # make bootstrap 72 | # make release 73 | 74 | - name: Perform CodeQL Analysis 75 | uses: github/codeql-action/analyze@v1 76 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/httprequest.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #ifndef HTTPREQUEST_H 7 | #define HTTPREQUEST_H 8 | 9 | #include "../util/headermap.h" 10 | 11 | #include 12 | #include 13 | 14 | #define MAX_REQUEST_SIZE 32768 // 32 KiB 15 | 16 | class HttpRequest 17 | { 18 | public: 19 | using Body = std::string_view; 20 | 21 | HttpRequest(); 22 | 23 | void reset(); 24 | 25 | std::string method() const 26 | { return m_isValid ? std::string(m_method) : std::string(); } 27 | 28 | std::string uri() const { return m_isValid ? m_uri : std::string(); } 29 | 30 | const std::vector& urlArguments() const { return m_urlArguments; } 31 | 32 | std::string httpVersion() const 33 | { return m_isValid ? std::string(m_httpVersion) : std::string(); } 34 | 35 | const Body body() const { return m_body; } 36 | 37 | std::string rawHeader(const std::string &name) const 38 | { 39 | const auto it = m_headers.find(name); 40 | return it == m_headers.end() ? std::string() 41 | : std::string(it->second); 42 | } 43 | 44 | const auto& rawHeaders() const { return m_headers; } 45 | 46 | bool isKeepAlive() const { return m_isKeepAlive; } 47 | 48 | bool isValid() const { return m_isValid; } 49 | 50 | friend std::ostream& operator<<(std::ostream &stream, const HttpRequest &req) 51 | { 52 | stream << "Method: " << req.method() 53 | << "|\nURI: " << req.uri() 54 | << "|\nVersion: " << req.httpVersion() 55 | 56 | << "|\n\nUrl Arguments:\n"; 57 | 58 | for(const auto& arg : req.urlArguments()) 59 | stream << arg << "|\n"; 60 | 61 | stream << "\nHeaders:\n"; 62 | 63 | for(const auto& item : req.rawHeaders()) 64 | stream << item.first << "| => |" << item.second << "|\n"; 65 | 66 | stream << "\nBody:\n" << req.body(); 67 | 68 | return stream; 69 | } 70 | 71 | static std::pair parseRange(const std::string &range); 72 | 73 | private: 74 | friend class HttpServices; 75 | 76 | void parse(); 77 | bool parseRequestLine(std::string::size_type &offset, 78 | const std::string &data); 79 | bool parseRequestHeaders(std::string::size_type &offset, 80 | const std::string &data); 81 | 82 | bool m_isValid = false; 83 | 84 | std::string m_rawData; 85 | 86 | std::string_view m_method; 87 | std::string m_uri; 88 | std::string_view m_httpVersion; 89 | Body m_body; 90 | 91 | std::vector m_urlArguments; 92 | 93 | HeaderMap m_headers; 94 | 95 | bool m_isKeepAlive = true; 96 | }; 97 | 98 | #endif // HTTPREQUEST_H 99 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/threadpool.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief Thread Pool 3 | * @author Ho 229 4 | * @date 2021/9/10 5 | * @note Thread pool without std::promise 6 | */ 7 | 8 | #ifndef THREADPOOL_H 9 | #define THREADPOOL_H 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | class ThreadPool 21 | { 22 | public: 23 | explicit ThreadPool(size_t count = std::thread::hardware_concurrency()); 24 | ~ThreadPool(); 25 | 26 | template 27 | void enqueue(F&& f, Args&&... args); 28 | 29 | private: 30 | // need to keep track of threads so we can join them 31 | std::vector m_workers; 32 | // the task queue 33 | std::queue> m_tasks; 34 | 35 | // synchronization 36 | std::mutex m_mutex; 37 | std::atomic_bool m_stop = false; 38 | std::condition_variable m_condition; 39 | }; 40 | 41 | // the constructor just launches some amount of workers 42 | inline ThreadPool::ThreadPool(size_t threads) 43 | { 44 | for(size_t i = 0; i < threads; ++i) 45 | m_workers.emplace_back([this] { 46 | for(;;) 47 | { 48 | std::function task; 49 | 50 | { 51 | std::unique_lock lock(this->m_mutex); 52 | 53 | this->m_condition.wait(lock, 54 | [this]{ return this->m_stop || !this->m_tasks.empty(); }); 55 | 56 | if(this->m_stop && this->m_tasks.empty()) 57 | return; 58 | 59 | task = std::move(this->m_tasks.front()); 60 | this->m_tasks.pop(); 61 | } 62 | 63 | task(); 64 | } 65 | }); 66 | } 67 | 68 | // add new work item to the pool 69 | template 70 | void ThreadPool::enqueue(F&& f, Args&&... args) 71 | { 72 | using return_type = typename std::result_of::type; 73 | 74 | auto task = std::make_shared>( 75 | std::bind(std::forward(f), std::forward(args)...)); 76 | 77 | { 78 | std::unique_lock lock(m_mutex); 79 | 80 | // don't allow enqueueing after stopping the pool 81 | if(m_stop) 82 | throw std::runtime_error("enqueue on stopped ThreadPool"); 83 | 84 | m_tasks.emplace([task](){ (*task)(); }); 85 | } 86 | 87 | m_condition.notify_one(); 88 | } 89 | 90 | // the destructor joins all threads 91 | inline ThreadPool::~ThreadPool() 92 | { 93 | m_stop = true; 94 | 95 | m_condition.notify_all(); 96 | 97 | for(std::thread &worker: m_workers) 98 | worker.join(); 99 | } 100 | 101 | #endif // THREADPOOL_H 102 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/backtrace.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2022/4/2 4 | */ 5 | 6 | #include "backtrace.h" 7 | 8 | extern "C" 9 | { 10 | #ifdef _WIN32 11 | # include 12 | # pragma comment(lib, "dbghelp.lib") 13 | #else 14 | # include 15 | # include 16 | # include 17 | #endif 18 | } 19 | 20 | #ifdef _WIN32 21 | LONG BackTrace::exceptionHandler(_EXCEPTION_POINTERS *exp) 22 | { 23 | std::stringstream stream; 24 | void *buf[ADDR_MAX_NUM]; 25 | 26 | stream << "\nEXCEPTION CODE: " 27 | << exp->ExceptionRecord->ExceptionCode; 28 | 29 | SymInitialize(GetCurrentProcess(), nullptr, true); 30 | 31 | uint16_t frames = CaptureStackBackTrace(0, ADDR_MAX_NUM, buf, nullptr); 32 | std::unique_ptr symbols(reinterpret_cast( 33 | malloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char)))); 34 | 35 | symbols->MaxNameLen = 255; 36 | symbols->SizeOfStruct = sizeof(SYMBOL_INFO); 37 | 38 | stream << "\nNUMBER OF ADDRESSES: " << frames 39 | << "\n======= Stack Trace =======\n"; 40 | 41 | for(uint16_t i = 0; i < frames; ++i) 42 | { 43 | SymFromAddr(GetCurrentProcess(), 44 | reinterpret_cast(buf[i]), nullptr, symbols.get()); 45 | 46 | stream << i << ": [" << symbols->Index << "] " << symbols->Name 47 | << " Addr: " << symbols->Address 48 | << " Reg: " << symbols->Register << "\n"; 49 | } 50 | 51 | stream << "\n"; 52 | 53 | instance().m_handler(stream); 54 | 55 | return EXCEPTION_EXECUTE_HANDLER; 56 | } 57 | #else // *nix 58 | void BackTrace::signalHandler(int signum) 59 | { 60 | std::stringstream stream; 61 | 62 | stream << "\nSIGNAL: " << strsignal(signum) << "\n"; 63 | 64 | void *buf[ADDR_MAX_NUM] = {}; 65 | 66 | int addrNum = backtrace(buf, ADDR_MAX_NUM); 67 | 68 | stream << "NUMBER OF ADDRESSES: " << addrNum 69 | << "\n======= Stack Trace =======\n"; 70 | 71 | std::unique_ptr symbols(backtrace_symbols(buf, addrNum)); 72 | if(!symbols) 73 | { 74 | stream << "BACKTRACE: CANNOT GET BACKTRACE SYMBOLS\n"; 75 | exit(-2); 76 | } 77 | 78 | for(int i = 0; i < addrNum; ++i) 79 | stream << i << ": " << symbols.get()[i] << "\n"; 80 | 81 | stream << "\n"; 82 | 83 | instance().m_handler(stream); 84 | 85 | exit(-1); 86 | } 87 | #endif 88 | 89 | BackTrace::BackTrace() 90 | { 91 | #ifdef _WIN32 92 | SetUnhandledExceptionFilter(exceptionHandler); 93 | #else // *nix 94 | signal(SIGSEGV, signalHandler); 95 | signal(SIGFPE, signalHandler); 96 | #endif 97 | } 98 | 99 | void BackTrace::installHandler(const Handler &handler) 100 | { 101 | instance().m_handler = handler; 102 | } 103 | 104 | BackTrace &BackTrace::instance() 105 | { 106 | static /*thread_local*/ BackTrace backTrace; 107 | return backTrace; 108 | } 109 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/httpservices.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #ifndef HTTPSERVICES_H 7 | #define HTTPSERVICES_H 8 | 9 | #include "httprequest.h" 10 | #include "httpresponse.h" 11 | #include "../abstract/abstractservices.h" 12 | 13 | typedef std::function Handler; 14 | 15 | class HttpServices : public AbstractServices 16 | { 17 | public: 18 | explicit HttpServices(); 19 | ~HttpServices() override; 20 | 21 | void addService(const std::string& method, const std::string& uri, 22 | const Handler& handler); 23 | 24 | /** 25 | * @brief Set method default service 26 | */ 27 | void setDefaultService(const std::string& method, 28 | const Handler& handler); 29 | 30 | /** 31 | * @brief Set default service 32 | */ 33 | void setDefaultService(const Handler& handler) 34 | { m_defaultHandler = handler; } 35 | 36 | void setAutoKeepAlive(bool isEnable) { m_isAutoKeepAlive = isEnable; } 37 | bool isAutoKeepAlive() const { return m_isAutoKeepAlive; } 38 | 39 | inline void onGet(const std::string& uri, const Handler& handler) 40 | { this->addService("GET", uri, handler); } 41 | 42 | inline void onGet(const Handler& handler) 43 | { this->setDefaultService("GET", handler); } 44 | 45 | inline void onHead(const std::string& uri, const Handler& handler) 46 | { this->addService("HEAD", uri, handler); } 47 | 48 | inline void onHead(const Handler& handler) 49 | { this->setDefaultService("HEAD", handler); } 50 | 51 | inline void onPost(const std::string& uri, const Handler& handler) 52 | { this->addService("POST", uri, handler); } 53 | 54 | inline void onPost(const Handler& handler) 55 | { this->setDefaultService("POST", handler); } 56 | 57 | inline void onPut(const std::string& uri, const Handler& handler) 58 | { this->addService("PUT", uri, handler); } 59 | 60 | inline void onPut(const Handler& handler) 61 | { this->setDefaultService("PUT", handler); } 62 | 63 | inline void onDelete(const std::string& uri, const Handler& handler) 64 | { this->addService("DELETE", uri, handler); } 65 | 66 | inline void onDelete(const Handler& handler) 67 | { this->setDefaultService("DELETE", handler); } 68 | 69 | protected: 70 | bool process(AbstractSocket *const socket) const override; 71 | 72 | private: 73 | void callHandler(HttpRequest *const request, 74 | HttpResponse *const response) const; 75 | 76 | struct UriHandler 77 | { 78 | std::unordered_map // Handler 80 | uriHandlers; 81 | 82 | Handler defaultHandler; 83 | }; 84 | 85 | Handler m_defaultHandler; 86 | 87 | std::unordered_map {URI -> Handler} 88 | UriHandler> // URI -> Handler 89 | m_services; 90 | 91 | bool m_isAutoKeepAlive = true; 92 | }; 93 | 94 | #endif // HTTPSERVICES_H 95 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/httpresponse.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #ifndef HTTPRESPONSE_H 7 | #define HTTPRESPONSE_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../define.h" 14 | #include "../util/headermap.h" 15 | 16 | typedef std::pair HttpState; 17 | 18 | class HttpResponse 19 | { 20 | public: 21 | using StringBody = std::string; 22 | 23 | using StreamBody = std::unique_ptr; 24 | 25 | struct FileBody 26 | { 27 | File file; 28 | off_t offset; 29 | size_t count; 30 | }; 31 | 32 | struct Body 33 | { 34 | enum Type 35 | { 36 | Text, 37 | Stream, 38 | File 39 | }; 40 | 41 | Body(const StringBody &text = {}) : text(text), type(Text) {} 42 | Body(StreamBody &&stream) : stream(std::move(stream)), type(Stream) {} 43 | Body(const FileBody &file) : file(file), type(File) {} 44 | 45 | StringBody text; 46 | StreamBody stream; 47 | FileBody file = {}; 48 | 49 | const Type type; 50 | }; 51 | 52 | HttpResponse(); 53 | 54 | void setBody(const StringBody &text); 55 | void setBody(StreamBody &&stream); 56 | void setBody(const FileBody &file); 57 | 58 | template 59 | decltype(auto) visitBody(Visitor &&visitor) 60 | { return visitBody(visitor, m_body); } 61 | 62 | template 63 | static decltype(auto) visitBody(Visitor &&visitor, Body &body) 64 | { 65 | if(body.type == Body::Stream) 66 | return visitor(body.stream); 67 | else if(body.type == Body::File) 68 | return visitor(body.file); 69 | 70 | return visitor(body.text); 71 | } 72 | 73 | void reset(); 74 | 75 | bool isValid() const; 76 | 77 | void setKeepAlive(bool isKeepAlive); 78 | bool isKeepAlive() const { return m_isKeepAlive; } 79 | 80 | void setHttpState(const HttpState& state); 81 | HttpState httpState() const { return m_httpState; } 82 | 83 | template 84 | void setRawHeader(const std::string& name, const std::string& value) 85 | { 86 | if constexpr(isInsert) 87 | m_headers.insert({name, value}); 88 | else 89 | m_headers[name] = value; 90 | } 91 | 92 | std::string rawHeader(const std::string& name) const 93 | { 94 | const auto it = m_headers.find(name); 95 | return it == m_headers.end() ? std::string() : it->second; 96 | } 97 | 98 | template 99 | inline HttpResponse& operator<<(T &&body) 100 | { 101 | this->setBody(std::move(body)); 102 | return *this; 103 | } 104 | 105 | static std::string matchContentType(const std::string &extension); 106 | 107 | static std::string replyRange(std::pair range, 108 | const size_t total, size_t &offset, size_t &count); 109 | 110 | private: 111 | friend class HttpServices; 112 | 113 | void toRawData(std::string& response); 114 | 115 | HttpState m_httpState = {200, "OK"}; 116 | HeaderMap m_headers; 117 | 118 | Body m_body; 119 | 120 | bool m_isKeepAlive = true; 121 | }; 122 | 123 | #endif // HTTPRESPONSE_H 124 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/27 4 | */ 5 | 6 | #ifndef UTIL_H 7 | #define UTIL_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef _WIN32 17 | #define gmtime_s(x, y) gmtime_r(y, x) 18 | #endif 19 | 20 | namespace Util 21 | { 22 | /** 23 | * @brief overloaded 24 | */ 25 | template struct overloaded : Ts... { using Ts::operator()...; }; 26 | template overloaded(Ts...) -> overloaded; 27 | 28 | inline const std::string toGmtFormat(const std::time_t time) 29 | { 30 | std::tm gmt; 31 | std::ostringstream ss; 32 | gmtime_s(&gmt, &time); 33 | ss << std::put_time(&gmt, "%a, %d %b %Y %H:%M:%S GMT"); 34 | return ss.str(); 35 | } 36 | 37 | /** 38 | * @return Current time in GMT format 39 | */ 40 | inline const std::string currentDateString() 41 | { 42 | const auto now = std::chrono::system_clock::now(); 43 | const auto itt = std::chrono::system_clock::to_time_t(now); 44 | 45 | return toGmtFormat(itt); 46 | } 47 | 48 | /** 49 | * @ref https://stackoverflow.com/questions/61030383/how-to-convert-stdfilesystemfile-time-type-to-time-t 50 | */ 51 | template 52 | std::time_t to_time_t(TP tp) 53 | { 54 | using namespace std::chrono; 55 | auto sctp = time_point_cast(tp - TP::clock::now() 56 | + system_clock::now()); 57 | return system_clock::to_time_t(sctp); 58 | } 59 | 60 | template 61 | inline constexpr int strcasecmp(const String &left, 62 | const String &right) 63 | { 64 | if(const auto diff = left.size() - right.size(); diff) 65 | return int(diff); 66 | 67 | #ifdef _WIN32 68 | return _strnicmp(left.data(), right.data(), left.size()); 69 | #else 70 | return ::strncasecmp(left.data(), right.data(), left.size()); 71 | #endif 72 | } 73 | 74 | template 75 | bool referTil(std::string::size_type &offset, 76 | std::string_view &dst, const std::string &src, 77 | const Compare &limit) 78 | { 79 | if(src.empty()) 80 | return false; 81 | 82 | const size_t srcSize = src.size(); 83 | const auto start = offset; 84 | size_t count = 0; 85 | 86 | for(; offset < srcSize; ++offset, ++count) 87 | { 88 | if constexpr(maxCount > 0) 89 | { 90 | if(maxCount <= count) 91 | break; 92 | } 93 | 94 | if(limit(src[offset])) 95 | { 96 | dst = {src.data() + start, count}; 97 | return true; 98 | } 99 | } 100 | 101 | dst = {src.data() + start, count}; 102 | return false; 103 | } 104 | 105 | template 106 | inline void toHex(std::string& buf, T num) 107 | { 108 | std::ostringstream stream; 109 | stream << std::hex << num; 110 | buf += stream.str(); 111 | } 112 | 113 | template 114 | class ScopeFunction 115 | { 116 | const Func m_func; 117 | 118 | public: 119 | explicit ScopeFunction(const Func &func) : m_func(func) {} 120 | ~ScopeFunction() { m_func(); } 121 | }; 122 | } 123 | 124 | #endif // UTIL_H 125 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/eventloop.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/10/3 4 | */ 5 | 6 | #include "tcpsocket.h" 7 | #include "sslsocket.h" 8 | #include "eventloop.h" 9 | #include "../abstract/abstractservices.h" 10 | 11 | EventLoop::EventLoop(const volatile bool &runnable, 12 | const std::chrono::milliseconds &timeout, 13 | AbstractServices *const services, 14 | const EventHandler &handler) : 15 | m_runnable(runnable), 16 | m_timeout(timeout), 17 | m_services(services), 18 | m_handler(handler) 19 | { 20 | m_queue.reserve(512); 21 | m_errorQueue.reserve(512); 22 | } 23 | 24 | EventLoop::~EventLoop() 25 | { 26 | while(auto socket = m_manager.takeFirst()) 27 | { 28 | m_handler(new ConnectEvent(socket, ConnectEvent::Close)); 29 | delete socket; 30 | } 31 | } 32 | 33 | void EventLoop::exec() 34 | { 35 | while(m_runnable && m_epoll.count()) 36 | { 37 | m_epoll.epoll(m_queue, m_errorQueue); 38 | 39 | if(!m_queue.empty()) 40 | this->processQueue(); 41 | 42 | if(!m_errorQueue.empty()) 43 | this->processErrorQueue(); 44 | 45 | // Clean up timeout connections 46 | m_manager.checkout(m_errorQueue); 47 | 48 | if(!m_errorQueue.empty()) 49 | this->processTimeoutQueue(); 50 | } 51 | } 52 | 53 | void EventLoop::processQueue() 54 | { 55 | for(auto& socket : m_queue) 56 | { 57 | if(socket->isListener()) 58 | { 59 | AbstractSocket *client = nullptr; 60 | 61 | while(socket->isValid()) 62 | { 63 | const Socket descriptor = socket->accept(); 64 | 65 | if(!AbstractSocket::isValid(descriptor)) 66 | break; 67 | 68 | client = socket->sslEnable() ? 69 | static_cast(new SslSocket(descriptor)) : 70 | static_cast(new TcpSocket(descriptor)); 71 | 72 | if(!client->isValid()) 73 | { 74 | delete client; 75 | continue; 76 | } 77 | 78 | client->setTimer(m_manager.start(m_timeout, client)); 79 | m_epoll.insert(client); 80 | 81 | m_handler(new ConnectEvent(client, ConnectEvent::Accpet)); 82 | } 83 | } 84 | else 85 | { 86 | if(m_services->process(socket)) 87 | m_manager.restart(socket->timer()); 88 | else // Close 89 | { 90 | m_handler(new ConnectEvent(socket, ConnectEvent::Close)); 91 | m_epoll.erase(socket); 92 | 93 | m_manager.destory(socket->timer()); 94 | delete socket; 95 | } 96 | } 97 | } 98 | 99 | m_queue.resize(0); 100 | } 101 | 102 | void EventLoop::processErrorQueue() 103 | { 104 | for(auto& socket : m_errorQueue) 105 | { 106 | m_epoll.erase(socket); 107 | 108 | if(socket->isListener()) 109 | { 110 | m_handler(new ExceptionEvent(ExceptionEvent::ListenerError, 111 | "An error occurred in the listener")); 112 | continue; 113 | } 114 | else 115 | { 116 | m_handler(new ConnectEvent(socket, ConnectEvent::Close)); 117 | 118 | m_manager.destory(socket->timer()); 119 | delete socket; 120 | } 121 | } 122 | 123 | m_errorQueue.resize(0); 124 | } 125 | 126 | void EventLoop::processTimeoutQueue() 127 | { 128 | for(auto& socket : m_errorQueue) 129 | { 130 | m_epoll.erase(socket); 131 | m_handler(new ConnectEvent(socket, ConnectEvent::Close)); 132 | 133 | delete socket; 134 | } 135 | 136 | m_errorQueue.resize(0); 137 | } 138 | -------------------------------------------------------------------------------- /TinyWebServer/src/abstract/abstractsocket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/6 4 | */ 5 | 6 | #ifndef ABSTRACTSOCKET_H 7 | #define ABSTRACTSOCKET_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../define.h" 14 | #include "../util/timermanager.h" 15 | 16 | extern "C" 17 | { 18 | #ifdef _WIN32 19 | # include 20 | # include 21 | #else 22 | # include 23 | # include 24 | # include 25 | # include 26 | #endif 27 | } 28 | 29 | class AbstractSocket 30 | { 31 | public: 32 | using TimerIter = typename TimerManager::iterator; 33 | 34 | virtual ~AbstractSocket() = default; 35 | 36 | virtual ssize_t read(char *buf, size_t count) = 0; 37 | virtual ssize_t write(const char* buf, size_t count) = 0; 38 | 39 | void read(std::string &buffer); 40 | 41 | template 42 | inline ssize_t write(const String& data) 43 | { return this->write(data.c_str(), data.size()); } 44 | 45 | virtual void close() = 0; 46 | 47 | virtual bool sslEnable() const = 0; 48 | 49 | virtual bool isValid() const = 0; 50 | 51 | bool isListener() const { return m_isListener; } 52 | 53 | #if SOCKET_INFO_ENABLE 54 | std::string hostName() const { return m_hostName; } 55 | std::string port() const { return m_port; } 56 | #endif 57 | 58 | template 59 | int setOption(int level, int option, const T value) 60 | { 61 | return setsockopt(m_descriptor, level, option, 62 | reinterpret_cast(&value), sizeof (T)); 63 | } 64 | 65 | template 66 | T option(int level, int option, int *ret = nullptr) const 67 | { 68 | T value; 69 | socklen_t len = sizeof (T); 70 | 71 | if(ret) 72 | *ret = getsockopt(m_descriptor, level, option, &value, &len); 73 | else 74 | getsockopt(m_descriptor, level, option, &value, &len); 75 | 76 | return value; 77 | } 78 | 79 | void setTimer(const TimerIter &timer) { m_timer = timer; } 80 | TimerIter timer() const { return m_timer; } 81 | 82 | void addTimes() { ++m_times; } 83 | size_t times() const { return m_times; } 84 | 85 | Socket descriptor() const { return m_descriptor; } 86 | 87 | bool sendStream(std::istream *const stream); 88 | 89 | virtual ssize_t sendFile(File file, off_t offset, size_t count) = 0; 90 | 91 | /** 92 | * @ref CS:APP(3) P662: int open_listenfd(char *port) 93 | * @brief Listen on the given port 94 | * @return true when successful 95 | */ 96 | bool listen(const std::string& hostName, const std::string& port); 97 | 98 | Socket accept() const; 99 | 100 | #ifdef _WIN32 101 | static bool initializatWsa(); 102 | static void cleanUpWsa(); 103 | #endif 104 | 105 | static inline constexpr bool isValid(const Socket& socket) 106 | { 107 | #ifdef _WIN32 108 | return socket != Socket(~0); 109 | #else 110 | return socket > 0; 111 | #endif 112 | } 113 | 114 | static inline void close(Socket& socket) 115 | { 116 | Socket descriptor = INVALID_SOCKET; 117 | std::swap(descriptor, socket); 118 | 119 | #ifdef _WIN32 120 | closesocket(descriptor); 121 | #else 122 | ::close(descriptor); 123 | #endif 124 | } 125 | 126 | protected: 127 | explicit AbstractSocket(const Socket socket = INVALID_SOCKET); 128 | 129 | // Disable copy 130 | AbstractSocket(AbstractSocket& other) = delete; 131 | AbstractSocket& operator=(const AbstractSocket& other) = delete; 132 | 133 | Socket m_descriptor = 0; 134 | 135 | size_t m_times = 0; 136 | 137 | TimerIter m_timer; 138 | 139 | bool m_isListener = false; 140 | 141 | #if SOCKET_INFO_ENABLE 142 | std::string m_hostName; 143 | std::string m_port; 144 | #endif 145 | }; 146 | 147 | #endif // ABSTRACTSOCKET_H 148 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/httprequest.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #include "url.h" 7 | #include "httprequest.h" 8 | 9 | #include 10 | 11 | HttpRequest::HttpRequest() 12 | { 13 | 14 | } 15 | 16 | void HttpRequest::parse() 17 | { 18 | std::string::size_type offset = 0; 19 | const size_t size = m_rawData.size(); 20 | 21 | // Request line 22 | if(m_rawData.empty() || !this->parseRequestLine(offset, m_rawData)) 23 | return; 24 | 25 | // Headers 26 | if(!this->parseRequestHeaders(offset, m_rawData)) 27 | return; 28 | 29 | // Body 30 | if(offset < size) 31 | m_body = {m_rawData.data() + offset, size - offset}; 32 | 33 | m_isValid = true; 34 | 35 | // Parse keep-alive 36 | const auto it = m_headers.find("Connection"); 37 | m_isKeepAlive = it == m_headers.end() ? true : 38 | Util::strcasecmp(it->second, "close"); 39 | } 40 | 41 | void HttpRequest::reset() 42 | { 43 | m_isValid = false; 44 | 45 | m_headers.clear(); 46 | m_urlArguments.clear(); 47 | 48 | m_rawData.clear(); 49 | } 50 | 51 | std::pair HttpRequest::parseRange(const std::string &range) 52 | { 53 | const std::regex reg("bytes=(\\d+)-(\\d*)"); 54 | std::smatch results; 55 | 56 | size_t start = 0, end = 0; 57 | if(std::regex_match(range.cbegin(), range.cend(), results, reg)) 58 | { 59 | if(const std::string &&num = results[1]; !num.empty()) 60 | start = std::stoul(num); 61 | 62 | if(const std::string &&num = results[2]; !num.empty()) 63 | end = std::stoul(num); 64 | } 65 | 66 | return {start, end}; 67 | } 68 | 69 | bool HttpRequest::parseRequestLine(std::string::size_type &offset, 70 | const std::string &data) 71 | { 72 | // Method 73 | if(!Util::referTil<8>(offset, m_method, data, [](const char &ch) 74 | { return ch == ' '; })) 75 | return false; 76 | ++offset; 77 | 78 | // URI 79 | std::string_view uri; 80 | if(Util::referTil(offset, uri, data, [](const char &ch) 81 | { return ch == '?' || ch == ' '; })) 82 | { 83 | m_uri = uriUnescape(uri); 84 | 85 | if(data.at(offset) == '?') 86 | { 87 | std::string_view arg; 88 | 89 | do 90 | { 91 | ++offset; 92 | 93 | if(!Util::referTil(offset, arg, data, [](const char &ch) 94 | { return ch == '&' || ch == ' '; })) 95 | return false; 96 | 97 | m_urlArguments.emplace_back(arg); 98 | } 99 | while(data[offset] == '&'); 100 | } 101 | } 102 | else 103 | return false; 104 | ++offset; 105 | 106 | // Version 107 | if(!Util::referTil<10>(offset, m_httpVersion, data, [](const char &ch) 108 | { return ch == '\r'; })) 109 | return false; 110 | offset += 2; 111 | 112 | return true; 113 | } 114 | 115 | bool HttpRequest::parseRequestHeaders(std::string::size_type &offset, 116 | const std::string &data) 117 | { 118 | const auto size = data.size(); 119 | while(offset < size && m_rawData.at(offset) != '\r') 120 | { 121 | std::pair item; 122 | 123 | if(!Util::referTil(offset, item.first, m_rawData, [](const char &ch) 124 | { return ch == ':'; })) 125 | return false; 126 | ++offset; 127 | 128 | // Ignore spaces 129 | while(offset < size && m_rawData.at(offset) == ' ') 130 | ++offset; 131 | 132 | if(!Util::referTil(offset, item.second, m_rawData, [](const char &ch) 133 | { return ch == '\r'; })) 134 | return false; 135 | offset += 2; 136 | 137 | m_headers.emplace(item); 138 | } 139 | 140 | offset += 2; 141 | return true; 142 | } 143 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/httpservices.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #include "httpservices.h" 7 | 8 | #include "../define.h" 9 | #include "../abstract/abstractsocket.h" 10 | 11 | HttpServices::HttpServices() 12 | { 13 | m_defaultHandler = [](HttpRequest *, HttpResponse *resp) 14 | { resp->setHttpState({405, "Method Not Allowed"}); }; 15 | } 16 | 17 | HttpServices::~HttpServices() 18 | { 19 | 20 | } 21 | 22 | void HttpServices::addService(const std::string &method, 23 | const std::string &uri, const Handler &handler) 24 | { 25 | auto it = m_services.find(method); 26 | if(it == m_services.end()) 27 | m_services[method] = {{{uri, handler}}, 28 | {[](HttpRequest *, HttpResponse *resp) { 29 | resp->setHttpState({404, "Not Found"}); }}}; 30 | else 31 | it->second.uriHandlers[uri] = handler; 32 | } 33 | 34 | void HttpServices::setDefaultService(const std::string &method, 35 | const Handler &handler) 36 | { 37 | auto it = m_services.find(method); 38 | if(it == m_services.end()) 39 | m_services[method] = {{}, handler}; 40 | else 41 | it->second.defaultHandler = handler; 42 | } 43 | 44 | bool HttpServices::process(AbstractSocket *const socket) const 45 | { 46 | static thread_local std::unique_ptr request(new HttpRequest); 47 | 48 | socket->read(request->m_rawData); 49 | 50 | request->parse(); 51 | 52 | if(!request->isValid()) 53 | return false; 54 | 55 | if(m_maxTimes) 56 | socket->addTimes(); 57 | 58 | static thread_local std::string buffer; 59 | static thread_local std::unique_ptr response(new HttpResponse); 60 | 61 | const Util::ScopeFunction func([] { 62 | request->reset(); 63 | response->reset(); 64 | buffer.clear(); 65 | }); 66 | 67 | if(m_isAutoKeepAlive) 68 | response->setKeepAlive(request->isKeepAlive()); 69 | 70 | this->callHandler(request.get(), response.get()); 71 | 72 | if(!response->isValid()) 73 | return true; 74 | 75 | response->toRawData(buffer); 76 | 77 | #if defined(__linux__) && TCP_CORK_ENABLE 78 | socket->setOption(IPPROTO_TCP, TCP_CORK, 1); 79 | #endif 80 | 81 | // Write Header and text body 82 | if(socket->write(buffer) != buffer.size()) 83 | return false; 84 | 85 | #if defined(__linux__) && TCP_CORK_ENABLE 86 | socket->setOption(IPPROTO_TCP, TCP_CORK, 0); 87 | #endif 88 | 89 | const bool ok = response->visitBody( 90 | Util::overloaded { 91 | [](const HttpResponse::StringBody &) { return true; }, 92 | 93 | [&socket](HttpResponse::StreamBody &stream) -> bool 94 | { return socket->sendStream(stream.get()); }, 95 | 96 | [&socket](const HttpResponse::FileBody &file) -> bool 97 | { 98 | return socket->sendFile( 99 | file.file, file.offset, file.count) == file.count; 100 | } 101 | }); 102 | 103 | if(!ok) 104 | return false; 105 | 106 | return response->isKeepAlive() && socket->times() <= m_maxTimes; 107 | } 108 | 109 | void HttpServices::callHandler(HttpRequest *const request, 110 | HttpResponse *const response) const 111 | { 112 | // Find Method -> {URI -> Handler} 113 | const auto methodIt = m_services.find(request->method()); 114 | if(methodIt != m_services.end()) 115 | { 116 | // URI -> Handler 117 | const auto handlerIt = methodIt->second.uriHandlers.find(request->uri()); 118 | if(handlerIt == methodIt->second.uriHandlers.end()) // URI not found 119 | methodIt->second.defaultHandler(request, response); // Default handler 120 | else 121 | handlerIt->second(request, response); 122 | 123 | return; 124 | } 125 | 126 | // Method not found 127 | m_defaultHandler(request, response); 128 | } 129 | -------------------------------------------------------------------------------- /TinyWebServer/src/util/timermanager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/20 4 | */ 5 | 6 | #ifndef TIMERMANAGER_H 7 | #define TIMERMANAGER_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define TIMER_THREAD_SAFE 0 17 | 18 | template 19 | class Timer 20 | { 21 | public: 22 | explicit Timer(const TimeType& timeout, const T& data) : 23 | m_userData(std::move(data)), 24 | m_timeout(timeout), 25 | m_deadline(std::chrono::system_clock::now() + timeout) 26 | {} 27 | 28 | inline void reset() 29 | { m_deadline = std::chrono::system_clock::now() + m_timeout; } 30 | 31 | inline const auto& deadline() const { return m_deadline; } 32 | 33 | inline const T& userData() const { return m_userData; } 34 | 35 | inline bool operator<(const Timer &right) const 36 | { return this->m_deadline < right.m_deadline; } 37 | 38 | private: 39 | const T m_userData; 40 | const TimeType& m_timeout; 41 | std::chrono::system_clock::time_point m_deadline; 42 | }; 43 | 44 | template 45 | struct isDuration 46 | : std::false_type 47 | { }; 48 | 49 | template 50 | struct isDuration> 51 | : std::true_type 52 | { }; 53 | 54 | template 55 | 56 | #if __cplusplus > 201703L // C++20 57 | requires isDuration::value && std::is_copy_constructible_v 58 | #endif 59 | 60 | class TimerManager 61 | { 62 | public: 63 | static_assert(isDuration::value, "TimeType must be std::duration."); 64 | static_assert(std::is_copy_constructible_v, "T must be copyable."); 65 | 66 | using TimerItem = Timer; 67 | using iterator = typename std::list::iterator; 68 | 69 | explicit TimerManager() = default; 70 | 71 | iterator start(const TimeType& timeout, const T& data) 72 | { 73 | #if TIMER_THREAD_SAFE 74 | std::unique_lock lock(m_mutex); 75 | #endif 76 | TimerItem timer(timeout, data); 77 | 78 | for(auto it = m_list.end(); it != m_list.begin(); --it) 79 | { 80 | if(*std::prev(it) < timer) 81 | return m_list.emplace(it, timer); 82 | } 83 | 84 | return m_list.emplace(m_list.begin(), timer); 85 | } 86 | 87 | void restart(iterator timerIt) 88 | { 89 | #if TIMER_THREAD_SAFE 90 | std::unique_lock lock(m_mutex); 91 | #endif 92 | timerIt->reset(); 93 | 94 | for(auto it = m_list.end(); it != m_list.begin(); --it) 95 | { 96 | if(*std::prev(it) < *timerIt) 97 | return m_list.splice(it, m_list, timerIt); 98 | } 99 | } 100 | 101 | void destory(iterator it) 102 | { 103 | if(!m_list.empty()) 104 | m_list.erase(it); 105 | } 106 | 107 | void checkout(std::vector& list) 108 | { 109 | #if TIMER_THREAD_SAFE 110 | std::unique_lock lock(m_mutex); 111 | #endif 112 | const auto now = std::chrono::system_clock::now(); 113 | 114 | while(!m_list.empty() && m_list.front().deadline() <= now) 115 | { 116 | list.emplace_back(m_list.front().userData()); 117 | m_list.pop_front(); 118 | } 119 | } 120 | 121 | bool isEmpty() const { return m_list.empty(); } 122 | 123 | size_t size() const { return m_list.size(); } 124 | 125 | T takeFirst() 126 | { 127 | #if TIMER_THREAD_SAFE 128 | std::unique_lock lock(m_mutex); 129 | #endif 130 | if(m_list.empty()) 131 | return {}; 132 | 133 | T ret = m_list.front().userData(); 134 | m_list.pop_front(); 135 | 136 | return ret; 137 | } 138 | 139 | private: 140 | #if TIMER_THREAD_SAFE 141 | std::mutex m_mutex; 142 | #endif 143 | 144 | std::list m_list; 145 | }; 146 | 147 | #endif // TIMERMANAGER_H 148 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/tcpsocket.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/6 4 | */ 5 | 6 | #include "tcpsocket.h" 7 | 8 | extern "C" 9 | { 10 | #if defined (OS_LINUX) // Linux 11 | # include 12 | #elif defined (OS_UNIX) // Unix 13 | # include 14 | #endif 15 | } 16 | 17 | TcpSocket::TcpSocket(const Socket socket) : AbstractSocket(socket) 18 | { 19 | 20 | } 21 | 22 | TcpSocket::~TcpSocket() 23 | { 24 | if(this->TcpSocket::isValid()) 25 | this->TcpSocket::close(); 26 | } 27 | 28 | ssize_t TcpSocket::read(char *buf, size_t count) 29 | { 30 | if(m_isListener || !buf || !count) 31 | return -1; 32 | 33 | ssize_t ret = 0; 34 | size_t leftSize = count; 35 | 36 | do 37 | { 38 | #ifdef _WIN32 39 | if(ret = recv(m_descriptor, buf, leftSize > INT32_MAX ? INT32_MAX : 40 | int(count), 0); ret <= 0) 41 | return ssize_t(count - leftSize); 42 | #else // Unix 43 | if(ret = ::read(m_descriptor, buf, leftSize); ret <= 0) 44 | { 45 | if(errno == EINTR) // Interrupted by signal handler return 46 | continue; // and call read() again 47 | else 48 | return count - leftSize; // errno set by read() 49 | } 50 | #endif 51 | 52 | leftSize -= size_t(ret); 53 | buf += ret; 54 | } 55 | while(leftSize); 56 | 57 | return ssize_t(count); 58 | } 59 | 60 | ssize_t TcpSocket::write(const char *buf, size_t count) 61 | { 62 | if(m_isListener || !buf || !count) 63 | return -1; 64 | 65 | size_t leftSize = count; 66 | ssize_t ret; 67 | 68 | do 69 | { 70 | #ifdef _WIN32 71 | if(ret = send(m_descriptor, buf, leftSize > INT32_MAX ? INT32_MAX : 72 | int(count), 0); ret <= 0) 73 | return ssize_t(count - leftSize); 74 | 75 | #else // Unix 76 | if(ret = ::write(m_descriptor, buf, count); ret <= 0) 77 | { 78 | if(errno == EINTR) // Interrupted by signal handler return 79 | continue; // and call write() again 80 | else 81 | return count - leftSize; // errno set by write() 82 | } 83 | #endif 84 | 85 | leftSize -= size_t(ret); 86 | buf += ret; 87 | } 88 | while(leftSize); 89 | 90 | return ssize_t(count); 91 | } 92 | 93 | void TcpSocket::close() 94 | { 95 | AbstractSocket::close(m_descriptor); 96 | } 97 | 98 | ssize_t TcpSocket::sendFile(File file, off_t offset, size_t count) 99 | { 100 | #if defined (OS_WINDOWS) // Windows 101 | // Create a page space 102 | HANDLE page = CreateFileMapping(file, nullptr, PAGE_READONLY, 103 | static_cast(uint64_t(count) >> 32), 104 | static_cast(count & 0xffffffff), 105 | nullptr); 106 | if(!page) 107 | return -1; 108 | 109 | // Map data from the file 110 | void *ptr = MapViewOfFile(page, FILE_MAP_READ, 111 | static_cast(uint64_t(offset) >> 32), 112 | static_cast( 113 | static_cast(offset) & 0xffffffff), 114 | count); 115 | if(!ptr) 116 | return -1; 117 | 118 | const ssize_t ret = this->write(reinterpret_cast(ptr), count); 119 | 120 | UnmapViewOfFile(ptr); 121 | CloseHandle(page); 122 | 123 | return ret; 124 | #else // *nix 125 | size_t leftSize = count; 126 | 127 | do 128 | { 129 | # if defined (OS_LINUX) // Linux 130 | ssize_t ret = 0; 131 | if(ret = sendfile64(m_descriptor, file, &offset, leftSize); 132 | ret <= 0) 133 | # else // Unix 134 | off_t ret = leftSize; 135 | if(sendfile(m_descriptor, file, offset, &ret, nullptr, 0) < 0 || !ret) 136 | # endif 137 | return count - leftSize; 138 | 139 | leftSize -= size_t(ret); 140 | } 141 | while(leftSize); 142 | 143 | return count; 144 | #endif 145 | } 146 | -------------------------------------------------------------------------------- /TinyWebServer/src/http/httpresponse.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/26 4 | */ 5 | 6 | #include "httpresponse.h" 7 | 8 | HttpResponse::HttpResponse() 9 | { 10 | 11 | } 12 | 13 | void HttpResponse::setBody(const StringBody &text) 14 | { 15 | if(text.empty()) 16 | return; 17 | 18 | m_headers["Content-Length"] = std::to_string(text.size()); 19 | 20 | new (&m_body) Body(text); 21 | } 22 | 23 | void HttpResponse::setBody(StreamBody &&stream) 24 | { 25 | if(stream->bad()) 26 | return; 27 | 28 | const auto start = stream->tellg(); 29 | stream->seekg(0, std::ios::end); 30 | 31 | if(const auto size = stream->tellg(); size) 32 | m_headers["Content-Length"] = std::to_string(size); 33 | 34 | stream->seekg(start); 35 | 36 | new (&m_body) Body(std::move(stream)); 37 | } 38 | 39 | void HttpResponse::setBody(const FileBody &file) 40 | { 41 | if(!file.count) 42 | return; 43 | 44 | m_headers["Content-Length"] = std::to_string(file.count); 45 | 46 | new (&m_body) Body(file); 47 | } 48 | 49 | void HttpResponse::reset() 50 | { 51 | new (&m_body) Body(); 52 | 53 | m_headers.clear(); 54 | 55 | m_httpState = {200, "OK"}; 56 | 57 | m_isKeepAlive = true; 58 | } 59 | 60 | bool HttpResponse::isValid() const 61 | { 62 | return m_httpState.first >= 200 && m_httpState.first <= 500 && 63 | !m_httpState.second.empty(); 64 | } 65 | 66 | void HttpResponse::setKeepAlive(bool isKeepAlive) 67 | { 68 | m_headers["Connection"] = isKeepAlive ? "keep-alive" : "close"; 69 | m_isKeepAlive = isKeepAlive; 70 | } 71 | 72 | void HttpResponse::setHttpState(const HttpState &state) 73 | { 74 | if(state.second.empty()) 75 | return; 76 | 77 | m_httpState = state; 78 | } 79 | 80 | std::string HttpResponse::matchContentType(const std::string &extension) 81 | { 82 | static const std::unordered_map mimeTypes 83 | { 84 | {".js", "application/javascript"}, 85 | {".json", "application/json"}, 86 | {".pdf", "application/pdf"}, 87 | {".swf", "application/x-shockwave-flash"}, 88 | {".xml", "application/xml"}, 89 | {".htm", "text/html"}, 90 | {".html", "text/html"}, 91 | {".css", "text/css"}, 92 | {".txt", "text/plain"}, 93 | {".png", "image/png"}, 94 | {".jpg", "image/jpg"}, 95 | {".jpeg", "image/jpeg"}, 96 | {".gif", "image/gif"}, 97 | {".svg", "image/svg+xml"}, 98 | {".ico", "image/x-icon"}, 99 | }; 100 | 101 | const auto it = mimeTypes.find(extension); 102 | return it == mimeTypes.end() ? "" : std::string{it->second}; 103 | } 104 | 105 | std::string HttpResponse::replyRange(std::pair range, 106 | const size_t total, size_t &offset, size_t &count) 107 | { 108 | // To valid range 109 | if(range.first >= total || range.second >= total) 110 | { 111 | range.first = 0; 112 | range.second = total - 1; 113 | } 114 | else if(range.second <= range.first) 115 | range.second = total - 1; 116 | 117 | offset = range.first; 118 | count = range.second - range.first + 1; 119 | 120 | return std::string("bytes ") 121 | .append(std::to_string(range.first)).append("-") 122 | .append(std::to_string(range.second)).append("/") 123 | .append(std::to_string(total)); 124 | } 125 | 126 | void HttpResponse::toRawData(std::string &response) 127 | { 128 | // Response line 129 | response.append("HTTP/1.1 ", 9).append(std::to_string(m_httpState.first)) 130 | .append(" ", 1).append(m_httpState.second).append("\r\n", 2); 131 | 132 | // Headers 133 | response.append("Date: ", 6).append(Util::currentDateString()).append("\r\n", 2); 134 | response.append("Server: TinyWebServer\r\n", 23); 135 | 136 | for(const auto &[key, value] : m_headers) 137 | response.append(key).append(": ", 2).append(value).append("\r\n", 2); 138 | 139 | // Body 140 | if(m_body.type == Body::Text) 141 | response.append("\r\n", 2).append(m_body.text); 142 | else 143 | response.append("\r\n", 2); 144 | } 145 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/epoll.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/20 4 | */ 5 | 6 | #include "epoll.h" 7 | 8 | #include 9 | 10 | Epoll::Epoll() 11 | { 12 | #if defined (OS_WINDOWS) 13 | m_connections.reserve(512); 14 | #elif defined (OS_LINUX) 15 | m_epoll = epoll_create1(0); 16 | #else 17 | m_kqueue = kqueue(); 18 | #endif 19 | } 20 | 21 | Epoll::~Epoll() 22 | { 23 | #if defined (OS_LINUX) 24 | close(m_epoll); 25 | #elif defined (OS_UNIX) 26 | close(m_kqueue); 27 | #endif 28 | } 29 | 30 | void Epoll::insert(AbstractSocket * const socket, bool exclusive) 31 | { 32 | #if defined (OS_WINDOWS) 33 | static_cast(exclusive); // Unused 34 | # if EPOLL_THREAD_SAFE 35 | std::unique_lock lock(m_mutex); 36 | # endif 37 | m_events.emplace_back(pollfd{socket->descriptor(), POLLIN, 0}); 38 | 39 | m_connections.insert(ConnectionItem(socket->descriptor(), socket)); 40 | #else // *nix 41 | # if defined (OS_LINUX) // Linux 42 | epoll_event event{(exclusive ? EPOLLEXCLUSIVE | EPOLLIN : EPOLLIN | EPOLLRDHUP) 43 | | EPOLLET, socket}; 44 | epoll_ctl(m_epoll, EPOLL_CTL_ADD, socket->descriptor(), &event); 45 | # else // Unix 46 | struct kevent event; 47 | EV_SET(&event, socket->descriptor(), EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, socket); 48 | kevent(m_kqueue, &event, 1, nullptr, 0, nullptr); 49 | # endif 50 | ++m_count; 51 | #endif 52 | } 53 | 54 | void Epoll::erase(AbstractSocket * const socket) 55 | { 56 | #if defined (OS_WINDOWS) // Windows 57 | # if EPOLL_THREAD_SAFE 58 | std::unique_lock lock(m_mutex); 59 | # endif 60 | // Remove socket from pollfd queue 61 | const auto it = std::find_if(m_events.cbegin(), m_events.cend(), 62 | [socket](const pollfd &event) -> bool { 63 | return socket->descriptor() == event.fd; 64 | }); 65 | if(it != m_events.cend()) 66 | m_events.erase(it); 67 | 68 | m_connections.erase(socket->descriptor()); 69 | #else // *nix 70 | # if defined (OS_LINUX) // Linux 71 | epoll_ctl(m_epoll, EPOLL_CTL_DEL, socket->descriptor(), nullptr); 72 | # else // Unix 73 | struct kevent event; 74 | EV_SET(&event, socket->descriptor(), EVFILT_READ, EV_DELETE | EV_DISABLE, 0, 0, socket); 75 | kevent(m_kqueue, &event, 1, nullptr, 0, nullptr); 76 | # endif 77 | --m_count; 78 | #endif 79 | } 80 | 81 | void Epoll::epoll(std::vector &events, 82 | std::vector &errorEvents) 83 | { 84 | #if defined (OS_WINDOWS) // Windows 85 | if(m_events.empty()) 86 | return; 87 | 88 | # if EPOLL_THREAD_SAFE 89 | m_mutex.lock(); 90 | # endif 91 | auto temp = m_events; 92 | # if EPOLL_THREAD_SAFE 93 | m_mutex.unlock(); 94 | # endif 95 | 96 | if(WSAPoll(&temp[0], ULONG(temp.size()), EPOLL_WAIT_TIMEOUT) <= 0) 97 | return; 98 | 99 | for(const auto &item : temp) 100 | { 101 | const auto it = m_connections.find(item.fd); 102 | if(it == m_connections.end()) 103 | continue; 104 | 105 | if(item.revents & POLLIN) 106 | events.emplace_back(it->second); 107 | else if(item.revents & POLLERR || item.revents & POLLHUP) 108 | errorEvents.emplace_back(it->second); 109 | } 110 | #else // *nix 111 | # if defined (OS_LINUX) // Linux 112 | int ret = -1; 113 | if((ret = epoll_wait(m_epoll, m_eventBuf, EPOLL_MAX_EVENTS, EPOLL_WAIT_TIMEOUT)) <= 0) 114 | return; 115 | 116 | epoll_event *item = nullptr; 117 | for(int i = 0; i < ret; ++i) 118 | { 119 | item = m_eventBuf + i; 120 | 121 | if(item->events & EPOLLERR || item->events & EPOLLHUP || item->events & EPOLLRDHUP) 122 | errorEvents.emplace_back(reinterpret_cast(item->data.ptr)); 123 | else if(item->events & EPOLLIN) 124 | events.emplace_back(reinterpret_cast(item->data.ptr)); 125 | } 126 | # else // Unix 127 | const timespec timeout{0, EPOLL_WAIT_TIMEOUT * 1000'000}; 128 | int ret = -1; 129 | if((ret = kevent(m_kqueue, nullptr, 0, m_eventBuf, EPOLL_MAX_EVENTS, &timeout)) <= 0) 130 | return; 131 | 132 | struct kevent *item = nullptr; 133 | for(int i = 0; i < ret; ++i) 134 | { 135 | item = m_eventBuf + i; 136 | 137 | if(item->flags & EV_EOF || item->flags & EV_ERROR) 138 | errorEvents.emplace_back(reinterpret_cast(item->udata)); 139 | else if(item->filter == EVFILT_READ) 140 | events.emplace_back(reinterpret_cast(item->udata)); 141 | } 142 | # endif 143 | #endif 144 | } 145 | -------------------------------------------------------------------------------- /TinyWebServer/src/core/sslsocket.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/8/7 4 | */ 5 | 6 | #include "sslsocket.h" 7 | 8 | #include 9 | 10 | extern "C" 11 | { 12 | #ifdef _WIN32 13 | #else // Unix 14 | # include 15 | # include 16 | # include 17 | # include 18 | #endif 19 | } 20 | 21 | SSL_CTX* SslSocket::sslContext = nullptr; 22 | 23 | SslSocket::SslSocket(const Socket socket) : 24 | AbstractSocket(socket), 25 | m_ssl(SSL_new(sslContext)) 26 | { 27 | if(!AbstractSocket::isValid(m_descriptor)) 28 | return; 29 | 30 | SSL_set_fd(m_ssl, int(m_descriptor)); 31 | 32 | if(SSL_accept(m_ssl) < 0) 33 | { 34 | ERR_print_errors_fp(stdout); 35 | AbstractSocket::close(m_descriptor); 36 | SSL_free(m_ssl); 37 | 38 | m_ssl = nullptr; 39 | } 40 | } 41 | 42 | SslSocket::~SslSocket() 43 | { 44 | if(this->SslSocket::isValid()) 45 | this->SslSocket::close(); 46 | } 47 | 48 | ssize_t SslSocket::read(char *buf, size_t count) 49 | { 50 | if(m_isListener || !m_ssl || !buf || !count) 51 | return -1; 52 | 53 | ssize_t ret = 0; 54 | size_t leftSize = count; 55 | 56 | do 57 | { 58 | if(ret = SSL_read(m_ssl, buf, count > INT32_MAX ? INT32_MAX : int(count)); 59 | ret <= 0) 60 | return ret; 61 | 62 | leftSize -= size_t(ret); 63 | buf += ret; 64 | } 65 | while(leftSize); 66 | 67 | return ssize_t(count); 68 | } 69 | 70 | ssize_t SslSocket::write(const char *buf, size_t count) 71 | { 72 | if(m_isListener || !m_ssl || !buf || !count) 73 | return -1; 74 | 75 | ssize_t ret = 0; 76 | size_t leftSize = count; 77 | 78 | do 79 | { 80 | if(ret = SSL_write(m_ssl, buf, count > INT32_MAX ? INT32_MAX : int(count)); 81 | ret <= 0) 82 | return ret; 83 | 84 | leftSize -= size_t(ret); 85 | buf += ret; 86 | } 87 | while(leftSize); 88 | 89 | return ssize_t(count); 90 | } 91 | 92 | void SslSocket::close() 93 | { 94 | if(!m_ssl) 95 | return; 96 | 97 | SSL_shutdown(m_ssl); 98 | SSL_free(m_ssl); 99 | 100 | AbstractSocket::close(m_descriptor); 101 | 102 | m_ssl = nullptr; 103 | } 104 | 105 | ssize_t SslSocket::sendFile(File file, off_t offset, size_t count) 106 | { 107 | #ifdef _WIN32 108 | // Create a page space 109 | HANDLE page = CreateFileMapping(file, nullptr, PAGE_READONLY, 110 | static_cast(uint64_t(count) >> 32), 111 | static_cast(count & 0xffffffff), 112 | nullptr); 113 | if(!page) 114 | return -1; 115 | 116 | // Map data from the file 117 | void *ptr = MapViewOfFile(page, FILE_MAP_READ, 118 | static_cast(uint64_t(offset) >> 32), 119 | static_cast( 120 | static_cast(offset) & 0xffffffff), 121 | count); 122 | if(!ptr) 123 | return -1; 124 | 125 | const ssize_t ret = this->write(reinterpret_cast(ptr), count); 126 | 127 | UnmapViewOfFile(ptr); 128 | CloseHandle(page); 129 | 130 | return ret; 131 | #else 132 | char *ptr = static_cast( 133 | mmap(nullptr, count, PROT_READ, MAP_SHARED, file, offset)); 134 | const int ret = this->write(ptr, count); 135 | munmap(ptr, count); 136 | 137 | return ret; 138 | #endif 139 | } 140 | 141 | bool SslSocket::initializatSsl(const std::string& certFile, 142 | const std::string& privateKey) 143 | { 144 | if(sslContext) 145 | return true; 146 | 147 | SSL_library_init(); 148 | OpenSSL_add_all_algorithms(); 149 | SSL_load_error_strings(); 150 | 151 | SSL_CTX *ctx = nullptr; 152 | 153 | if(!(ctx = SSL_CTX_new(SSLv23_server_method()))) 154 | return false; 155 | 156 | if(SSL_CTX_use_certificate_file(ctx, certFile.c_str(), 157 | SSL_FILETYPE_PEM) <= 0 || 158 | SSL_CTX_use_PrivateKey_file(ctx, privateKey.c_str(), 159 | SSL_FILETYPE_PEM) <= 0 || 160 | !SSL_CTX_check_private_key(ctx)) 161 | { 162 | ERR_print_errors_fp(stdout); 163 | SSL_CTX_free(ctx); 164 | return false; 165 | } 166 | 167 | sslContext = ctx; 168 | 169 | return true; 170 | } 171 | 172 | std::string SslSocket::sslVersion() 173 | { 174 | return OpenSSL_version(OPENSSL_VERSION); 175 | } 176 | 177 | void SslSocket::cleanUpSsl() 178 | { 179 | if(!sslContext) 180 | return; 181 | 182 | SSL_CTX_free(sslContext); 183 | sslContext = nullptr; 184 | } 185 | -------------------------------------------------------------------------------- /TinyWebServer/src/abstract/abstractsocket.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/11/21 4 | */ 5 | 6 | #include "abstractsocket.h" 7 | 8 | extern "C" 9 | { 10 | #ifdef _WIN32 11 | #else 12 | # include 13 | # include 14 | # include 15 | # include 16 | #endif 17 | } 18 | 19 | AbstractSocket::AbstractSocket(const Socket socket) : m_descriptor(socket) 20 | { 21 | #if SOCKET_INFO_ENABLE 22 | if(!isValid(socket)) 23 | return; 24 | 25 | // initialize information about socket 26 | sockaddr_in addr; 27 | socklen_t len = sizeof (addr); 28 | 29 | getpeername(m_descriptor, reinterpret_cast(&addr), &len); 30 | 31 | char hostName[32]; 32 | m_hostName = inet_ntop(addr.sin_family, &addr.sin_addr, 33 | hostName, sizeof (hostName)); 34 | m_port = std::to_string(ntohs(addr.sin_port)); 35 | #endif 36 | } 37 | 38 | void AbstractSocket::read(std::string &buffer) 39 | { 40 | ssize_t ret = 0; 41 | 42 | do 43 | { 44 | const auto start = buffer.size(); 45 | buffer.resize(start + SOCKET_BUF_SIZE); // Alloc space 46 | 47 | if(ret = this->read(buffer.data() + start, SOCKET_BUF_SIZE); ret <= 0) 48 | buffer.resize(start); 49 | else if(const auto newSize = start + size_t(ret); newSize != buffer.size()) 50 | buffer.resize(newSize); // Fit space 51 | } 52 | while(ret == SOCKET_BUF_SIZE); 53 | } 54 | 55 | bool AbstractSocket::sendStream(std::istream * const stream) 56 | { 57 | if(!stream) 58 | return false; 59 | 60 | static thread_local std::unique_ptr buffer(new char[SOCKET_BUF_SIZE]()); 61 | 62 | while(!stream->eof()) 63 | { 64 | stream->read(buffer.get(), SOCKET_BUF_SIZE); 65 | if(this->write(buffer.get(), size_t(stream->gcount())) != stream->gcount()) 66 | return false; 67 | } 68 | 69 | return true; 70 | } 71 | 72 | bool AbstractSocket::listen(const std::string &hostName, const std::string &port) 73 | { 74 | if(m_isListener || hostName.empty() || port.empty()) 75 | return false; 76 | 77 | addrinfo hints, *addrList, *it; 78 | int optval = 1; 79 | 80 | // Get a list of potential server addresses 81 | memset(&hints, 0, sizeof(addrinfo)); 82 | hints.ai_socktype = SOCK_STREAM; // Accept connections 83 | hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG // ... on any IP address 84 | | AI_NUMERICSERV; // ... using port number 85 | getaddrinfo(hostName.c_str(), port.c_str(), &hints, &addrList); 86 | 87 | // Walk the list for one that we can bind to 88 | for(it = addrList; it; it = it->ai_next) 89 | { 90 | // Create a socket descriptor 91 | if(!AbstractSocket::isValid( 92 | (m_descriptor = socket(it->ai_family, it->ai_socktype, it->ai_protocol)))) 93 | continue; 94 | 95 | // Eliminates "Address already in use" error from bind 96 | this->setOption(SOL_SOCKET, SO_REUSEADDR, optval); 97 | 98 | // Bind the descriptor to the address 99 | if(!bind(m_descriptor, it->ai_addr, socklen_t(it->ai_addrlen))) 100 | break; // Success 101 | 102 | AbstractSocket::close(m_descriptor); // Bind failed, try the next 103 | } 104 | 105 | // Clean up 106 | freeaddrinfo(addrList); 107 | if(!it) // No address worked 108 | return false; 109 | 110 | // Make it a listening socket ready to accpet connection requests 111 | if(::listen(m_descriptor, LISTENQ) < 0) 112 | { 113 | AbstractSocket::close(m_descriptor); 114 | return false; 115 | } 116 | 117 | // Set non-blocking 118 | #ifdef OS_WINDOWS 119 | u_long mode = 1; 120 | ioctlsocket(m_descriptor, FIONBIO, &mode); 121 | #else 122 | int flags; 123 | if((flags = fcntl(m_descriptor, F_GETFL, nullptr)) < 0 || 124 | fcntl(m_descriptor, F_SETFL, flags | O_NONBLOCK) == -1) 125 | return false; 126 | 127 | //this->setOption(IPPROTO_TCP, TCP_NODELAY, 1); 128 | #endif 129 | 130 | #if SOCKET_INFO_ENABLE 131 | m_hostName = hostName; 132 | m_port = port; 133 | #endif 134 | 135 | m_isListener = true; 136 | return true; 137 | } 138 | 139 | Socket AbstractSocket::accept() const 140 | { 141 | if(!m_isListener) 142 | return {}; 143 | 144 | sockaddr_storage addr; 145 | socklen_t len = sizeof(addr); 146 | 147 | #if defined (OS_WINDOWS) // Windows 148 | const Socket socket = ::accept( 149 | m_descriptor, reinterpret_cast(&addr), &len); 150 | #elif defined (OS_LINUX) // Linux 151 | const Socket socket = ::accept4( 152 | m_descriptor, reinterpret_cast(&addr), &len, O_NONBLOCK); 153 | #else // Unix 154 | const Socket socket = ::accept( 155 | m_descriptor, reinterpret_cast(&addr), &len); 156 | 157 | int flags; 158 | if((flags = fcntl(m_descriptor, F_GETFL, nullptr)) < 0 || 159 | fcntl(m_descriptor, F_SETFL, flags | O_NONBLOCK) == -1) 160 | return INVALID_SOCKET; 161 | #endif 162 | 163 | return socket; 164 | } 165 | 166 | #ifdef _WIN32 167 | bool AbstractSocket::initializatWsa() 168 | { 169 | WSAData info; 170 | 171 | return !(WSAStartup(MAKEWORD(2, 2), &info) || 172 | LOBYTE(info.wVersion) != 2 || HIBYTE(info.wVersion) != 2); 173 | } 174 | 175 | void AbstractSocket::cleanUpWsa() 176 | { 177 | WSACleanup(); 178 | } 179 | #endif 180 | 181 | -------------------------------------------------------------------------------- /TinyWebServer/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Ho 229 3 | * @date 2021/7/25 4 | */ 5 | 6 | #define PROFILER_ENABLE 0 7 | #define _GLIBCXX_SANITIZE_VECTOR 0 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #if PROFILER_ENABLE 15 | # include 16 | #endif 17 | 18 | #include "webserver.h" 19 | #include "util/util.h" 20 | #include "core/sslsocket.h" 21 | #include "util/backtrace.h" 22 | #include "http/httpservices.h" 23 | #include "util/sharedfilepool.h" 24 | 25 | #ifdef _WIN32 26 | # if _MSC_VER >= 1600 27 | # pragma execution_character_set("utf-8") 28 | # endif 29 | #endif 30 | 31 | static auto server = std::make_unique(); 32 | 33 | void signalHandler(int signum) 34 | { 35 | if(signum == SIGINT) 36 | server->quit(); 37 | #ifdef _WIN32 38 | else if(signum == SIGBREAK) 39 | #else 40 | else if(signum == SIGUSR2) 41 | #endif 42 | server->requestQuit(); 43 | } 44 | 45 | int main(int argc, char** argv) 46 | { 47 | std::cout << "Welcome to Tiny Web Server.[" << Util::currentDateString() 48 | << "]\nOpenSSL version: " << SslSocket::sslVersion() << "\n"; 49 | 50 | // -help, --help or other 51 | if(argc <= 2 || std::string_view("-help") == argv[1]) 52 | { 53 | std::cerr << "Usage: " << argv[0] << 54 | " [http-port] [https-port] [shared-directory]" 55 | " [certificate-file] [privateKey-file]\n\n" 56 | "example: ./TinyWebServer 80 443 ./shared_files\n"; 57 | return 1; 58 | } 59 | 60 | if(argc == 6) 61 | { 62 | if(SslSocket::initializatSsl(argv[4], argv[5])) 63 | std::cerr << "OpenSSL initializat successful.\n"; 64 | else 65 | std::cerr << "OpenSSL initializat failed.\n"; 66 | } 67 | 68 | BackTrace::installHandler([](const std::stringstream &stream) { 69 | std::cerr << stream.rdbuf(); 70 | }); 71 | 72 | auto services = new HttpServices; 73 | server->setServices(services); 74 | 75 | // server->installEventHandler([](Event *event) { 76 | // if(event->type() == Event::ConnectEvent) 77 | // { 78 | // ConnectEvent *accept = static_cast(event); 79 | 80 | // auto socket = accept->socket(); 81 | 82 | // if(socket->sslEnable()) 83 | // std::cerr << "[SSL] "; 84 | // else 85 | // std::cerr << "[TCP] "; 86 | 87 | // if(accept->state() == ConnectEvent::Accpet) 88 | // std::cerr << "Accepted connection descriptor "; 89 | // else 90 | // std::cerr << "Connection closed descriptor "; 91 | 92 | // std::cerr << socket->descriptor() <<"\n"; 93 | // } 94 | // else if(event->type() == Event::ExceptionEvent) 95 | // { 96 | // ExceptionEvent *exception = static_cast(event); 97 | 98 | // switch (exception->error()) 99 | // { 100 | // case ExceptionEvent::ListenerError: 101 | // std::cerr << exception->message(); 102 | // break; 103 | // case ExceptionEvent::UnknownError: 104 | // std::cerr << "Unknown error.\n"; 105 | // break; 106 | // } 107 | // } 108 | // }); 109 | 110 | // Add "Adder" Service 111 | services->onGet("/adder", [](HttpRequest *req, HttpResponse *resp) { 112 | int sum = 0; 113 | for(const auto& arg : req->urlArguments()) 114 | sum += atoi(arg.c_str()); 115 | 116 | resp->setRawHeader("Content-Type", "text/html; charset=utf-8"); 117 | 118 | resp->setBody("Tiny Web Server" 119 | "

Tiny Web Server / Adder

Result: " 120 | + std::to_string(sum) + "

\n"); 121 | }); 122 | 123 | services->onGet("/hello", [](HttpRequest *, HttpResponse *resp) { 124 | resp->setRawHeader("Content-Type", "text/html; charset=utf-8"); 125 | *resp << "Hello world.\n"; 126 | }); 127 | 128 | services->onPost("/post", [](HttpRequest *req, HttpResponse *resp) { 129 | std::cout << "post data: " << req->body() << "\n"; 130 | resp->setBody(std::string(req->body())); 131 | }); 132 | 133 | constexpr auto build404Response = [](HttpResponse *resp) { 134 | resp->setRawHeader("Content-Type", "text/html; charset=utf-8"); 135 | resp->setBody("

Tiny Web Server

" 136 | "404 Not Found" 137 | "
∑(っ°Д°;)っ

\n"); 138 | resp->setHttpState({404, "Not Found"}); 139 | }; 140 | 141 | std::unique_ptr pool; 142 | if(argc > 3 && fs::is_directory(argv[3])) 143 | { 144 | pool.reset(new SharedFilePool(argv[3])); 145 | 146 | services->onHead([&pool](HttpRequest *req, HttpResponse *resp) { 147 | if(auto ret = pool->get(req->uri()); ret.has_value()) 148 | { 149 | resp->setRawHeader("Content-Length", 150 | std::to_string(ret->fileSize)); 151 | if(const auto type = HttpResponse::matchContentType(ret->extension); 152 | !type.empty()) 153 | resp->setRawHeader("Content-Type", type); 154 | } 155 | else 156 | resp->setHttpState({404, "Not Found"}); 157 | }); 158 | 159 | services->onGet([&pool, &build404Response] 160 | (HttpRequest *req, HttpResponse *resp) { 161 | std::string &&uri = req->uri(); 162 | 163 | if(uri.back() == '/') 164 | uri.append("index.html"); 165 | 166 | if(auto ret = pool->get(uri); ret.has_value()) 167 | { 168 | const auto file = ret.value(); 169 | 170 | if(const auto type = HttpResponse::matchContentType(file.extension); 171 | !type.empty()) 172 | resp->setRawHeader("Content-Type", type); 173 | 174 | size_t offset = 0, count = file.fileSize; 175 | 176 | resp->setRawHeader("Accept-Ranges", "bytes"); 177 | if(const auto value = req->rawHeader("Range"); value.empty()) 178 | { 179 | // Cache control 180 | resp->setRawHeader("Last-Modified", file.lastModified); 181 | if(req->rawHeader("If-Modified-Since") == file.lastModified) 182 | { 183 | resp->setRawHeader("Cache-Control", "max-age=0, public"); 184 | resp->setHttpState({304, "Not Modified"}); 185 | return; 186 | } 187 | else 188 | resp->setRawHeader("Cache-Control", "public"); 189 | } 190 | else 191 | { 192 | // Range 193 | const auto range = HttpRequest::parseRange(value); 194 | resp->setRawHeader("Content-Range", 195 | HttpResponse::replyRange( 196 | range, file.fileSize, offset, count)); 197 | 198 | resp->setHttpState({206, "Partial Content"}); 199 | } 200 | 201 | resp->setBody(HttpResponse::FileBody{ 202 | file.file, off_t(offset), count}); 203 | } 204 | else 205 | build404Response(resp); 206 | }); 207 | 208 | std::cout << "Shared directory: " << pool->root() << ".\n"; 209 | } 210 | else 211 | { 212 | services->onGet([&build404Response](HttpRequest *, HttpResponse *resp) { 213 | build404Response(resp); }); 214 | } 215 | 216 | if(argc >= 2) // HTTP 217 | { 218 | if(server->listen(ANY_HOST, argv[1], false)) 219 | std::cout << "Listening HTTP port: " << argv[1] << "\n"; 220 | else 221 | std::cerr << "Listen 0.0.0.0:" + std::string(argv[1]) 222 | + " failed, please rerun with an administrator.\n"; 223 | } 224 | 225 | if(argc >= 3 && SslSocket::isSslAvailable()) // HTTPS 226 | { 227 | if(server->listen(ANY_HOST, argv[2], true)) 228 | std::cout << "Listening HTTPS port: " << argv[2] << "\n"; 229 | else 230 | std::cerr << "Listen 0.0.0.0:" + std::string(argv[2]) 231 | + " failed, please rerun with an administrator.\n"; 232 | } 233 | 234 | std::cout << "\n"; 235 | 236 | signal(SIGINT, signalHandler); 237 | 238 | #if PROFILER_ENABLE 239 | ProfilerStart("test_capture.prof"); 240 | #endif 241 | 242 | const int ret = server->exec(); 243 | 244 | #if PROFILER_ENABLE 245 | ProfilerStop(); 246 | #endif 247 | 248 | std::cout << "\nExit.\n"; 249 | 250 | return ret; 251 | } 252 | --------------------------------------------------------------------------------