cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发

2023-05-16

目录

  • 说明/前言
  • 原生的httplib会阻塞你的主线程
  • 解决httplib阻塞主线程的问题
  • BashController - 面向对象风格使用httplib
      • 自定义controller --- MyController.h文件
      • 自定义controller --- TestController.h文件
      • 使用controller功能的main.cpp文件
      • 测试结果
  • HttpServer.h

说明/前言

         ~~~~~~~~          - cpp-httplib是一个简单易用, 跨平台, 开发快速, 仅头文件的c++ http库, 只需要把一个头文件包含到你的代码里, 就可以用很少的代码完成一个http server, 但它仍有一些不足之处, 比如会阻塞你的主线程(cpp-httplib的github地址)
         ~~~~~~~~          - 在不影响cpp-httplib原生代码的基础上, 对其进行了进一步的封装, 且完全和httplib解耦
         ~~~~~~~~          - 封装类为header-only, 直接inlude使用(封装的具体代码在文章最后的HttpServer.h中)
         ~~~~~~~~          - 避免了httplib原生使用方式阻塞主线程的问题
         ~~~~~~~~          - 在支持原生开发方式的基础上, 增加了BashController类,模仿java的spring mvc的开发方式, 以解耦代码, 支持较大型的web项目
         ~~~~~~~~          - 正文主要讲述使用方式, 封装的具体代码在文章最后, 可以直接复制使用
         ~~~~~~~~          - 跨平台, 可在linux和window中使用, 但原生的cpp-httplib对mingw支持不太好, windows下尽量使用msvc编译
         ~~~~~~~~          - 确保你的编译器最低支持c++11

原生的httplib会阻塞你的主线程


         ~~~~~~~~         cpp-httplib是一个简单易用, 跨平台, 开发快速, 仅头文件的c++ http库. 只需要把一个头文件包含到你的代码里, 就可以用很少的代码完成一个http server, 就像下面这样. 但是, 它会阻塞当前线程, 导致while循环中的cout不执行, 看下面的代码

#include <httplib.h>
#include <chrono>

using namespace httplib;

int main()
{
	httplib::Server svr;

	svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) 
	{
	  res.set_content("Hello World!", "text/plain");
	});
	
	cout << "start http server" << endl; 

	// listen函数后, 将阻塞当前线程
	svr.listen("0.0.0.0", 8080);
	
	// 由于主线程被阻塞, 因此下面的代码不会执行 
    while (true)
    {
        cout << "main thread running ..." << endl;
        this_thread::sleep_for(chrono::seconds(2)); // 睡眠2秒
    }
	
	return 0;
}

执行结果
httplib原生使用方式的执行结果

         ~~~~~~~~         阻塞的原因是因为, 在httplib的listen函数中有一个while循环, 其中不停的执行accept()函数, 用来监听端口对应的文件描述符, 所以, 只要你开一个子线程, 让listen函数在子线程中执行, 就不会阻塞主线程了
         ~~~~~~~~         但其实你不用太关心原因, 甚至不用操心线程, 因为这些我都给你封装好了 _

解决httplib阻塞主线程的问题


         ~~~~~~~~         把HttpServer.h复制到httplib的同级目录下, 在你的程序中include它, 通过HttpServer.h使用httplib, 就不会阻塞你的主线程了 , 如下:(HttpServer.h代码较多, 放到文章最后了)

         ~~~~~~~~          HttpServer.h的代码在文章的最后, 你可以直接复制使用

#include <httplib.h>
#include <chrono>
#include <HttpServer.h> // HttpServer.h头文件管理httplib::Server对象

using namespace httplib;
using namespace std;

int main()
{
	// 实例化一个HttpServer对象hs, 内部会自动初始化httplib::Server
	HttpServer* hs = new HttpServer("0.0.0.0", 8080);
	
    // 获取hs内部的httplib::Server指针
    Server* svr = hs->getHttplibServer();
    
    // 使用httplib写服务器
	svr->Get("/hi", [](const httplib::Request &, httplib::Response &res) 
	{
	  res.set_content("Hello World!", "text/plain");
	});
   	cout << "start http server" << endl; 
	
	// HttpServer对象调用listenInThread(), 把listen函数放入一个子线程运行
	// 注意, 子线程运行后不能让main函数退出, 因为主线程被关闭后, 所有的子线程会被强制关闭
	hs->listenInThread();

    while (true)
    {
        cout << "main thread running ..." << endl;
        this_thread::sleep_for(chrono::seconds(2));
    }
    
	return 0;
}

执行结果
在这里插入图片描述

BashController - 面向对象风格使用httplib


        ~~~~~~~         - HttpServer.h不但能自动为你的httplib::Server对象创建子线程, 还提供了一种类似于java spring mvc风格的http服务器开发方式, 以解耦你的代码. 让你的程序更加清晰明了.
        ~~~~~~~         - 你只需要继承BashController, 并按下面的方式编写的你服务器响应代码.

自定义controller — MyController.h文件


继承BashController, 重写bind函数, 像下面这样

#pragma once
#include "HttpServer.h"

// 自定义的controllor, 需要继承BaseController
class MyController : public httplib::BaseController 
{
    //重写bind函数, 添加映射关系
    void bind() override                
    {
    	
        server->Get("/hi", BIND_CONTROLLER(MyController::get_helloWorld)); // 注意: 绑定时, 必须包含本类的类名称
        // 当然, 这里可以添加很多个映射关系
        
    }
    
    // 具体的请求处理逻辑
    void get_helloWorld(const httplib::Request& req, httplib::Response& resp)
    {
        std::string jsonData = "{\"status\": true, \"code\": 2023}";
        resp.set_content(jsonData, "Application/json");
    } 
};

自定义controller — TestController.h文件


另一个自定义的controler, 写法和MyController完全相同

#pragma once
#include <iostream>
#include "HttpServer.h"
// 自定义的controllor, 需要继承BaseController
class TestController : public httplib::BaseController
{
    void bind() override
    {
        server->Get("/test", BIND_CONTROLLER(TestController::hello));
    }
    
    void hello(const httplib::Request& req, httplib::Response& resp)
    {
        std::string jsonData = "{\"status\": true, \"code\": 2023}";
        resp.set_content(jsonData, "Application/json");
    } 
};

使用controller功能的main.cpp文件


当你把自定义的controllor写到头文件中, 只在main中include它们时, 你的main函数会非常简洁和清晰, 像下面这样:

#include <HttpServer.h>
#include <chrono>


using namespace httplib;
using namespace std;

#include <MyController.h> // include你自定义的controllor
#include <TestController.h>  // include你自定义的controllor


int main()
{
    HttpServer* hs = new HttpServer("0.0.0.0", 8080);

    MyController() >> hs;		// 初始化MyController对象, 并注入到hs
    TestController() >> hs;		// 初始化TestController对象, 并注入到hs
    
    hs->listenInThread();		// 子线程中启动http server


    while (true)
    {
        cout << "main thread running ..." << endl;
        this_thread::sleep_for(chrono::seconds(2));
    }
}

测试结果


在这里插入图片描述

HttpServer.h


         ~~~~~~~~          具体的封装代码, 把它放在httplib.h的同级目录下, 就可以按文章中提到的方式使用他们了

// 将HttpServer.h放到httplib.h文件的同级目录下
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
#include <typeinfo>
#include <httplib.h>



// controllor bind函数时用到的宏
#define BIND_CONTROLLER(__selector__) \
    std::bind(&__selector__, this, std::placeholders::_1, std::placeholders::_2)

namespace httplib
{
    // 线程池(复制的httplib的线程池, 添加了动态增加线程数量的功能)
    class HttpServerThreadPool : public TaskQueue
    {
    public:
        
        explicit HttpServerThreadPool(size_t n) : shutdown_(false)
        {
            while (n)
            {
                threads_.emplace_back(worker(*this));
                n--;
            }
        }

        void addAThread()
        {
            threads_.emplace_back(worker(*this));
        }
        
        std::size_t getThreadNum()
        {
            return threads_.size();
        }
        
        HttpServerThreadPool(const ThreadPool &) = delete;
        ~HttpServerThreadPool() override = default;

        void enqueue(std::function<void()> fn) override
        {
            {
                std::unique_lock<std::mutex> lock(mutex_);
                jobs_.push_back(std::move(fn));
            }

            cond_.notify_one();
        }

        void shutdown() override
        {
            // Stop all worker threads...
            {
                std::unique_lock<std::mutex> lock(mutex_);
                shutdown_ = true;
            }

            cond_.notify_all();

            // Join...
            for (auto &t : threads_)
            {
                t.join();
            }
        }

    private:
        struct worker
        {
            explicit worker(HttpServerThreadPool &pool) : pool_(pool) {}

            void operator()()
            {
                for (;;)
                {
                    std::function<void()> fn;
                    {
                        std::unique_lock<std::mutex> lock(pool_.mutex_);

                        pool_.cond_.wait(
                            lock, [&]
                            { return !pool_.jobs_.empty() || pool_.shutdown_; });

                        if (pool_.shutdown_ && pool_.jobs_.empty())
                        {
                            break;
                        }

                        fn = std::move(pool_.jobs_.front());
                        pool_.jobs_.pop_front();
                    }

                    assert(true == static_cast<bool>(fn));
                    fn();
                }
            }

            HttpServerThreadPool &pool_;
        };
        friend struct worker;

        std::vector<std::thread> threads_;
        std::list<std::function<void()>> jobs_;

        bool shutdown_;

        std::condition_variable cond_;
        std::mutex mutex_;
    };

    class BaseController;

    // HttpServer, 用于管理httplib::Server对象
    class HttpServer
    {
    private:
        std::string host = "";
        int port = -1;
        static httplib::HttpServerThreadPool *serverPool;
        httplib::Server *server = new httplib::Server();
        static std::atomic<int> poolUseNum;
        int socket_flags;
        void buildServerThreadPool();
        
        friend class BaseController;

    public:
        HttpServer(const HttpServer &) = delete;
        HttpServer() = delete;

        HttpServer(const std::string &_host, int _port, int _socket_flags = 0) : host(std::move(_host)), port(_port), socket_flags(_socket_flags) {}
        HttpServer(int _port, int _socket_flags = 0) : host(std::move("0.0.0.0")), port(_port), socket_flags(_socket_flags) {}

        httplib::Server* getHttplibServer();
        /*
            如果serverPool为null, 将为static serverPool 创建一个新的HttpThreadPool, 并在该线程池中监听
            如果serverPool不为null, 将直接使用static serverPool, 在线程池中执行httplib::Server的listen函数
        */
        auto listenInThread();

        // 在本地监听httplib::Server的listen函数
        void listenInLocal();

        // 释放server指针,如果poolUseNum为0, 也将释放serverPool
        ~HttpServer();
    };

    // BashController, 模仿java spring mvc的开发风格
    class BaseController
    {

    protected:
        // 必须重写bind方法, 在其中绑定具体的请求响应地址和请求响应方法, 否则抛出一个string异常
        virtual void bind()
        {
            throw std::string("must override ", __FUNCTION__);
        }
        httplib::Server *server = nullptr;

    public:
        BaseController() = default;
        BaseController(BaseController &) = delete;

        void operator>>(httplib::Server *_server)
        {
            server = _server;
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        void initToServer(httplib::Server *_server)
        {
            server = _server;
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        void operator>>(httplib::HttpServer *_server)
        {

            server = _server->getHttplibServer();
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        void initToServer(httplib::HttpServer *_server)
        {

            server = _server->getHttplibServer();
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        ~BaseController()
        {
            if (server != nullptr)
            {
                server = nullptr;
            }
            // print("destroy controller");
        }
    };

}

httplib::HttpServerThreadPool *httplib::HttpServer::serverPool = nullptr;
std::atomic_int httplib::HttpServer::poolUseNum(0);

void httplib::HttpServer::buildServerThreadPool()
{
    poolUseNum++;

    if (serverPool == nullptr)
    {
  
        serverPool = new httplib::HttpServerThreadPool{(std::size_t)poolUseNum.load()};
    }

}

httplib::Server *
httplib::HttpServer::getHttplibServer()
{
    return server;
}

auto httplib::HttpServer::listenInThread()
{

    buildServerThreadPool();

    if (serverPool != nullptr)
    {
        serverPool->addAThread();
    }

    std::cout << "listen to " << port << std::endl;
    return serverPool->enqueue(std::bind(&httplib::Server::listen, server, host, port, socket_flags));
}

void httplib::HttpServer::listenInLocal()
{
    server->listen(host, port, socket_flags);
    // print("listen in port {}", port);
}

httplib::HttpServer::~HttpServer()
{

    if (this->server != nullptr)
    {
        delete (server);
    }

    poolUseNum--;
    if (poolUseNum == 0 && serverPool != nullptr)
    {
        delete (serverPool);
        serverPool = nullptr;
    }
}

``

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发 的相关文章

  • CPP服务器08--http请求响应实现

    http服务设计 对于静态页面服务器来说 xff0c 其工作流程如下 xff1a 接收客户端消息 解析出http请求报文 业务逻辑 xff0c 拼装响应报文 发送给客户端结果 http连接类 设计目标 xff1a 将客户端唯一文件描述符封装
  • ROS下面调用自定义的头文件和.cpp/.so文件(亲测有效)

    前言 ROS下面使用已经编译好的ROS package是很方便的 xff0c 但是大多数我们可能自己定义了一些头文件 xff0c 想去直接引用 xff0c 那么如何在ROS下面调用自己的定义的函数呢 xff1f ROS下调用自定义的头文件
  • pixhawk position_estimator_inav.cpp思路整理及数据流

    写在前面 xff1a 这篇blog主要参考pixhawk的高度解算算法解读 xff0c 并且加以扩展 xff0c 扩展到其他传感器 xff0c 其实里面处理好多只是记录了流程 xff0c 至于里面实际物理意义并不是很清楚 xff0c 也希望
  • windows上使用cmake 编译yaml-cpp源码,生成yam-cpp.lib

    1 打开cmake gui 2 添加CmakeList 3 建立build 4 进入工程中生成debug和release版本的lib
  • vscode c语言插件_推荐学习C语言或CPP使用的代码编辑器

    1 Visual Studio Code VS Code来自微软 xff0c 是一个开源的 基于 Electron 的轻量代码编辑器 xff0c 亦是笔者推荐的一款编写C C 43 43 的 代码编辑器 那作为一款代码编辑器 xff0c V
  • pixhawk px4 commander.cpp

    对于复杂的函数 xff0c 要做的就是看函数的输入是什么 来自哪里 xff0c 经过处理后得到什么 给谁用 xff0c 这样就可以把程序逻辑理清 中间的分析就是看函数如何处理的 span class hljs keyword extern
  • 设置cpp-httplib 服务器模式模式下的线程池大小 以及如何增加默认处理函数 以便能够实现http请求转发

    先说说默认的创建的线程池数量 原因是某天调试在gdb调试下 一启动程序发现 开启了好多线程 如下图 因为我们程序 没几个线程 数了下 居然有60多个线程 不需要那么多 所以看下 httplib的源码 构造函数的时候 设置了最大线程池数量 看
  • 侯捷-C++面向对象高级开发(上)-String类实现

    String类实现 String h ifndef MY STRING H define MY STRING H include
  • QT自定义QTableWidget

    目录 QT自定义QTableWidget 1 实现效果 2 具体实现 1 表格QTabelWidget 2 表头QHeaderView 3 插入数据 QT自定义QTableWidget 1 实现效果 2 具体实现 1 表格QTabelWid
  • 学习笔记-BNF、EBNF、ABNF语法格式描述规范

    目标是确认一些c cpp的语法细节 需要看cpp语法定义文件 考虑从c的语法定义文件开始确认 考虑实现一个简化的语言定义和编译器 为后续的实际需求做自定义扩展 参考网页 https en wikipedia org wiki Extende
  • C++ 实现 C# delegate 机制

    C 里的 delegate C 里的 delegate 作为语法特性的一部分 使用起来非常方便 首先按照函数签名 声明一个 delegate 类型 delegate void DelegateType 之后就可以用这个 delegate 类
  • C++ 函数模板与类模板template,以及具体化、实例化

    函数模板 需要创建针对不同参数类型的实现相同功能的不同函数 注 模板不能缩短可执行程序 最终仍是有多个独立的函数定义 另 若对不同类型的参数执行不同的算法 可以重载模板定义 前提是两函数的特征标不同 例 template
  • Cpp学习——string模拟实现

    目录 一 string的成员变量 二 string的各项功能函数 1 构造函数 2 析构函数 3 扩容函数 4 插入与删除数据的函数 5 运算符重载 6 打印显示函数 7 拷贝构造 8 find函数 一 string的成员变量 在模拟实现s
  • 《算法导论》总结与分析

    算法导论总结与分析 分治 strassen算法 介绍 步骤 正确性证明 复杂度分析 排序 堆排序 介绍 步骤 构建 排序 优先队列 复杂度分析 快速排序 介绍 步骤 复杂度分析 最坏情况 最好情况 线性时间排序 介绍 步骤 复杂度分析 数据
  • 侯捷-C++面向对象高级开发(上)-complex类实现

    complex类实现 comlex h ifndef COMPLEX H define COMPLEX H include
  • extern C 在c/c++中的使用

    http blog csdn net jscese article details 37821961 1 问题定义 在研究操作系统源代码或者在嵌入式系统中编写程序时 经常会发现下面这种用法 cpp view plain copy print
  • QCustomPlot获取选点坐标

    QCustomPlot版本 Version 2 1 1 设置点选择模式 customPlot gt setInteractions QCP iSelectPlottables 2 绑定点击事件 connect customPlot QCus
  • HTTPS 连接 Python

    我正在尝试验证该目标是否公开了 https Web 服务 我有通过 HTTP 连接的代码 但我不确定如何通过 HTTPS 连接 我读过您使用 SSL 但我也读到它不支持证书错误 我得到的代码来自 python 文档 import httpl
  • 使用 python 2.7 进行 URL 编码

    gt gt gt import httplib gt gt gt x httplib HTTPConnection localhost 8080 gt gt gt x connect gt gt gt x request GET camer
  • 使用 httplib 进行不完整读取

    我在从特定网站获取 RSS 提要时一直遇到问题 我最终编写了一个相当丑陋的程序来执行此功能 但我很好奇为什么会发生这种情况以及是否有更高级别的接口正确处理此问题 这个问题并不是真正的问题 因为我不需要经常检索提要 我已经阅读了一个捕获异常并

随机推荐

  • 前端控制台怎么打开

    去点击右上角三个点 更多工具 开发者工具 成功打开控制台 xff1a
  • 数据库的某个字段在前端表格里没显示出来

    当前卖出费率一栏全部为空 那问题肯定是出在数据库这方面 vue文件里的表格字段必须跟数据库的对应字段一致才行 vue里的NowRate必须修改为sellRate xff0c 跟数据库的字段名一致 修改之后记得重启 xff0c 正常显示
  • 2023年3月计算机三级网络技术备考

    一 专项练习 网络系统结构与设计的基本原则 1 1基础知识 1 1 1 广域网技术的发展 下列关于光以太网技术特征的描述中 xff0c 错误 的是 A 能够根据用户的需求分配宽带 B 以信元为单位传输数据 C 具有保护用户和网络资源安全的认
  • Vue3项目使用 wow.js 让页面滚动更有趣~

    wow js是一个可以在页面滚动的过程中逐渐释放动画效果 xff0c 让页面滚动更有趣的一个插件库 官网 xff1a wow js Reveal Animations When Scrolling 本文就演示一下如何在Vue3项目使用 wo
  • Failed to start bean ‘documentationPluginsBootstrapper ‘; nested exception is java.lang.NullPointer

    在配置使用swagger的时候启动报错 xff0c 如下 xff1a 原因在于我在swagger配置里加上了 64 EnableSwagger2注解 xff0c 在加上它之前可以正常启动 解决方法 xff1a 在配置文件里加上 spring
  • 串口发送float类型数据

    STM32串口发送float类型数据 一 代码 1 发送 span class token keyword void span span class token function send gyro span span class toke
  • STM32 F4串口空闲中断 + DMA实现数据发送

    STM32 F4串口空闲中断 43 DMA实现数据发送 前言文章目录一 空闲中断二 DMA三 代码部分1 串口配置2 DMA配置 前言 最近在做 STM32 43 ROS车的项目 xff0c STM32与ROS之间通信由于数据量大 xff0
  • Darknet YoloV4编译+训练(避免踩坑)

    AlexAB darknet yolov4编译 43 训练 时间间隔好几天今天来更新一下yolov4的训练 训练篇 在训练之前需要对大佬的源码进行编译本本机编译 xff0c 编译过程可查看下述链接 xff1a https blog csdn
  • ubuntu下如何创建ros工作空间、创建ros功能包、创建ros节点

    1 打开终端进入存放ros工作空间的目录 xff08 比如我这里将它放在home目录下的test文件夹中 xff09 cd test 2 开始创建ros工作空间 mkdir p catkin ws src cd catkin ws src
  • STM32串口通信 (采用链表接收不定长数据帧)

    STM32串口通信 链表接收不定长数据帧 数据帧说明不太恰当的比方 数据缓冲链表结构效果展示工程文件 数据帧说明 STM32数据寄存器为USARTx gt DR寄存器 可以看到DR寄存器只有 8 0 位可以使用 xff0c 第8位用于奇偶校
  • A*寻路算法

    目录 1 动画演示2 游戏中的自动寻路A 算法3 A 寻路算法原理4 调试代码分析代码5 代码 1 动画演示 2 游戏中的自动寻路A 算法 随着3D游戏的日趋流行 在复杂的3D游戏环境中如何能使非玩家控制角色准确实现自动寻路功能成为了3D游
  • 2022数学建模国赛B题和C题高质量论文代码数据

    目录 B题论文 5 1 问题一的建模与求解 5 1 1 使用极坐标求解具体位置 C题论文 1 1 研究背景 1 2 问题的提 5 1 问题一的建模与求解 5 1 1 数据的预处理 B题论文 5 1 问题一的建模与求解 5 1 1 使用极坐标
  • stm32小白学习之寄存器名称

    IDR输入只读寄存器 xff0c ODR输出可读可写寄存器 BSRR xff08 置位寄存器 xff09 与BRR xff08 复位寄存器 xff09 CRL xff08 端口配置低位寄存器 xff09 与CRH xff08 端口配置高位寄
  • 使用Vite创建Vue3+TS项目并整合Element Plus框架等一条龙服务

    记录一下使用Vite创建Vue3 43 TS项目以及整合Element Plus框架 xff0c 还有Less Pinia Vue router monaco editor等插件或组件 一 使用Vite创建Vue3 43 TS项目 第一步
  • Qt学习 第37节:QString

    在阅读QString文档时 xff0c 出了一个词 implicit sharing copy on write xff0c 不是很懂 xff0c 下面链接解释的表明白 QT的隐式共享 Implicit Sharing 道路与梦想 CSDN
  • 下载Postman并且汉化使用

    下载Postman并且汉化使用 一 下载postman postman有不同的版本 xff0c 如果要汉化就要下载的版本与汉化包一致 下载地址 xff1a postman官网下载地址 xff1a https www postman com
  • 【Vue】postman汉化教程 保姆级教程 包教会

    下载链接 xff1a Win64 Win32 历史版本下载 请把下面链接的 34 版本号 34 替换为指定的版本号 xff0c 例如 xff1a 8 8 0 版本链接Windows32位https dl pstmn io download
  • 操作系统实验——进程与线程

    目录 1 使用GCC xff08 1 xff09 参数 xff08 2 xff09 自定义头文件 xff08 3 xff09 makefile脚本 xff08 4 xff09 gdb调试 2 进程 xff08 1 xff09 新建进程 xf
  • 串口应用(USART)

    串行口应用 1 USART介绍 通用同步异步收发器 USART 提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的 外部设备之间进行全双工数据交换 USART利用分数波特率发生器提供宽范围的波特率选择 它支持同步单向通信和半双工单线
  • cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发

    目录 说明 前言原生的httplib会阻塞你的主线程解决httplib阻塞主线程的问题BashController 面向对象风格使用httplib自定义controller MyController h文件自定义controller Tes