Rust 实现动态库加载和基于此功能实现的插件管理

栏目: 编程语言 · Rust · 发布时间: 6年前

内容简介:最近开发后端 UCenter 服务,考虑到该服务核心逻辑变动相对少,稳定性要求高,单点处理能力要强,且 IO 瓶颈较少(大多缓存),有较多的校验和加解密工作,因此需要使用性能更为强劲的语言,最终考虑使用 Rust(无 GC、内存安全、性能与 C/C++ 差距不明显)以及其最近风头正劲的基于用户中心服务提供平台下属多个应用的统一授权工作,无论是第三方登录授权(如基于 OAuth 的授权或对外部以 OAuth 支持)、SSO(Single Sign-On)以及解决跨应用用户数据共享、互通等。虽然服务核心逻辑十

最近开发后端 UCenter 服务,考虑到该服务核心逻辑变动相对少,稳定性要求高,单点处理能力要强,且 IO 瓶颈较少(大多缓存),有较多的校验和加解密工作,因此需要使用性能更为强劲的语言,最终考虑使用 Rust(无 GC、内存安全、性能与 C/C++ 差距不明显)以及其最近风头正劲的基于 Actor 模型的 Web 框架: Actix-web 。对于框架和语言的介绍我会另起文章,先说说这个用户中心服务。

用户中心服务提供平台下属多个应用的统一授权工作,无论是第三方登录授权(如基于 OAuth 的授权或对外部以 OAuth 支持)、SSO(Single Sign-On)以及解决跨应用用户数据共享、互通等。虽然服务核心逻辑十分稳定,但对于各类子应用接入会有较多的拓展需求,例如不同应用的专用数据表的访问、多协议适配等。对于动态语言或存在虚拟机的语言而言,动态库加载相对简单,但对于 Rust 这种静态和无(或极小)运行时语言,拓展则相对困难。不过 Rust 提供了 FFI(Foreign Function Interface)支持,我们则利用这个实现我们的需求。

一个关键的库

虽然 Rust 提供了 FFI,拥有调用 ABI(Application Binary Interface)的能力,但我们肯定不是在这里仅仅引入一个确定的动态链接库,而是一个能够动态根据配置或自动读取目录列表自动加载链接库的功能,因此,我们需要使用这个库: libloading( https://crates.io/crates/libloading ),这个库提供了动态加载动态链接库(Dynamical Library)的能力,其原理是利用了不同系统提供的 API 实现的,例如 Windows 环境下,则通过 Win32 API GetProcAddress 实现动态加载。

该库提供的文档很清晰地展示了其用法,十分简单直接:

extern crate libloading as lib;

fn call_dynamic() -> lib::Result<u32> {
    let lib = lib::Library::new("/path/to/liblibrary.so")?;
    unsafe {
        let func: lib::Symbol<unsafe extern fn() -> u32> = lib.get(b"my_func")?;
        Ok(func())
    }
}

知道了如何动态加载库,我们就可以进行下一步。

定义 Trait 和插件拓展接口函数

由于 ABI(Application Binary Interface) 支持的类型有限,我们不能将整个拓展所有细节定义于 ABI 上,例如我们难免在系统中需要表现力丰富的枚举类型(例如及其常见的 Result<T, E> 以及 Option<T> )和一些必要的结构体,更重要的是,我们不希望有太多地方脱离了 Rust 的安全检查,除了引入外部接口必要的 unsafe 代码以外,最好一切都在掌控之下。

因此我们最好的做法就是通过一个 拓展接口函数 创建并返回一个实例(指针),该实例是一个已知 Rust Trait 的实现,在主服务拓展管理器中将实例指针引入转换为提供安全检查的容器里后,最终我们只与这个实例打交道,唯一和外部接触的,就是那个 拓展接口函数

我们需要先定义一个 Trait(下述代码示例包括一些私有定义,若希望自己实现可参考调整为自己的实现):

use std::any::Any;
use ucenter::UcenterResult;
use ucenter::socialite::SocialUserInfo;
use ucenter::database::Conn;

pub trait UcenterApp: Any + Send + Sync {
    /// 获取扩展名称
    fn name(&self) -> &'static str;

    /// 创建或更新用户
    fn create_or_update_user(&self, conn: &Conn, guid: u32, userinfo: SocialUserInfo) -> UcenterResult<u32>;

    /// 当拓展被加载时触发该事件
    fn on_extend_load(&self) {}
}

上述代码中,我们定义了三个方法,所有的拓展都必须要实现这第一个和第二个方法( on_extend_load 包含默认实现)。此时我们就可以实现一个拓展了,以下是拓展的实现代码(不保证正确,仅参考):

#[macro_use] extern crate serde_derive;
#[macro_use] extern crate ucenter;
#[macro_use] extern crate diesel;

use ucenter::UcenterResult;
use ucenter::socialite::SocialUserInfo;
use ucenter::database::Conn;

#[derive(Serialize, Deserialize, Debug, Clone, Insertable)]
#[table_name = "app_qa_system_users"]
pub struct CreateUserFromSocialite {
    pub guid: u32,
    pub wechat_openid: String,
    pub wechat_unionid: Option<String>,
    pub nickname: Option<String>,
    pub avatar: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone, Queryable)]
pub struct UserBaseDisplay {
    pub id: u32,
    pub global_user_id: u32,
    pub internal_user_id: Option<u32>,
}

/// 实现了 Default Trait,可以通过 QASystemExtend::default() 创建
#[derive(Default, Debug)]
pub struct QASystemExtend;

impl UcenterApp for QASystemAppExtend {
    fn name(&self) -> &'static str {
        "qa-system"
    }

    fn create_or_update_user(&self, conn: &Conn, g_uid: u32, userinfo: SocialUserInfo) -> UcenterResult<u32> {
        use ucenter::schema::app_qa_system_users::dsl::*;

        let result: bool = select(exists(app_qa_system_users.filter(guid.eq(g_uid))))
            .get_result(conn)
            .map_err(map_database_error("app_qa_system_users"))?;

        if !result {
            let create = CreateUserFromSocialite {
                guid: g_uid,
                openid: userinfo.id,
                unionid: Some(userinfo.unionid),
                nickname: userinfo.nickname,
                avatar: userinfo.avatar
            };

            diesel::insert_into(app_choujiang_users)
                .values(&create)
                .execute(conn)
                .map_err(map_database_error("app_qa_system_users"))?;

            let generate_id = last_insert_id!(conn, "app_qa_system_users") as u32;

            return Ok(generate_id);
        }

        let user = find_by_id!(conn => (
            app_qa_system_users((id, global_user_id, internal_user_id)) global_user_id = g_uid => UserBaseDisplay
        ))?;

        Ok(user.id)
    }
}

Ok,完工,现在我们需要定义 拓展接口函数 以供扩展管理器加载这个扩展:

pub extern "C" fn _app_extend_create() -> *mut ucenter::UcenterApp {
    // 创建对象
    let object = QASystemAppExtend::default();
    // 通过 Box 在堆上存储该对象实例
    let boxed: Box<ucenter::UcenterApp> = Box::new(object);
    // 返回原始指针(这是个 unsafe 调用,不过在 extern "C" 这种 ABI 定义处整个代码段都处于 unsafe 下,所以不用额外写 unsafe)
    Box::into_raw(boxed)
}

最后,我们需要在 Cargo.toml 处告知编译器,将其编译为动态链接库而非静态库,在 Cargo.toml 添加下述内容:

[lib]
crate-type = ["cdylib", "rlib"]

编译后,我们就得到了一个动态链接库( .so 文件或 .dll )。

拓展管理器的实现

拓展管理器我们要做的其实不多,加载的部分我们已经能够通过开头的 libloading 示例代码看出,不过这里仍然给出一个基本的实现方案:

pub struct AppExtendManager {
    path: String,
    extends: HashMap<String, Arc<Box<UcenterApp>>>,
    loaded_libraries: Vec<Library>,
}

impl AppExtendManager {
    pub fn new(path: String) -> AppExtendManager {
        AppExtendManager {
            path,
            extends: HashMap::new(),
            loaded_libraries: Vec::new(),
        }
    }

    pub fn load_all(&mut self) -> UcenterResult<()> {
        let r = fs::read_dir(self.path.clone()).map_err(|err| {
            UcenterError::system_io_error(Some(UcenterErrorDetail::String(format!("{:?}", err))))
        })?;

        for i in r {
            let entity = i.map_err(|err| {
                UcenterError::system_io_error(Some(UcenterErrorDetail::String(format!("{:?}", err))))
            })?;

            let path = entity.path();
            let match_ext = {
                if cfg!(target_os = "windows") {
                    path.extension().map(|v| v.to_str().unwrap()).unwrap_or("").eq("dll")
                } else {
                    path.extension().map(|v| v.to_str().unwrap()).unwrap_or("").eq("so")
                }
            };

            if path.is_file() && match_ext {
                unsafe {
                    self.load_extend(path)
                }?;
            }
        }

        Ok(())
    }

    pub unsafe fn load_extend<P: AsRef<OsStr>>(&mut self, filename: P) -> UcenterResult<()> {
        type ExtendCreator = unsafe fn() -> *mut UcenterApp;

        let lib = Library::new(filename.as_ref())
            .or(Err(UcenterError::system_extend_dynamical_error(
                Some(UcenterErrorDetail::String("Cannot load extend.".into()))
            )))?;

        self.loaded_libraries.push(lib);

        let lib = self.loaded_libraries.last().unwrap();
        let constructor: Symbol<ExtendCreator> = lib.get(b"_app_extend_create")
            .or(Err(UcenterError::system_extend_dynamical_error(
                Some(UcenterErrorDetail::String("The `_app_extend_create` symbol wasn't found.".into()))
            )))?;

        let boxed_raw = constructor();

        let extend = Box::from_raw(boxed_raw);
        extend.on_extend_load();

        debug!("Extend {} loadded.", extend.name());

        self.extends.insert(extend.name().to_string(), Arc::new(extend));

        Ok(())
    }

    pub fn select<T: Into<String>>(&self, target: T) -> UcenterResult<Arc<Box<UcenterApp>>>
    {
        let key: String = target.into();
        self.extends.get(&key).map(|v| v.clone()).ok_or(UcenterError::system_subsystem_error(None))
    }
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

面向对象技术UML教程

面向对象技术UML教程

王少峰 / 清华大学出版社 / 2004-2 / 24.00元

《面向对象技术UML教程》主要介绍统一建模语言UML及其应用。全书内容丰富,包括UML的用例图、顺序图、协作图、类图、对象图、状态图、活动图、构件图和部署图等9个图中所涉及的术语、规则和应用,以及数据建模、OCL、业务建模、Web建模、设计模式、OO实现语言、RUP等方面的内容,同时介绍了Rose开发工具中的一些用法。《面向对象技术UML教程》最后是一个课程注册系统的实例研究,以及一些思考题和设计......一起来看看 《面向对象技术UML教程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具