The cpp-httplib library provides a header-only HTTP server implementation to C++. A primary limitation is its listen() function blocks the calling thread, prevanting concurrent execution of other tasks.
Threaded Server Wrapper
A wrapper class HttpServer manages httplib::Server instances in separate threads to prevent blocking:
class HttpServer {
httplib::Server* server;
MyHttpServerThreadPool* serverPool;
public:
HttpServer(const std::string& host, int port);
httplib::Server* getHttplibServer();
void listenInThread();
void listenInLocal();
};
Usage example:
#include <HttpServer.h>
int main() {
HttpServer* hs = new HttpServer("0.0.0.0", 8080);
httplib::Server* svr = hs->getHttplibServer();
svr->Get("/hi", [](const httplib::Request&, httplib::Response& res) {
res.set_content("Response Text", "text/plain");
});
hs->listenInThread(); // Non-blocking execution
while(true) {
// Main thread continues execution
std::this_thread::sleep_for(std::chrono::seconds(1));
}
delete hs;
return 0;
}
Controller-based Architecture
Extend BaseController to organize request handlers:
class CustomController : public httplib::BaseController {
protected:
void bind() override {
server->Get("/data", BindController(&CustomController::getData, this));
server->Post("/submit", BindController(&CustomController::postData, this));
}
void getData(const httplib::Request& req, httplib::Response& resp) {
resp.set_content("{ \"status\": \"success\" }", "application/json");
}
void postData(const httplib::Request& req, httplib::Response& resp) {
// Process request body
resp.set_content("{ \"result\": \"processed\" }", "application/json");
}
};
Integration with main application:
int main() {
HttpServer* hs = new HttpServer(8080);
hs->addController(new CustomController());
hs->listenInThread();
// Application logic continues
return 0;
}
Macro-based Request Binding
For concise syntax, enable macro bindings by setting HTTPSERVER_AUTO_CONTROLLER to 1:
#define HTTPSERVER_AUTO_CONTROLLER 1
class AutoController : public httplib::AutoBashController {
GetRequestBind(handleGet, "/auto/get");
void handleGet(const httplib::Request& req, httplib::Response& resp) {
resp.body = "{ \"type\": \"auto_response\" }";
}
PostRequestBind(handlePost, "/auto/post");
void handlePost(const httplib::Request& req, httplib::Response& resp) {
std::string requestBody = req.body;
resp.set_content("{ \"received\": true }", "application/json");
}
};
Thread Pool Implementation
A custom thread pool manages concurrent request processing:
class MyHttpServerThreadPool {
std::vector<std::thread> workers;
std::list<std::function<void()>> tasks;
std::mutex taskMutex;
std::condition_variable taskCondition;
bool shutdownFlag = false;
void workerFunction() {
while(!shutdownFlag) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(taskMutex);
taskCondition.wait(lock, [this] {
return !tasks.empty() || shutdownFlag;
});
if(shutdownFlag && tasks.empty()) break;
task = std::move(tasks.front());
tasks.pop_front();
}
task();
}
}
public:
MyHttpServerThreadPool(size_t threadCount) {
for(size_t i = 0; i < threadCount; ++i) {
workers.emplace_back(&MyHttpServerThreadPool::workerFunction, this);
}
}
void enqueue(std::function<void()> task) {
{
std::unique_lock<std::mutex> lock(taskMutex);
tasks.push_back(std::move(task));
}
taskCondition.notify_one();
}
};
Request Filtering Mechanism
Global request/response filters can be implemented:
class ControllerWithFilters : public httplib::AutoBashController {
BeforeFilter(requestLogger);
void requestLogger(const httplib::Request& req, httplib::Response& resp) {
std::cout << "Request: " << req.method << " " << req.path << std::endl;
}
AfterFilter(responseLogger);
void responseLogger(const httplib::Request& req, httplib::Response& resp) {
std::cout << "Response status: " << resp.status << std::endl;
}
};
Error Handling Configuration
Custom error handlers improve robustness:
HttpServer* serverInstance = new HttpServer("localhost", 8080);
httplib::Server* httpCore = serverInstance->getHttplibServer();
httpCore->set_error_handler([](const httplib::Request&, httplib::Response& res) {
res.set_content("{ \"error\": \"Request failed\" }", "application/json");
});
serverInstance->listenInThread();
Resource Management
Proper cleanup prevents memory leaks:
HttpServer::~HttpServer() {
if(server != nullptr) {
server->stop();
delete server;
}
for(auto controller : controllerVec) {
delete controller;
}
controllerVec.clear();
}