~~~~~~~~ - 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编译
~~~~~~~~ - 关于httplib更高级的用法, 请直接到(cpp-httplib的github地址)参考
~~~~~~~~ - 确保你的编译器最低支持c++14
~~~~~~~~ cpp-httplib是一个简单易用, 跨平台, 开发快速, 仅头文件的c++ http库. 只需要把一个头文件包含到你的代码里, 就可以用很少的代码完成一个http server, 就像下面这样. 但是, 它会阻塞当前线程, 导致while循环中的cout不执行, 看下面的代码
#include
#include
using namespace httplib;
using namespace std;
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的listen函数中有一个while循环, 其中不停的执行accept()函数, 用来监听端口对应的文件描述符, 所以, 只要你开一个子线程, 让listen函数在子线程中执行, 就不会阻塞主线程了
~~~~~~~~ 但其实你不用太关心原因, 甚至不用操心线程, 因为这些我都给你封装好了 _
~~~~~~~~ 把HttpServer.h复制到httplib的同级目录下, 在你的程序中include它, 通过HttpServer.h使用httplib, 就不会阻塞你的主线程了 , 如下:(HttpServer.h代码较多, 放到文章最后了)
~~~~~~~~ HttpServer.h的代码在文章的最后, 你可以直接复制使用
#include
#include
#include // 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;
}
~~~~~~~ - HttpServer.h不但能自动为你的httplib::Server对象创建子线程, 还提供了一种类似于java spring mvc风格的http服务器开发方式, 以解耦你的代码. 让你的程序更加清晰明了.
~~~~~~~ - 你只需要继承BashController, 并按下面的方式编写的你服务器响应代码.
自定义controler, 解耦代码
#pragma once
#include "Httpserver.h"
class TestController : public httplib::BaseController
{
// 注意: 必须重写bind()函数, 并在内部填写映射关系, 否则会抛出异常
void bind() override
{
// BindController可以绑定httplib中所有参数为(const httplib::Request& req, httplib::Response& resp)的方法
server->Get("/get", BindController(&TestController::getApi, this));
server->Post("/post", BindController(&TestController::postApi, this));
}
// 注意:Request和Response这两个参数必须是引用类型
void getApi(const httplib::Request& req, httplib::Response& resp)
{
std::string jsonData = "{\"type\": \"get\", \"code\": 2001}";
resp.set_content(jsonData, "application/json");
}
// 注意:Request和Response这两个参数必须是引用类型
void postApi(const httplib::Request& req, httplib::Response& resp)
{
std::string data = req.body;
std::cout << "接收请求数据: \n" << data << std::endl;
std::string jsonData = "{\"type\": \"post\", \"code\": 2001}";
resp.set_content(jsonData, "application/json");
}
};
当你把自定义的controllor写到头文件中, 只在main中include它们时, 你的main函数会非常简洁和清晰, 像下面这样:
#include
#include
using namespace httplib;
using namespace std;
// #include // include你自定义的controllor
#include // 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));
}
}
~~~~~~~~ 具体的封装代码, 把它放在httplib.h的同级目录下, 就可以按文章中提到的方式使用他们了
// 将HttpServer.h放到httplib.h文件的同级目录下
#pragma once
#include
#include
#include
#include
#include
#include
#include
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;
// 绑定函数
template<class Func, class T>
auto BindController(Func&& func, T&& obj)
{
httplib::Server::Handler handler = std::bind(func, obj, std::placeholders::_1, std::placeholders::_2);
return handler;
}
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;
}
}