首页 > 手把手教你 用C++实现一个 可持久化 的http_server

手把手教你 用C++实现一个 可持久化 的http_server

前言

本文介绍一个有趣的 通过C++实现的 持久化的http_server demo,这样我们通过http通信之后的数据可以持久化存储,即使server挂了,数据也不会丢失。我们的http_sever 也就能够真正得作为一个后端server了。

本身持久化这个能力是数据库提供的,像通用的http交互数据都会通过SQL server或者MySQL这样的存储系统来存储关系型数据,而这里我只是调度了一个单机存储引擎作为持久化存储。

主要用到的一些技术:

  1. mongoose C语言 网络通信库,在此库基础上实现了一个C++的httpserver
  2. Rocksdb 单机存储引擎,作为持久化通信数据的存储。
  3. 通过模版工厂来优雅得创建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 variablebool Start(); // Start a http server with a portbool Close();// Send a message as a http requeststatic void SendHttpRsp(mg_connection *connection, std::string rsp);static mg_serve_http_opts s_server_option;static KVEngine *kv_engine_; // For persistent the dataprivate:// Listen the event on the portstatic 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 
#include #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 factorystatic 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 constructorOperationFactory() = 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 urlauto *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之前的数据。

有了针对HTTP-SERVER的持久化能力和友好的可扩展性代码,那我就可以持续玩一些有持久化能力的用户需求了。

当然实际中的httpserver请求上层的封装到底层的存储服务都会复杂千万倍,正真能够支持分布式一致性服务的底层存储 不论是newSQL还是NoSQL都会是非常复杂的实现过程。

更多相关:

  • ng g s services/http  app.module.ts ... @NgModule({declarations: [...],imports: [...HttpClientModule,//这个很重紧要,没有就会报错],providers: [],bootstrap: [AppComponent] }) expor...

  • set-misc-nginx-module模块是标准的HttpRewriteModule指令的扩展,提供更多的功能,如URI转义与非转义、JSON引述、Hexadecimal/MD5/SHA1/Base32/Base64编码与解码、随机数等等。在后面的应用中,都将会接触使用到这个模块的。该模块是由章亦春先生开发的,他开发的其他模块应用...

  • 该源码包是MySQL-python-1.2.4b4.tar.gz 从2013-06-28以来一直没有更新,注意该网站可以区分访问的终端类型是Windows还是Linux之类的,从而返回的源码包格式不一样。 在CentOS上的安装方法是 http://www.cnblogs.com/jackluo/p/3559978.html...

  • ATS默认提供了对Referer头的http request的防盗链功能,主要应用于图片,对视频等会使用级别更高的防盗链功能,比如事先约定好key,采用md5或HMAC-Sha1算法加密等。 在remap.config中按如下格式设置: map_with_referer client-URL origin-server-URL re...

  • 测试大文件下载 curl -I "http://resource.tsk.erya100.com/TS/flv/TS180/5836/9.flv?t=1430796561727" 单条转发模式in per remap mode 在remap.config中添加一条 map http://resource.tsk.e...

  • 经过长期探索,发现一个不需要手动设置线程休眠时间(e.g. std::this_thread::sleep_for(std::chrono::microseconds(1)))的代码: Github: https://github.com/log4cplus/ThreadPool #ifndef THREAD_POOL_H_7e...

  • nth_element(first,nth,last) first,last 第一个和最后一个迭代器,也可以直接用数组的位置。  nth,要定位的第nn 个元素,能对它进行随机访问. 将第n_thn_th 元素放到它该放的位置上,左边元素都小于它,右边元素都大于它. 测试代码: http://www.cplusplus.com...

  • c/c++老版本的rand()存在一定的问题,在转换rand随机数的范围,类型或者分布时,常常会引入非随机性。 定义在 中的随机数库通过一组协作类来解决这类问题:随机数引擎 和 随机数分布类 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将(引擎和分布对象)定义为 st...

  • jsoncpp 是一个C++ 语言实现的json库,非常方便得支持C++得各种数据类型到json 以及 json到各种数据类型的转化。 一个json 类型的数据如下: {"code" : 10.01,"files" : "","msg" : "","uploadid" : "UP000000" } 这种数据类型方便我们人阅读以...

  • 问题如下: 已知一组数(其中有重复元素),求这组数可以组成的所有子集中,子 集中的各个元素和为整数target的子集,结果中无重复的子集。 例如: nums[] = [10, 1, 2, 7, 6, 1, 5], target = 8 结果为: [[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]] 同样之前有...

  • importjava.security.SecureRandom;importjavax.crypto.Cipher;importjavax.crypto.SecretKey;importjavax.crypto.SecretKeyFactory;importjavax.crypto.spec.DESKeySpec;//结果与DES算...

  • 题目:替换空格 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 输入:s = "We are happy." 输出:"We%20are%20happy." 限制: 0 <= s 的长度 <= 10000 解题: 时间复杂度:O(n) 空间复杂度:O(n) class Solution { public:s...

  • 在C++11标准库中,string.h已经添加了to_string方法,方便从其他类型(如整形)快速转换成字面值。 例如: for (size_t i = 0; i < texArrSize; i++)RTX_Shader.SetInt(string("TexArr[") + to_string(i) + "]", 7 + i);...

  • Ubuntu 14.04安装并升级之后,变成楷体字体非常难看,我昨天搞了一晚上,终于理了个头绪,这里整理一下。 经过网上调研,大家的一致看法是,使用开源字体库文泉驿的微黑字体效果比较理想,甚至效果不输windows平台的雅黑字体。下面我打算微黑来美化Ubuntu 14.04. 1.安装文泉驿微黑字体库 sudo aptitude...

  • 使用string时发现了一些坑。 我们知道stl 容器并不是线程安全的,所以在使用它们的过程中往往需要一些同步机制来保证并发场景下的同步更新。 应该踩的坑还是一个不拉的踩了进去,所以还是记录一下吧。 string作为一个容器,随着我们的append 或者 针对string的+ 操作都会让string内部的数据域动态增加,而动态增加的...