Skip to content

C++20协程和基于io_uringの百万并发服务器; 可异步读写, 支持Transfer-Encoding分块编码传输文件; 基于压缩前缀树编写的路由, 支持通配符解析;+ http/https/websocket、socks5代理、Json解析、Json静态反射到结构体(可以无宏反射到聚合类), 封装了线程安全的LFUCache和LRUCache, 静态枚举映射, 支持STL容器的print/toString等

License

Notifications You must be signed in to change notification settings

HengXin666/HXLibs

Repository files navigation

HXLibs

  • 基于io_uring+协程的http/https服务器, 基于压缩前缀树的路由, 支持http/https解析, WebSocket协议, 支持Transfer-Encoding分块编码传输文件.

  • 客户端提供了简单的协程的request方法(API), 并且支持使用socks5代理. 支持http/https, 以及解析Transfer-Encoding分块编码的响应体

  • 封装了LFUCacheLRUCache, 并且提供有线程安全的版本, 支持透明查找(C++14), 支持原地构造emplace. (支持C++11的版本: HXCache)

  • Json解析:

    • 支持静态反射注册到结构体, 实现toString生成json字符串和自动生成的构造函数实现jsonString/jsonObj赋值到结构体, 只需要一个宏即可实现!

    • 支持对聚合类进行序列化、反序列化! 不需要宏定义! 直接使用即可 (示例: JsonTest.cpp)

  • 万能print/toString等工具类;

构建要求

  • Linux 5.1+
  • GCC / Clang 编译器
  • C++20

快速开始

  • 编写端点 (提供了简化使用的 API 宏)
#include <HXWeb/HXApi.hpp> // 使用简化的api

// 用户定义的控制器
class MyWebController {
    ROUTER
        .on<GET, POST /*一个端点函数 挂载多种请求方式(GET, DEL ...) (这是一个`可变参数`)*/>("/", [] ENDPOINT {
            co_return res.setStatusAndContent(
                Status::CODE_200, "<h1>Hello! this Heng_Xin 自主研发的 Http 服务器!</h1>");
        })
        .on<GET>("/home/{id}/{name}/**", [] ENDPOINT {
            // 获取第一个(index = 0)路径参数 {id}, 并且解析为`u_int32_t`类型, 局部变量命名为:`id`
            PARSE_PARAM(0, u_int32_t, id);

            // 获取第一个(index = 1)路径参数 {name}, 并且解析为`std::string`类型, 局部变量命名为:`name`
            PARSE_PARAM(1, std::string, name);

            // 解析通配符**路径, 保存到 std::string uwp 这个局部变量中
            PARSE_MULTI_LEVEL_PARAM(uwp);
            // 注意, 一般的url对于尾部是否有`/`是全部当做没有的
            // 如: /home/id == /home/id/
            // 而通配符**路径是需要当做有的:
            // 如: /files/**
            // 不解析 /files
            // 但是解析 /files/ 为 ""

            // 解析查询参数为键值对; url?awa=xxx 这种
            GET_PARSE_QUERY_PARAMETERS(queryMap);

            if (queryMap.count("loli")) // 如果解析到 ?loli=xxx
                std::cout << queryMap["loli"] << '\n'; // xxx 的值

            RESPONSE_DATA(
                200,
                html,
                "<h1> Home id 是 " + std::to_string(*id) + ", "
                "而名字是 " + *name + "</h1>"
                "<h2> 来自 URL: " + req.getRequesPath() + " 的解析</h2>"
                "<h3>[通配符]参数为: " + uwp + "</h3>"
            );
        })
        .on<GET>("/favicon.ico", [] ENDPOINT {
            // 支持分块编码 (内部自动识别文件拓展名, 以选择对应的`Content-Type`)
            co_return co_await res.useChunkedEncodingTransferFile("favicon.ico");
        })
    ROUTER_END;
};
  • 绑定控制器到全局路由
#include <HXWeb/HXApi.hpp> // 宏所在头文件

ROUTER_BIND(MyWebController); // 这个类在上面声明过了
  • 启动服务器, 并且监听 127.0.0.1:28205, 并且设置路由失败时候返回的界面
    • 可选: 可以设置线程数和超时时间 | 每个线程独享一个uring, 但是绑定同一个端口, 由操作系统进行负载均衡
#include <HXWeb/HXApi.hpp> // 宏所在头文件
#include <HXWeb/server/Server.h>

int main() {
    chdir("../static");
    setlocale(LC_ALL, "zh_CN.UTF-8");
    ROUTER_BIND(WSChatController);
    // 设置路由失败时候的端点
    ROUTER_ERROR_ENDPOINT([] ENDPOINT {
        static_cast<void>(req);
        res.setResponseLine(HX::web::protocol::http::Status::CODE_404)
           .setContentType(HX::web::protocol::http::ResContentType::html)
           .setBodyData("<!DOCTYPE html><html><head><meta charset=UTF-8><title>404 Not Found</title><style>body{font-family:Arial,sans-serif;text-align:center;padding:50px;background-color:#f4f4f4}h1{font-size:100px;margin:0;color:#990099}p{font-size:24px;color:gold}</style><body><h1>404</h1><p>Not Found</p><hr/><p>HXLibs</p>");
        co_return;
    });

    // 启动Http服务 [阻塞于此]
    HX::web::server::Server::startHttp("127.0.0.1", "28205", 16 /*可选 线程数(互不相关)*/, 10s /*可选 超时时间*/);

    // 或者, 启动Https服务 [阻塞于此], 需要提供证书和密钥
    HX::web::server::Server::startHttps("127.0.0.1", "28205", "certs/cert.pem", "certs/key.pem");
    return 0;
}

进阶的, 实际上端点的ENDPOINT宏, 是:

#define ENDPOINT  \
(                 \
    Request& req, \
    Response& res \
) -> Task<>

也就是lambda表达式, 因此您可以捕获一些变量, 例如:

class ChatController {
    struct LogPrint {
        void print() const {
            HX::print::println("欢迎光临~");
        }
    };

    ROUTER
        .on<GET, POST>("/", [log = LogPrint{}] ENDPOINT {
            RESPONSE_DATA(
                200,
                html,
                co_await HX::STL::utils::FileUtils::asyncGetFileContent("indexByJson.html")
            );
            log.print();
        })
    ROUTER_END;
};

(一般场景下没啥用), 但是下面的功能可以让您面向切面编程端点(类似于拦截器):

下面并没有使用ENDPOINT, 可以发现, 代码略有复杂和重复.

class ServerAddInterceptorTestController {
public:
    struct Log {
        decltype(std::chrono::steady_clock::now()) t;

        bool before(HX::web::protocol::http::Request& req, HX::web::protocol::http::Response& res) {
            HX::print::println("请求了: ", req.getPureRequesPath());
            static_cast<void>(res);
            t = std::chrono::steady_clock::now();
            return true;
        }

        bool after(HX::web::protocol::http::Request& req, HX::web::protocol::http::Response& res) {
            auto t1 = std::chrono::steady_clock::now();
            auto dt = t1 - t;
            int64_t us = std::chrono::duration_cast<std::chrono::milliseconds>(dt).count();
            HX::print::println("已响应: ", req.getPureRequesPath(), "花费: ", us, " us");
            static_cast<void>(res);
            return true;
        }
    };

    ROUTER
        .on<GET, POST>("/", [](
            Request& req,
            Response& res
        ) -> Task<> {
            auto map = req.getParseQueryParameters();
            if (map.find("loli") == map.end()) {
                res.setResponseLine(Status::CODE_200)
                .setContentType(HX::web::protocol::http::ResContentType::html)
                .setBodyData("<h1>You is no good!</h1>");
                co_return;
            }
            res.setResponseLine(Status::CODE_200)
            .setContentType(HX::web::protocol::http::ResContentType::html)
            .setBodyData("<h1>yo si yo si!</h1>");
        }, Log{})
        .on<GET, POST>("/home/{id}/**", [](
            Request& req,
            Response& res
        ) -> Task<> {
            static_cast<void>(req);
            res.setResponseLine(Status::CODE_200)
            .setContentType(HX::web::protocol::http::ResContentType::html)
            .setBodyData("<h1>This is Home</h1>");
            co_return;
        })
    ROUTER_END;
};

Tip

为了防止api宏带来的命名空间污染, 特别是在头文件中声明控制器端点的, 建议在尾部添加#include <HXWeb/UnHXApi.hpp>undef那些宏.

相关依赖

依赖库 说明 备注
liburing io_uring的封装 https://github.com/axboe/liburing
hashlib 用于WebSocket构造SHA-1信息摘要; 以及进行Base64编码 https://create.stephan-brumme.com/hash-library/
OpenSSL 3.3.1+ 用于https的证书/握手 https://github.com/openssl/openssl

性能测试

Tip

  • Arth Linux
  • 13th Gen Intel(R) Core(TM) i9-13980HX
  • RAM: 64GB
  • cmake -> Release
  • http
# build: add_definitions(-DCOMPILE_WEB_SOCKET_SERVER_MAIN)  # websocket服务端

# http 没有文件读写的情况下:
➜ wrk -t32 -c1100 http://127.0.0.1:28205/home/123/123
Running 10s test @ http://127.0.0.1:28205/home/123/123
  32 threads and 1100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.48ms    2.52ms  36.60ms   86.18%
    Req/Sec    77.98k    16.30k  241.91k    74.07%
  23512408 requests in 10.10s, 4.51GB read
  Socket errors: connect 99, read 0, write 0, timeout 0
Requests/sec: 2327117.67
Transfer/sec:    457.18MB

# http 读写 static/WebSocketIndex.html
➜ wrk -t32 -c1100 http://127.0.0.1:28205/            
Running 10s test @ http://127.0.0.1:28205/
  32 threads and 1100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.18ms    1.63ms  23.96ms   86.39%
    Req/Sec    45.53k    13.36k  263.46k    89.82%
  14176629 requests in 10.09s, 43.81GB read
  Socket errors: connect 99, read 0, write 0, timeout 0
Requests/sec: 1405535.24
Transfer/sec:      4.34GB
  • https
# build: add_definitions(-DHTTPS_FILE_SERVER_MAIN)  # https简单的文件服务器
# 无文件读写, 仅仅https连接和通过会话密钥加解密通信
➜ wrk -d10s -t32 -c1000 --timeout 5s https://127.0.0.1:28205
Running 10s test @ https://127.0.0.1:28205
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.04ms    3.21ms  70.65ms   86.96%
    Req/Sec    35.71k     6.56k   77.68k    70.85%
  11447330 requests in 10.10s, 1.97GB read
Requests/sec: 1133384.87
Transfer/sec:    199.96MB

# (100s) 小文件读写 (static/test/github.html) 563.9kb 采用分块编码, 但是需要https加密
➜ wrk -d100s -t32 -c1000 --timeout 5s https://127.0.0.1:28205/files/a
Running 2m test @ https://127.0.0.1:28205/files/a
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    62.20ms   35.25ms 529.39ms   72.92%
    Req/Sec     1.01k   358.38     3.09k    65.19%
  3203315 requests in 1.67m, 843.28GB read
Requests/sec:  32001.06
Transfer/sec:      8.42GB

# (10s) 小文件读写 (static/test/github.html) 563.9kb 采用分块编码, 但是需要https加密
# 并且修改了 include/HXSTL/utils/FileUtils.h 定义的:
    /// @brief 读取文件buf数组的缓冲区大小
    static constexpr std::size_t kBufMaxSize = 4096U * 2; # 进行了 * 2
# 注: 这个是后来加上的测试, 也就是说其他的测试都是在`kBufMaxSize = 4096U`时候测试的, 因此这个只能和上面的形成对比
➜ wrk -d10s -t32 -c1000 https://127.0.0.1:28205/files/a
Running 10s test @ https://127.0.0.1:28205/files/a
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    39.14ms   23.01ms 155.74ms   67.09%
    Req/Sec     1.60k   374.46     3.74k    69.51%
  516854 requests in 10.10s, 136.26GB read
Requests/sec:  51182.79
Transfer/sec:     13.49GB

# 对比 小文件读写 (static/test/github.html) 563.9kb `没有`采用分块编码, 但是需要https加密
➜ wrk -d10s -t32 -c1000 https://127.0.0.1:28205/test
Running 10s test @ https://127.0.0.1:28205/test
  32 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   105.38ms   41.80ms 427.97ms   69.93%
    Req/Sec   294.52     43.68   425.00     74.12%
  94528 requests in 10.10s, 49.77GB read
Requests/sec:   9360.72
Transfer/sec:      4.93GB

代码规范

--> C++ 编码规范

开发计划

--> 开发计划

开发日志

--> 开发日志

特别感谢

Stargazers

Stargazers repo roster for @HengXin666/HXNet

Forkers

Forkers repo roster for @HengXin666/HXNet

About

C++20协程和基于io_uringの百万并发服务器; 可异步读写, 支持Transfer-Encoding分块编码传输文件; 基于压缩前缀树编写的路由, 支持通配符解析;+ http/https/websocket、socks5代理、Json解析、Json静态反射到结构体(可以无宏反射到聚合类), 封装了线程安全的LFUCache和LRUCache, 静态枚举映射, 支持STL容器的print/toString等

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages