RTTR實現高擴展性的c++ http服務端

前言

          以前寫過一篇 c++利用RTTR實現插件式加載動態庫html

         今天在這個基礎上結合http服務器,實現一個插件式動態擴展的http服務端,須要一個httplib.h的頭文件來提供http的功能,在java spring框架中能夠直接在方法上加上一個RequestMapping就能夠增長一個web api接口,本文目的也是如此,引入一個http服務,而後經過RTTR添加相對應的元數據直接將方法注入到http服務上去,並藉助rttr的提供json序列化功能自動完成數據的轉換,直接將代碼轉換爲http api.項目自己依賴glog和RTTR.java

 

API部分

TestAPI.hc++

#include <string>
#include <rttr/registration.h>
#include <ostream>

struct Parameter {
    std::string name;
    int age;

    Parameter();

    friend std::ostream &operator<<(std::ostream &os, const Parameter &parameter) {
        os << "name: " << parameter.name << " age: " << parameter.age;
        return os;
    }

RTTR_ENABLE();
};

struct Result : Parameter {
    std::string result;

    Result();

RTTR_ENABLE(Parameter);
};

struct TestAPI {
    TestAPI();

    virtual Result call(Parameter parameter) = 0;

    virtual ~TestAPI() {};

    virtual void test() = 0;
};

此次咱們加入了Parameter和Result兩個對象參數.git

TestAPI.cppgithub

#include "TestAPI.h"
Parameter::Parameter() {

}

Result::Result() {

}

建議將構造函數寫入到cpp文件,哪怕是空的這樣能保證相RTTR會正確連接.web

Registration.cpp算法

#include "TestAPI.h"

RTTR_REGISTRATION {
    rttr::registration::class_<Parameter>("Parameter")
            .

                    constructor<>()(
                    rttr::policy::ctor::as_object
            )

            .property("age", &Parameter::age)
            .property("name", &Parameter::name);

    rttr::registration::class_<Result>("Result")
            .

                    constructor<>()(
                    rttr::policy::ctor::as_object
            )

            .property("result", &Result::result);
}

此次加入Parameter和Result對象的RTTR支持,這樣就可使用JSON的系列化功能了.rttr::policy::ctor::as_object注意這裏是以對象方式建立的,RTTR支持對象或者是std::shared_ptr建立,若是類型錯誤可能致使一些反射功能代碼不能正常運行.spring

Imple1API

class Imple1Api : public TestAPI {
public:
    Imple1Api();

    ~Imple1Api();

    virtual void test();

    virtual Result call(Parameter parameter);
};
#include "Imple1Api.h"

TestAPI::TestAPI() {

}

Imple1Api::Imple1Api() {

}

Imple1Api::~Imple1Api() {

}

void Imple1Api::test() {
    LOG_INFO << "hello i am  Imple1Api";
}

Result Imple1Api::call(Parameter parameter) {
    LOG_INFO << "Imple1Api call" << parameter;
    Result result;
    result.name = parameter.name;
    result.age = parameter.age;
    result.result = "Imple1Api result ";
    return result;
}

Registration.cppjson

#include "Imple1Api.h"

RTTR_PLUGIN_REGISTRATION {
    rttr::registration::class_<Imple1Api>("Imple1Api")
            (
                    rttr::metadata("API_VENDOR", "Imple1Api"),
                    rttr::metadata("API_VERSION", "v1"),
                    rttr::metadata("API_TYPE", "WEB")
            )
            .constructor<>()
            .method("test",
                    rttr::select_overload<void()>(&TestAPI::test)
            ).method("call",
                     rttr::select_overload<Result(Parameter)>(&TestAPI::call)
            );
};

關鍵點:此次將call方法註冊到RTTR,並加上了API_VERSION API_TYPE 這兩個元數據支持web api,實際上這裏的元數據能夠根據本身業務須要進行擴展,好比多是驅動api,多是算法api,而後每一種api都會被不一樣的管理器管理起來,以實現不一樣的訪問控制功能,這裏的代碼就是隻簡單實現了一個WEB服務的管理器,提供web的api,能夠想象的到這種擴張方式是很是靈活的.api

 

Imple2API

和Imple1API幾乎同樣.

class Imple2Api : public TestAPI {
public:
    Imple2Api();

    ~Imple2Api();

    virtual void test();

    virtual Result call(Parameter parameter);
};
#include "Imple2Api.h"

TestAPI::TestAPI() {

}

Imple2Api::Imple2Api() {

}

Imple2Api::~Imple2Api() {

}

void Imple2Api::test() {
    LOG_INFO << "hello i am Imple2Api";
}

Result Imple2Api::call(Parameter parameter) {
    LOG_INFO << "Imple2Api call";
    Result result;
    result.name = parameter.name;
    result.age = parameter.age;
    result.result = "Imple2Api result ";
    return result;
}
RTTR_PLUGIN_REGISTRATION {
    rttr::registration::class_<Imple2Api>("Imple2Api")
            (
                    rttr::metadata("API_VENDOR", "Imple2Api"),
                    rttr::metadata("API_VERSION", "v2"),
                    rttr::metadata("API_TYPE", "WEB")
            )
            .constructor<>()
            .method("test",
                    rttr::select_overload<void()>(&TestAPI::test)
            ).method("call",
                     rttr::select_overload<Result(Parameter)>(&TestAPI::call)
            )
            ;
};

類加載器

#include <string>
#include <vector>
#include <rttr/registration.h>
#include <rttr/library.h>
#include <map>

class Context {
public:
    Context();

    template<typename T>
    const std::shared_ptr<T> create(std::string vendor) {
        for (auto pair:_typeMap) {
            for (auto t: pair.second) {
                if (t.is_derived_from<T>()) {
                    if (t.get_metadata("API_VENDOR").to_string() == vendor) {
                        const rttr::variant &var = t.create();
                        return var.get_value<std::shared_ptr<T>>();
                    }
                }
            }
        }
        throw std::invalid_argument("can not find api for " + vendor);
    };

    void loadLibrary(std::string libPath) {
        std::shared_ptr<rttr::library> lib = std::shared_ptr<rttr::library>(new rttr::library(libPath));

        if (!lib->load()) {
            LOG_ERROR << "load library error " << lib->get_error_string();
        }
        {
            std::vector<rttr::type> temp;
            for (auto t : lib->get_types()) {
                if (t.is_class() && !t.is_wrapper()) {
                    LOG_INFO << "find class " << t.get_name();
                    temp.push_back(t);
                    _types.push_back(t);
                }
            }
            _typeMap.insert({lib, temp});
        }
    };

    std::vector<rttr::type> getAllType() {
        return _types;
    }

private:
    std::map<std::shared_ptr<rttr::library>, std::vector<rttr::type>> _typeMap;
    std::vector<rttr::type> _types;
};
#include "Context.h"
Context::Context() {
}

加載器基本沒有變化,直接添加一個新的數據結構以方便調用.

 

測試代碼

#include <TestAPI.h>
#include "Context.h"
#include "httplib.h"

void initWebServer(std::vector<rttr::type> types, httplib::Server &svr) {
    for (int i = 0; i < types.size(); ++i) {
        if (types[i].get_metadata("API_TYPE") == "WEB") {
            std::string version = types[i].get_metadata("API_VERSION").to_string();
            auto obj2 = types[i].create();
            auto ms = types[i].get_methods();
            for (auto m:ms) {
                std::string methodName = m.get_name().data();
                std::string url = "/" + version + "/" + methodName;
                LOG_INFO << url;
                rttr::array_range<rttr::parameter_info> infos = m.get_parameter_infos();
                if (infos.size() > 1) {
                    LOG_ERROR << "web api only support one json object";
                    continue;
                }
                svr.Post(url.data(), [infos, obj2, m](const httplib::Request &req, httplib::Response &res) {
                    if (infos.size() == 1) {
                        rttr::variant var = infos.begin()->get_type().create();
                        std::string text = req.body;
                        parseJSonObject(text, var);//請參照官方代碼的jsondemo
                        std::vector<rttr::argument> args{var};
                        rttr::variant result = m.invoke_variadic(obj2, args);
                        //請參照官方代碼的轉爲json的demo
                        res.set_content(toJSonString(result), "application/json");
                    } else {
                        rttr::instance result = m.invoke(obj2);
                        res.set_content(toJSonString(result), "application/json");
                    }

                });
            }
        }
    }
}

int main() {
    httplib::Server svr;
    Context context;
    context.loadLibrary(
            "libImpl1API.so");
    context.loadLibrary(
            "libImpl2API.so");

    initWebServer(context.getAllType(), svr);

    svr.set_error_handler([](const httplib::Request & /*req*/, httplib::Response &res) {
        const char *fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
        char buf[BUFSIZ];
        snprintf(buf, sizeof(buf), fmt, res.status);
        res.set_content(buf, "text/html");
    });

    svr.listen("0.0.0.0", 8080);

    return 0;
}

總結

          能夠看到分別將兩個庫中註冊的類型和方法,按照元數據指定的方式直接添加到http 服務註冊成api.甚至改成熱加載也不是不很是複雜的事情.更爲關鍵的是這種擴展方式是徹底的水平方向擴展,對於開發者也很是友善,只要實現指定的api接口,填寫上本身的api相關元數據既可.沒有繁瑣宏定義,也沒有框架上的限制,api接口,以及元數據的擴展都是動態的能夠擴展的

相關文章
相關標籤/搜索