plugin

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 13 Imported by: 0

README

gohttp-plugin

gohttp-plugin 是 gohttpd 的插件框架,用于将 HTTP 服务以独立进程(插件)的形式动态加载到 gohttpd 中。插件通过 hashicorp/go-plugin 的 net/rpc 协议与宿主进程通信。

特性

  • 独立进程隔离:每个插件是一个独立的可执行二进制文件,崩溃不会影响宿主进程。
  • 多服务支持:单个插件进程可同时服务多个 (ServerName, Path) 组合,每个组合可携带不同的配置。
  • 自动健康监控:宿主进程持续监控插件进程状态,进程退出后自动以指数退避策略重启。
  • 热加载:插件目录中的可执行文件在宿主启动时自动扫描加载。
  • 反向代理:宿主将匹配的请求反向代理到插件内部的 HTTP 服务端口。

架构

┌─────────────────────────────┐
│          gohttpd 宿主        │
│                             │
│  ┌───────────────────────┐  │
│  │   Plugin Manager       │  │
│  │  (plugin.go)           │  │
│  │  - 加载/注册/重启/关闭   │  │
│  │  - 健康监控             │  │
│  │  - 反向代理             │  │
│  └──────────┬────────────┘  │
│             │ net/rpc       │
└─────────────┼───────────────┘
              │
              ▼
┌─────────────────────────────┐
│       插件进程 (独立二进制)    │
│                             │
│  ┌───────────────────────┐  │
│  │   PluginServer (sdk)   │  │
│  │  - Meta/Start/ShutDown │  │
│  │  - Serve/UnServe       │  │
│  │  - 内部 HTTP 服务        │  │
│  └───────────────────────┘  │
└─────────────────────────────┘
插件加载流程
  1. 宿主启动时调用 LoadPlugins(PluginRoot) 扫描插件目录。
  2. 对每个可执行文件,通过 go-plugin 启动插件进程。
  3. 宿主通过 RPC 调用 Meta() 获取插件名称。
  4. 宿主通过 RPC 调用 Start() 初始化插件资源。
  5. 健康监控 goroutine 监视进程退出并自动重启。
路由分发流程

当路由引用某个插件时:

  1. GetHandler(serverName, path, name, options) 返回反向代理。
  2. 若该 (serverName, path) 服务尚未启动,宿主调用 Serve() 启动它。
  3. 插件在 127.0.0.1:0(随机端口)启动内部 HTTP 服务并返回实际端口。
  4. 宿主创建指向该端口的反向代理,将匹配请求转发过去。

目录结构

gohttp-plugin/
├── plugin.go           # 宿主端插件管理器(加载、注册、重启、健康监控、反向代理)
├── protocol/
│   ├── protocol.go     # GoHttpPlugin 接口定义(宿主与插件共享的契约)
│   └── rpc.go          # net/rpc 服务端/客户端封装及 go-plugin 适配器
├── sdk/
│   └── server.go       # 插件开发 SDK(PluginServer、HandlerFactory、Serve 入口)
├── cmd/
│   └── my-plugin/
│       └── main.go     # 示例插件(demo)
├── go.mod
├── go.sum
└── LICENSE

快速开始

编写一个插件

插件是一个独立的 Go 程序,使用 sdk 包实现 protocol.GoHttpPlugin 接口。

package main

import (
	"fmt"
	"net/http"

	"gitee.com/kingecg/gohttp-plugin/sdk"
)

func main() {
	srv := &sdk.PluginServer{
		Name:    "my-plugin",
		Version: "1.0.0",
		HandlerFactory: func(config map[string]interface{}) http.Handler {
			greeting := "Hello from plugin!"
			if g, ok := config["greeting"].(string); ok {
				greeting = g
			}
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				fmt.Fprintln(w, greeting)
			})
		},
	}
	sdk.Serve(srv)
}
构建插件
go build -o plugins/my-plugin ./cmd/my-plugin/
配置 gohttpd

在 gohttpd 的配置文件中指定插件目录和路由:

{
    "plugin_root": "./plugins",
    "servers": [{
        "name": "demo",
        "port": 8080,
        "paths": [{
            "path": "/my-plugin",
            "plugin_name": "my-plugin",
            "plugin_options": { "greeting": "Hello from path A!" }
        }, {
            "path": "/my-plugin-b",
            "plugin_name": "my-plugin",
            "plugin_options": { "greeting": "Hello from path B!" }
        }]
    }]
}

同一个插件进程即可同时服务两个路径,且返回不同的问候语。

SDK 使用指南

PluginServer

PluginServer 是插件端的主要辅助类型,实现了 protocol.GoHttpPlugin 接口。插件作者设置以下字段后调用 sdk.Serve() 即可:

字段 类型 说明
Name string 插件名称,用于 HttpPath.PluginName 配置
Version string 插件版本号(仅信息用途)
Handler http.Handler 静态 HTTP 处理器,所有请求都转发给它
HandlerFactory HandlerFactory 根据配置创建 HTTP 处理器的工厂函数,支持多服务不同配置

注意HandlerFactory 优先于 Handler。若两者都未设置,插件将返回 404。

HandlerFactory
type HandlerFactory func(config map[string]interface{}) http.Handler

Serve() 为每个不同的 (serverName, path) 组合调用一次 HandlerFactory,传入该组合的配置,返回对应的处理器。这使得单个插件进程可以同时服务多个路径,且每个路径使用不同的配置。

生命周期方法

插件作者可通过覆盖 PluginServer 的方法实现自定义生命周期逻辑:

方法 调用时机 说明
Meta() 插件加载时 返回插件名称和版本
Start() 插件加载时(Meta 之后) 初始化插件自身资源
Serve(serverName, path, config) 路由首次引用时 启动内部 HTTP 服务,返回端口
UnServe(serverName, path) 路由移除或配置变更时 停止内部 HTTP 服务
ShutDown() 插件关闭时 释放插件资源并停止所有服务

宿主端 API(plugin.go)

宿主端(gohttpd)通过 plugin 包管理插件:

函数/方法 说明
LoadPlugins(pluginRoot) 扫描目录并加载所有可执行插件
GetManager() 获取全局插件管理器单例
Manager.GetHandler(serverName, path, name, options) 获取插件的反向代理处理器
Manager.UnServe(serverName, path, name) 停止指定服务
Manager.RestartPlugin(name) 手动重启插件
Manager.Shutdown() 优雅关闭所有插件
健康监控与自动重启

每个插件实例启动后,monitorHealth() goroutine 每 5 秒检查一次插件进程状态。若进程退出:

  1. 等待退避时间(初始 1 秒,指数递增,最大 30 秒)。
  2. 调用 RestartPlugin() 重启插件进程。
  3. 重启成功后,自动重新服务所有已注册的 (serverName, path) 组合(使用保存的配置)。
  4. 退避时间重置为 1 秒。

协议(protocol 包)

protocol 包定义了宿主与插件之间的共享契约,双方都导入此包。

GoHttpPlugin 接口
type GoHttpPlugin interface {
	Meta() (PluginMeta, error)
	Start() error
	ShutDown() error
	Serve(ServerName, Path string, Config map[string]interface{}) (int, error)
	UnServe(ServerName, Path string) error
}
RPC 通信
  • GoHttpPluginRPCServer:运行在插件进程内,包装真实的实现并通过 net/rpc 暴露。
  • GoHttpPluginRPCClient:运行在宿主进程内,通过 net/rpc 调用远程插件。
  • GoHttpPluginPlugin:go-plugin 适配器,注册在插件集合中,名称固定为 "gohttp-plugin"
握手配置

宿主与插件使用相同的握手配置:

plugin.HandshakeConfig{
	ProtocolVersion:  1,
	MagicCookieKey:   "GOHTTPD_PLUGIN",
	MagicCookieValue: "v1",
}

通信协议为 net/rpcAllowedProtocols: [plugin.ProtocolNetRPC])。

插件加载规则

  • 插件目录中的可执行文件会被加载为插件。
  • 目录、非可执行文件会被静默跳过。
  • .so 文件(旧插件格式)会被跳过。
  • 插件名称(Meta() 返回值)必须唯一,重复名称会导致加载失败。
  • 插件名称不能为空。

依赖

许可证

MIT

Documentation

Overview

Package plugin provides the host-side plugin manager for gohttpd.

Plugins are standalone executable binaries that communicate with the host via hashicorp/go-plugin's net/rpc protocol. Each plugin is a single long-running process that can serve multiple HTTP services simultaneously, each identified by a (ServerName, Path) pair.

Architecture

A plugin process manages its own lifecycle (Meta, Start, ShutDown) and can start/stop individual HTTP services (Serve, UnServe). The host calls Serve() for each server/path combination that references the plugin, and the plugin starts an internal HTTP server for each one, returning the listening port. The host then reverse-proxies matching requests to that port.

This allows a single plugin to serve multiple different server/path combinations with different configurations simultaneously, using only one plugin process.

Plugin loading flow:

  1. Server startup calls LoadPlugins(PluginRoot) from gohttp.go
  2. LoadPlugins scans the directory for executable files
  3. For each executable, startPlugin() launches the plugin process via hashicorp/go-plugin
  4. The host calls Meta() via RPC to get the plugin's name
  5. The host calls Start() via RPC to initialize plugin resources
  6. A health monitoring goroutine watches for process exits and auto-restarts

When a route references a plugin name:

  1. GetHandler(serverName, path, name, options) returns the reverse proxy
  2. If the (serverName, path) service is not yet started, the host calls Serve(serverName, path, options) to start it and creates a reverse proxy to the returned port

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultErrorHandler

func DefaultErrorHandler(pluginName string) http.Handler

DefaultErrorHandler returns an HTTP handler that returns a 500 error indicating the plugin was not found.

func LoadPlugins

func LoadPlugins(pluginRoot string) (int, error)

LoadPlugins scans the given directory for executable files and loads them as plugins. Each executable is launched as a child process via hashicorp/go-plugin. Non-executable files, directories, and .so files are silently skipped.

Returns the number of successfully loaded plugins.

Types

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages all loaded plugin instances.

func GetManager

func GetManager() *Manager

GetManager returns the global plugin manager.

func (*Manager) Get

func (m *Manager) Get(name string) *PluginInstance

Get returns the plugin instance for the given name. Returns nil if the plugin does not exist.

func (*Manager) GetHandler

func (m *Manager) GetHandler(serverName, path, name string, options map[string]interface{}) http.Handler

GetHandler returns an http.Handler for the named plugin serving the given server/path combination with the given options.

If the (serverName, path) service is already started, its reverse proxy is returned. Otherwise the host calls Serve(serverName, path, options) to start a new HTTP service within the plugin and creates a reverse proxy to it. This allows a single plugin process to serve multiple server/path combinations with different configurations simultaneously.

func (*Manager) Register

func (m *Manager) Register(inst *PluginInstance)

Register adds a plugin instance to the manager.

func (*Manager) RestartPlugin

func (m *Manager) RestartPlugin(name string) error

RestartPlugin restarts a plugin by name. Returns an error if the plugin is not found or fails to restart.

func (*Manager) Shutdown

func (m *Manager) Shutdown()

Shutdown gracefully stops all plugins and kills their processes. This should be called during server shutdown.

func (*Manager) UnServe

func (m *Manager) UnServe(serverName, path, name string)

UnServe stops the HTTP service for the given server/path combination. It is called when a route is removed or its config changes.

type PluginInstance

type PluginInstance struct {
	Name       string                // plugin name (from Meta())
	ExecPath   string                // path to the plugin executable
	Client     *plugin.Client        // go-plugin client (manages the plugin process)
	PluginImpl protocol.GoHttpPlugin // RPC client to the plugin
	// contains filtered or unexported fields
}

PluginInstance represents a running plugin process. A single plugin process can serve multiple HTTP services simultaneously, each identified by a (ServerName, Path) pair.

type PluginService

type PluginService struct {
	ServerName string                 // server name this service belongs to
	Path       string                 // path this service serves
	Config     map[string]interface{} // config used to start this service
	Port       int                    // port the plugin's HTTP server is listening on
	Proxy      *httputil.ReverseProxy // reverse proxy to the plugin's HTTP server
}

PluginService represents a single HTTP service served by a plugin. Each service has its own reverse proxy pointing to the plugin's internal HTTP server for that (ServerName, Path) combination.

Directories

Path Synopsis
cmd
my-plugin command
Package protocol defines the shared interface between the gohttpd host and its plugins.
Package protocol defines the shared interface between the gohttpd host and its plugins.
Package sdk provides helper types and functions for writing gohttpd plugins.
Package sdk provides helper types and functions for writing gohttpd plugins.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL