mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 11:29:48 +08:00
refactor: plugin structure
This commit is contained in:
235
plugin.go
235
plugin.go
@@ -1,235 +0,0 @@
|
||||
package hrp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/hrp/internal/ga"
|
||||
pluginHost "github.com/httprunner/hrp/plugin/host"
|
||||
pluginShared "github.com/httprunner/hrp/plugin/shared"
|
||||
)
|
||||
|
||||
type pluginFile string
|
||||
|
||||
const (
|
||||
goPluginFile pluginFile = pluginShared.Name + ".so" // built from go plugin
|
||||
hashicorpGoPluginFile pluginFile = pluginShared.Name + ".bin" // built from hashicorp go plugin
|
||||
hashicorpPyPluginFile pluginFile = pluginShared.Name + ".py"
|
||||
)
|
||||
|
||||
type hrpPlugin interface {
|
||||
init(path string) error // init plugin
|
||||
has(funcName string) bool // check if plugin has function
|
||||
call(funcName string, args ...interface{}) (interface{}, error) // call function
|
||||
quit() error // quit plugin
|
||||
}
|
||||
|
||||
// goPlugin implements golang official plugin
|
||||
type goPlugin struct {
|
||||
*plugin.Plugin
|
||||
cachedFunctions map[string]reflect.Value // cache loaded functions to improve performance
|
||||
}
|
||||
|
||||
func (p *goPlugin) init(path string) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
log.Warn().Msg("go plugin does not support windows")
|
||||
return fmt.Errorf("go plugin does not support windows")
|
||||
}
|
||||
|
||||
var err error
|
||||
// report event for loading go plugin
|
||||
defer func() {
|
||||
event := ga.EventTracking{
|
||||
Category: "LoadGoPlugin",
|
||||
Action: "plugin.Open",
|
||||
}
|
||||
if err != nil {
|
||||
event.Value = 1 // failed
|
||||
}
|
||||
go ga.SendEvent(event)
|
||||
}()
|
||||
|
||||
p.Plugin, err = plugin.Open(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", path).Msg("load go plugin failed")
|
||||
return err
|
||||
}
|
||||
|
||||
p.cachedFunctions = make(map[string]reflect.Value)
|
||||
log.Info().Str("path", path).Msg("load go plugin success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *goPlugin) has(funcName string) bool {
|
||||
fn, ok := p.cachedFunctions[funcName]
|
||||
if ok {
|
||||
return fn.IsValid()
|
||||
}
|
||||
|
||||
sym, err := p.Plugin.Lookup(funcName)
|
||||
if err != nil {
|
||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||
return false
|
||||
}
|
||||
fn = reflect.ValueOf(sym)
|
||||
|
||||
// check function type
|
||||
if fn.Kind() != reflect.Func {
|
||||
p.cachedFunctions[funcName] = reflect.Value{} // mark as invalid
|
||||
return false
|
||||
}
|
||||
|
||||
p.cachedFunctions[funcName] = fn
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *goPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
fn := p.cachedFunctions[funcName]
|
||||
return pluginShared.CallFunc(fn, args...)
|
||||
}
|
||||
|
||||
func (p *goPlugin) quit() error {
|
||||
// no need to quit for go plugin
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashicorpPlugin implements hashicorp/go-plugin
|
||||
type hashicorpPlugin struct {
|
||||
pluginShared.FuncCaller
|
||||
cachedFunctions map[string]bool // cache loaded functions to improve performance
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) init(path string) error {
|
||||
|
||||
f, err := pluginHost.Init(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", path).Msg("load go hashicorp plugin failed")
|
||||
return err
|
||||
}
|
||||
p.FuncCaller = f
|
||||
|
||||
p.cachedFunctions = make(map[string]bool)
|
||||
log.Info().Str("path", path).Msg("load hashicorp go plugin success")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) has(funcName string) bool {
|
||||
flag, ok := p.cachedFunctions[funcName]
|
||||
if ok {
|
||||
return flag
|
||||
}
|
||||
|
||||
funcNames, err := p.GetNames()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, name := range funcNames {
|
||||
if name == funcName {
|
||||
p.cachedFunctions[funcName] = true // cache as exists
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
p.cachedFunctions[funcName] = false // cache as not exists
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) call(funcName string, args ...interface{}) (interface{}, error) {
|
||||
return p.FuncCaller.Call(funcName, args...)
|
||||
}
|
||||
|
||||
func (p *hashicorpPlugin) quit() error {
|
||||
// kill hashicorp plugin process
|
||||
pluginHost.Quit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) initPlugin(path string) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// priority: hashicorp plugin > go plugin > builtin functions
|
||||
// locate hashicorp plugin file
|
||||
pluginPath, err := locatePlugin(path, hashicorpGoPluginFile)
|
||||
if err == nil {
|
||||
// found hashicorp go plugin file
|
||||
p.plugin = &hashicorpPlugin{}
|
||||
return p.plugin.init(pluginPath)
|
||||
}
|
||||
|
||||
// locate go plugin file
|
||||
pluginPath, err = locatePlugin(path, goPluginFile)
|
||||
if err == nil {
|
||||
// found go plugin file
|
||||
p.plugin = &goPlugin{}
|
||||
return p.plugin.init(pluginPath)
|
||||
}
|
||||
|
||||
// plugin not found
|
||||
return nil
|
||||
}
|
||||
|
||||
// locatePlugin searches destPluginFile upward recursively until current
|
||||
// working directory or system root dir.
|
||||
func locatePlugin(startPath string, destPluginFile pluginFile) (string, error) {
|
||||
stat, err := os.Stat(startPath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var startDir string
|
||||
if stat.IsDir() {
|
||||
startDir = startPath
|
||||
} else {
|
||||
startDir = filepath.Dir(startPath)
|
||||
}
|
||||
startDir, _ = filepath.Abs(startDir)
|
||||
|
||||
// convention over configuration
|
||||
pluginPath := filepath.Join(startDir, string(destPluginFile))
|
||||
if _, err := os.Stat(pluginPath); err == nil {
|
||||
return pluginPath, nil
|
||||
}
|
||||
|
||||
// current working directory
|
||||
cwd, _ := os.Getwd()
|
||||
if startDir == cwd {
|
||||
return "", fmt.Errorf("searched to CWD, plugin file not found")
|
||||
}
|
||||
|
||||
// system root dir
|
||||
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
|
||||
if parentDir == startDir {
|
||||
return "", fmt.Errorf("searched to system root dir, plugin file not found")
|
||||
}
|
||||
|
||||
return locatePlugin(parentDir, destPluginFile)
|
||||
}
|
||||
|
||||
// callFunc calls function with arguments
|
||||
// only support return at most one result value
|
||||
func (p *parser) callFunc(funcName string, arguments ...interface{}) (interface{}, error) {
|
||||
// call with plugin function
|
||||
if p.plugin != nil && p.plugin.has(funcName) {
|
||||
return p.plugin.call(funcName, arguments...)
|
||||
}
|
||||
|
||||
// get builtin function
|
||||
function, ok := builtin.Functions[funcName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("function %s is not found", funcName)
|
||||
}
|
||||
fn := reflect.ValueOf(function)
|
||||
|
||||
// call with builtin function
|
||||
return pluginShared.CallFunc(fn, arguments...)
|
||||
}
|
||||
Reference in New Issue
Block a user