前言
本文介绍一个有趣的 通过C++实现的 持久化的http_server demo,这样我们通过http通信之后的数据可以持久化存储,即使server挂了,数据也不会丢失。我们的http_sever 也就能够真正得作为一个后端server了。
本身持久化这个能力是数据库提供的,像通用的http交互数据都会通过SQL server或者MySQL这样的存储系统来存储关系型数据,而这里我只是调度了一个单机存储引擎作为持久化存储。
主要用到的一些技术:
- mongoose C语言 网络通信库,在此库基础上实现了一个C++的httpserver
- Rocksdb 单机存储引擎,作为持久化通信数据的存储。
- 通过模版工厂来优雅得创建http url 和 对应其操作的实现。
代码地址:PersistentHttpserver
实现过程
1. HTTPSERVER C++封装
这里mongoose的通信库基本都已经实现了http应用层及以下的通信接口的封装,包括接受http协议的数据并解析或者封装成http请求并发送,我这里需要做的仅仅是做一些接口调用使用C++实现就可以了。
基本接口如下:
class HttpServer {
public:
HttpServer() {}
~HttpServer() {
Close();
if (kv_engine_) {
delete kv_engine_;
kv_engine_ = nullptr;
}
}
void Init(const std::string &port); // Init some variable
bool Start(); // Start a http server with a port
bool Close();
// Send a message as a http request
static void SendHttpRsp(mg_connection *connection, std::string rsp);
static mg_serve_http_opts s_server_option;
static KVEngine *kv_engine_; // For persistent the data
private:
// Listen the event on the port
static void OnHttpEvent(mg_connection *connection, int event_type,
void *event_data);
// Handle the http request with the definite url.
static void HandleHttpEvent(mg_connection *connection,
http_message *http_req);
std::string m_port_;
mg_mgr m_mgr_;
};
2. Rocksdb单机引擎使用
大家需要高级功能可以扩展,这里仅仅是使用了一些基本的接口调度起了rocksdb
#pragma once
#include <iostream>
#include <string>
#include "rocksdb/db.h"
#include "rocksdb/options.h"
class KVEngine {
public:
KVEngine(std::string path) : path_(path) {}
~KVEngine() {
if (db_) {
delete db_;
db_ = nullptr;
}
}
void Init() {
opt_.create_if_missing = true;
if (Open() != "ok") {
std::cout << "Open db failed " << std::endl;
}
}
std::string Open() {
auto s = rocksdb::DB::Open(opt_, path_, &db_);
if (!s.ok()) {
return s.ToString();
}
return "ok";
}
std::string Get(const std::string& key) {
if (nullptr == db_) {
return "db_ is nullptr, please init it.";
}
std::string tmp_val;
auto s = db_->Get(rocksdb::ReadOptions(), key, &tmp_val);
if (!s.ok()) {
return "not ok";
}
return tmp_val;
}
std::string Put(const std::string& key, const std::string& val) {
if (nullptr == db_) {
return "db_ is nullptr, please init it.";
}
auto s = db_->Put(rocksdb::WriteOptions(), key, val);
if (!s.ok()) {
std::cout << "Put failed " << s.ToString() << std::endl;
return s.ToString();
}
return "ok";
}
private:
std::string path_;
rocksdb::DB* db_;
rocksdb::Options opt_;
}
3. 模版工厂来创建 url 及其 handler
通过如下模版工厂,我们后续增加更多的URL的时候,不需要更改httpserver.cpp源码 ,仅仅需要增加一个扩展类 及其 实现,并将这个映射添加到全局映射表中就可以了。
template <class OperationType_t>
class OperationRegister {
public:
virtual OperationType_t* CreateOperation(
const std::string& op_name, mg_connection* conn, http_message* hm) = 0;
protected:
OperationRegister() = default;
virtual ~OperationRegister() = default;
};
// Factory class template
template <class OperationType_t>
class OperationFactory {
public:
// Single pattern of the factory
static OperationFactory<OperationType_t>& Instance() {
static OperationFactory<OperationType_t> instance;
return instance;
}
void RegisterOperation(const std::string& op_name,
mg_connection* conn, http_message* hm,
OperationRegister<OperationType_t>* reg) {
operationRegister[op_name] = reg;
}
OperationType_t* GetOperation(
const std::string& op_name,
mg_connection* conn, http_message* hm) {
if (operationRegister.find(op_name) != operationRegister.end()) {
return operationRegister[op_name]->CreateOperation(op_name, conn, hm);
}
return nullptr;
}
private:
// We don't allow to constructor, copy constructor and align constructor
OperationFactory() = default;
~OperationFactory() = default;
OperationFactory(const OperationFactory&) = delete;
const OperationFactory& operator= (const OperationFactory&) = delete;
std::map<std::string, OperationRegister<OperationType_t>* > operationRegister;
};
// An template class to create the detail Operation
template <class OperationType_t, class OperationImpl_t>
class OperationImplRegister : public OperationRegister<OperationType_t> {
public:
explicit OperationImplRegister(
const std::string& op_name, mg_connection* conn, http_message* hm) {
OperationFactory<OperationType_t>::Instance().RegisterOperation(op_name, conn, hm, this);
}
OperationType_t* CreateOperation(
const std::string& op_name, mg_connection* conn, http_message* hm) {
return new OperationImpl_t(op_name, conn, hm);
}
};
后续仅仅需要将对应的URL 字符串 及其实现类添加到如下映射表中就可以了。
// Register all the http request's input string and their Class pointer.
void InitializeAllOp(mg_connection* conn, http_message* hm) {
static bool initialize = false;
if (!initialize) {
static OperationImplRegister<Url, GetValue>
getValue("/test/getvalue", conn, hm);
static OperationImplRegister<Url, SetValueUrl>
setValue("/test/setvalue", conn, hm);
static OperationImplRegister<Url, RouteUrl>
routeUrl("/", conn, hm);
initialize = true;
}
}
我们在实际
HandleHttpEvent
逻辑中就不需要做任何更改,十分友好得提升了代码得可扩展性。
void HttpServer::HandleHttpEvent(mg_connection *connection, http_message *http_req) {
std::string req_str = std::string(http_req->message.p, http_req->message.len);
std::string url = std::string(http_req->uri.p, http_req->uri.len);
InitializeAllOp(connection, http_req);
// Register the operation for the url
auto *judge = new JudgeOperation(connection, http_req);
auto res = judge->Judge(url);
if (res != "ok") {
SendHttpRsp(connection, res);
}
}
关于模版工厂的细节可以参考:C++ 通过模版工厂实现 简单反射机制
编译及使用
1. 编译
编译之前需要确保测试环境已经成功安装了rocksdb。
git clone https://github.com/BaronStack/PersistentHttpserver.git
cd PersistentHttpserver
make httpserver
rocksdb的on mac安装:
brew install rocksdb
rocksdb的on linux安装:rocksdb-Install
2. 使用
- 第一个console :
./httpserver
- 第二个console:
╰─$ curl -d "value=firstvalue" 127.0.0.1:7999/test/setvalue
{ "result": ok }
设置了一个数值之后可以看到httpserver运行的目录处 生成了一个db目录:
db
|-- 000010.sst
|-- 000013.sst
|-- 000016.sst
|-- 000019.sst
|-- 000025.sst
|-- 000030.log
|-- CURRENT
|-- IDENTITY
|-- LOCK
|-- LOG
|-- MANIFEST-000029
|-- OPTIONS-000029
`-- OPTIONS-000032
停止第一个./httpserver 进程,重新运行,在第二个终端再此输入获取数据的请求命令
╰─$ curl -d "value=firstvalue" 127.0.0.1:7999/test/getvalue
{ "result": firstvalue }
可以看到能够获取到重启server之前的数据。