Golang运行时模块的启用与禁用指南

Golang运行时模块的启用与禁用指南 我正在考虑构建一个采用类似Apache模块(概念上,而非Go模块)方法的应用程序。我将设置一个modules_available目录和一个modules_enabled目录,要启用模块时只需创建从available到enabled的符号链接。我希望这些更改能在运行时生效,而不需要用户重新编译应用程序。

如果有所帮助的话,我认为所有模块都将具有相同的接口,包含几个函数:接收数据并返回处理后的数据。

实现这个功能有多容易?如果容易的话,是否有相关的详细教程可以参考?

作为背景说明,我正考虑重写这个Ruby应用程序以更好地利用多线程功能。

GitHub GitHub

头像

digininja/pipal

pipal - 密码分析工具

在Ruby中,动态加载所有启用的检查器很容易,但由于我是Go语言的新手,我仍在尝试弄清楚哪些是可行的、哪些可行但不应该做、以及哪些是不可能的。


更多关于Golang运行时模块的启用与禁用指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

Go 插件可能很有用,请参阅此处

更多关于Golang运行时模块的启用与禁用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


今天早上我尝试使用模块和接口,找到了一种可行的方法,但在添加新模块时需要做一些额外工作。结合这个方案,我认为正好能满足我的需求。谢谢。

值得提醒的是,除非你确实需要在运行时加载代码,否则我不会建议这样做。通过在编译时编译模块并根据配置文件或类似机制启用/禁用它们,你的工作会变得更简单。

这看起来应该可行。从示例代码来看,我会获取启用目录的列表,然后遍历所有文件并对它们调用open操作。

可以预见这个周末我会绞尽脑汁尝试让它运行起来,同时还要理解所有相关概念,但我很期待这个过程。

你可以发送(可能交叉编译的)包含更新文件的二进制文件 🙂

插件功能很棒,但截至 Go-1.10.x

  • 仅适用于 {linux,darwin}-amd64 平台
  • 尚未完全解决所有错误

所以……效果可能因人而异。

为什么不呢?是因为在Go语言中这不是一个好的做法,难以操作,还是有其他原因?

目前的工作方式是,用户安装应用程序后,如果需要对模块进行一些小调整,我可以直接发送单个文件给他们。他们只需将该文件以不同名称放入目录中,就能立即看到变化。如果使用单个编译后的二进制文件,这类小调整可能会更困难。

在Go语言中实现这一功能的常见方法是使用每个文件的init()函数,将文件中的函数或类型注册到某个包级变量中。我之前在论坛上写过示例,但找不到了……基本思路如下:

main.go

var registry = make(map[string]func() string)

func main() {
  for name, fn := registry {
    fmt.Println(name, "says", fn())
  }
}

foo.go

func init() {
  registry["foo"] = foo
}

func foo() string {
  return "hello"
}

输出结果:“foo says hello”

如果在构建时执行此操作,我希望能够以尽可能少的精力灵活添加新模块。我在 Ruby 中的实现方式是构建一个模块数组,然后遍历该数组,对每个模块调用相同的函数。

模块无需保持任何状态,因此不需要实例化为对象,它们可以是类方法或库中的函数。这些函数都使用相同的名称,只是位于不同的命名空间中(请原谅可能不准确的术语)。

大致伪代码如下:

mod_list = dir (module_directory)
for each mod_name in mod_list
  mod_array << mod_name.register()
end

for each item I want to parse
  for each module in mod_array
    results[module name] = module.parse(data)
  end
end

for each module in array
  print module.generate_results (results[module name])
end

在Go中实现运行时模块的动态加载是可行的,但需要采用插件机制而非符号链接方式。Go的plugin包(自Go 1.8起支持)允许动态加载编译好的共享库,但存在跨平台限制(主要支持Linux)。以下是实现方案:

首先定义模块接口:

// module.go
package main

type Processor interface {
    Process(input string) string
    Name() string
}

var Modules map[string]Processor

主程序动态加载插件:

// main.go
package main

import (
    "fmt"
    "plugin"
    "path/filepath"
)

func loadModules() {
    Modules = make(map[string]Processor)
    
    // 读取modules_enabled目录
    enabledPath := "./modules_enabled"
    files, _ := filepath.Glob(filepath.Join(enabledPath, "*.so"))
    
    for _, file := range files {
        p, err := plugin.Open(file)
        if err != nil {
            continue
        }
        
        sym, err := plugin.Lookup("Plugin")
        if err != nil {
            continue
        }
        
        if processor, ok := sym.(Processor); ok {
            Modules[processor.Name()] = processor
        }
    }
}

func main() {
    loadModules()
    
    // 使用加载的模块处理数据
    input := "test data"
    for name, processor := range Modules {
        output := processor.Process(input)
        fmt.Printf("Module %s: %s -> %s\n", name, input, output)
    }
}

示例插件实现:

// modules_available/upper/upper.go
package main

import "strings"

type UpperProcessor struct{}

func (u *UpperProcessor) Process(input string) string {
    return strings.ToUpper(input)
}

func (u *UpperProcessor) Name() string {
    return "upper"
}

// 导出符号
var Plugin UpperProcessor

构建插件:

go build -buildmode=plugin -o modules_enabled/upper.so modules_available/upper/upper.go

关键限制:

  1. 插件必须使用相同Go版本编译
  2. 主程序和插件需共享接口定义(可通过公共包解决)
  3. 主要支持Linux系统
  4. 插件加载后无法卸载

替代方案可使用Hashicorp插件系统(基于RPC)或使用Lua/Python等脚本语言集成实现更灵活的跨平台动态加载。对于密码分析工具,建议将核心逻辑保持静态编译,仅对可扩展分析规则采用动态加载。

回到顶部