前言
本文介紹一個有趣的 通過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之前的資料。