diff --git a/examples/plugin/admin/web_admin.go b/examples/plugin/admin/web_admin.go
new file mode 100644
index 0000000..28bb926
--- /dev/null
+++ b/examples/plugin/admin/web_admin.go
@@ -0,0 +1,460 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/darkit/goproxy/examples/plugin"
+)
+
+// 全局插件管理器
+var pm *plugin.PluginManager
+
+// AdminHandler 处理管理界面主页
+func AdminHandler(w http.ResponseWriter, r *http.Request) {
+ // HTML模板
+ tmpl := `
+
+
+
+
+ 插件管理系统
+
+
+
+ 插件管理系统
+
+
+
系统信息
+
操作系统: {{.OS}}
+
动态加载支持: {{if .DynamicLoadingSupported}}是{{else}}否{{end}}
+
插件目录: {{.PluginsDir}}
+
已加载插件数量: {{len .Plugins}}
+
+
+ 已安装插件
+
+ {{range .Plugins}}
+
+
+
+
{{.Description}}
+
+ {{if .Config}}
+
+
配置:
+ {{range $key, $value := .Config}}
+
+
{{$key}}:
+
{{$value}}
+
+ {{end}}
+
+ {{end}}
+
+
+ {{if .Enabled}}
+
+ {{else}}
+
+ {{end}}
+
+
+
+ {{end}}
+
+ {{if not .Plugins}}
+ 没有已安装的插件。
+ {{end}}
+
+ 操作
+
+
+
+`
+
+ // 解析模板
+ t, err := template.New("admin").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 准备数据
+ data := struct {
+ Plugins []plugin.PluginInfo
+ OS string
+ DynamicLoadingSupported bool
+ PluginsDir string
+ }{
+ Plugins: pm.GetPluginInfos(),
+ OS: strings.Title(strings.ToLower(fmt.Sprintf("%s", strings.Split(runtime.GOOS, "/")[0]))),
+ DynamicLoadingSupported: pm.IsDynamicLoadingSupported(),
+ PluginsDir: pluginsDir,
+ }
+
+ // 渲染模板
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// EnablePluginHandler 启用插件
+func EnablePluginHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/enable/"):]
+
+ if err := pm.EnablePlugin(pluginName); err != nil {
+ http.Error(w, fmt.Sprintf("启用插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 尝试初始化和启动插件
+ ctx := context.Background()
+ plugin, exists := pm.GetPlugin(pluginName)
+ if exists && plugin.IsEnabled() {
+ config, _ := pm.GetPluginConfig(pluginName)
+ if err := plugin.Init(ctx, config); err != nil {
+ http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ if err := plugin.Start(ctx); err != nil {
+ http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// DisablePluginHandler 禁用插件
+func DisablePluginHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/disable/"):]
+
+ // 先停止插件
+ ctx := context.Background()
+ plugin, exists := pm.GetPlugin(pluginName)
+ if exists && plugin.IsEnabled() {
+ if err := plugin.Stop(ctx); err != nil {
+ http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+ }
+
+ // 禁用插件
+ if err := pm.DisablePlugin(pluginName); err != nil {
+ http.Error(w, fmt.Sprintf("禁用插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// ConfigPluginHandler 配置插件
+func ConfigPluginHandler(w http.ResponseWriter, r *http.Request) {
+ pluginName := r.URL.Path[len("/config/"):]
+
+ if r.Method == http.MethodPost {
+ // 处理配置表单提交
+ if err := r.ParseForm(); err != nil {
+ http.Error(w, fmt.Sprintf("解析表单失败: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ // 收集配置项
+ config := make(map[string]interface{})
+ for key, values := range r.PostForm {
+ if key == "pluginName" {
+ continue // 跳过插件名称字段
+ }
+
+ if len(values) > 0 {
+ // 尝试将值转换为适当的类型
+ value := values[0]
+
+ // 布尔值处理
+ if value == "true" {
+ config[key] = true
+ } else if value == "false" {
+ config[key] = false
+ } else if strings.Contains(value, ".") {
+ // 尝试解析为浮点数
+ if f, err := strconv.ParseFloat(value, 64); err == nil {
+ config[key] = f
+ } else {
+ config[key] = value
+ }
+ } else if i, err := strconv.Atoi(value); err == nil {
+ // 尝试解析为整数
+ config[key] = i
+ } else {
+ // 默认为字符串
+ config[key] = value
+ }
+ }
+ }
+
+ // 保存配置
+ if err := pm.SetPluginConfig(pluginName, config); err != nil {
+ http.Error(w, fmt.Sprintf("保存配置失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 重定向回主页
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ // 获取插件信息和配置
+ info, config := getPluginInfo(pluginName)
+ if info == nil {
+ http.Error(w, "插件不存在", http.StatusNotFound)
+ return
+ }
+
+ // 配置表单模板
+ tmpl := `
+
+
+
+
+ 配置 {{.Info.Name}}
+
+
+
+ 配置 {{.Info.Name}}
+
+
+
+ 返回插件列表
+
+
+`
+
+ // 解析模板
+ t, err := template.New("config").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 准备数据
+ data := struct {
+ Info *plugin.PluginInfo
+ Config map[string]interface{}
+ }{
+ Info: info,
+ Config: config,
+ }
+
+ // 渲染模板
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// ReloadPluginsHandler 重新加载插件
+func ReloadPluginsHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ // 停止所有插件
+ ctx := context.Background()
+ if err := pm.StopPlugins(ctx); err != nil {
+ http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 重新加载插件
+ if err := pm.LoadPlugins(); err != nil {
+ http.Error(w, fmt.Sprintf("加载插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 初始化和启动插件
+ if err := pm.InitPlugins(ctx); err != nil {
+ http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ if err := pm.StartPlugins(ctx); err != nil {
+ http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// API接口处理器 - 返回插件列表
+func APIPluginsHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ plugins := pm.GetPluginInfos()
+ if err := json.NewEncoder(w).Encode(plugins); err != nil {
+ http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// getPluginInfo 获取插件信息和配置
+func getPluginInfo(name string) (*plugin.PluginInfo, map[string]interface{}) {
+ plugins := pm.GetPluginInfos()
+
+ for _, info := range plugins {
+ if info.Name == name {
+ config, _ := pm.GetPluginConfig(name)
+ return &info, config
+ }
+ }
+
+ return nil, nil
+}
+
+// 插件目录
+var pluginsDir = "./plugins"
+
+func main3() {
+ // 创建插件管理器
+ pm = plugin.NewPluginManager(pluginsDir)
+
+ // 加载插件
+ if err := pm.LoadPlugins(); err != nil {
+ log.Fatalf("加载插件失败: %v", err)
+ }
+
+ // 初始化并启动插件
+ ctx := context.Background()
+ if err := pm.InitPlugins(ctx); err != nil {
+ log.Printf("初始化插件失败: %v", err)
+ }
+
+ if err := pm.StartPlugins(ctx); err != nil {
+ log.Printf("启动插件失败: %v", err)
+ }
+
+ // 注册处理器
+ http.HandleFunc("/", AdminHandler)
+ http.HandleFunc("/enable/", EnablePluginHandler)
+ http.HandleFunc("/disable/", DisablePluginHandler)
+ http.HandleFunc("/config/", ConfigPluginHandler)
+ http.HandleFunc("/reload", ReloadPluginsHandler)
+ http.HandleFunc("/api/plugins", APIPluginsHandler)
+
+ // 启动Web服务器
+ log.Println("管理界面启动在 :8080...")
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
diff --git a/examples/plugin/interface.go b/examples/plugin/interface.go
new file mode 100644
index 0000000..1fa5079
--- /dev/null
+++ b/examples/plugin/interface.go
@@ -0,0 +1,91 @@
+package plugin
+
+// IPlugin 插件接口
+// 这个文件定义了所有插件必须实现的接口
+// 注意:这个文件应该与实际插件代码一起编译
+type IPlugin interface {
+ // Name 插件名称
+ Name() string
+ // Version 插件版本
+ Version() string
+ // Description 插件描述
+ Description() string
+ // Author 插件作者
+ Author() string
+ // Init 初始化插件
+ Init(config map[string]interface{}) error
+ // Start 启动插件
+ Start() error
+ // Stop 停止插件
+ Stop() error
+ // IsEnabled 插件是否启用
+ IsEnabled() bool
+ // SetEnabled 设置插件启用状态
+ SetEnabled(enabled bool)
+}
+
+// BasePlugin 提供插件接口的基本实现
+// 插件开发者可以嵌入此结构体,以减少需要实现的方法数量
+type BasePlugin struct {
+ name string
+ version string
+ description string
+ author string
+ enabled bool
+}
+
+// NewBasePlugin 创建一个基本插件
+func NewBasePlugin(name, version, description, author string) *BasePlugin {
+ return &BasePlugin{
+ name: name,
+ version: version,
+ description: description,
+ author: author,
+ enabled: true,
+ }
+}
+
+// Name 获取插件名称
+func (p *BasePlugin) Name() string {
+ return p.name
+}
+
+// Version 获取插件版本
+func (p *BasePlugin) Version() string {
+ return p.version
+}
+
+// Description 获取插件描述
+func (p *BasePlugin) Description() string {
+ return p.description
+}
+
+// Author 获取插件作者
+func (p *BasePlugin) Author() string {
+ return p.author
+}
+
+// IsEnabled 插件是否启用
+func (p *BasePlugin) IsEnabled() bool {
+ return p.enabled
+}
+
+// SetEnabled 设置插件启用状态
+func (p *BasePlugin) SetEnabled(enabled bool) {
+ p.enabled = enabled
+}
+
+// Init 初始化插件,子类需要重写此方法
+func (p *BasePlugin) Init(config map[string]interface{}) error {
+ return nil
+}
+
+// Start 启动插件,子类需要重写此方法
+func (p *BasePlugin) Start() error {
+ return nil
+}
+
+// Stop 停止插件,子类需要重写此方法
+func (p *BasePlugin) Stop() error {
+ return nil
+}
diff --git a/examples/plugin/manager/plugin_manager.go b/examples/plugin/manager/plugin_manager.go
new file mode 100644
index 0000000..82f983d
--- /dev/null
+++ b/examples/plugin/manager/plugin_manager.go
@@ -0,0 +1,169 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "runtime"
+
+ "github.com/darkit/goproxy/examples/plugin"
+)
+
+// 这是内置插件的示例实现
+// LogPlugin 日志插件
+type LogPlugin struct {
+ name string
+ version string
+ description string
+ author string
+ enabled bool
+ level string
+}
+
+// 创建内置日志插件
+func NewLogPlugin() *LogPlugin {
+ return &LogPlugin{
+ name: "InternalLogPlugin",
+ version: "1.0.0",
+ description: "内置日志插件,在不支持动态加载插件的平台上使用",
+ author: "开发者",
+ enabled: true,
+ level: "info",
+ }
+}
+
+// 实现Plugin接口
+func (p *LogPlugin) Name() string {
+ return p.name
+}
+
+func (p *LogPlugin) Version() string {
+ return p.version
+}
+
+func (p *LogPlugin) Description() string {
+ return p.description
+}
+
+func (p *LogPlugin) Author() string {
+ return p.author
+}
+
+func (p *LogPlugin) IsEnabled() bool {
+ return p.enabled
+}
+
+func (p *LogPlugin) SetEnabled(enabled bool) {
+ p.enabled = enabled
+}
+
+func (p *LogPlugin) Init(ctx context.Context, config map[string]interface{}) error {
+ if level, ok := config["level"].(string); ok {
+ p.level = level
+ }
+ fmt.Println("内置日志插件已初始化,日志级别:", p.level)
+ return nil
+}
+
+func (p *LogPlugin) Start(ctx context.Context) error {
+ fmt.Println("内置日志插件已启动")
+ return nil
+}
+
+func (p *LogPlugin) Stop(ctx context.Context) error {
+ fmt.Println("内置日志插件已停止")
+ return nil
+}
+
+// Log 记录日志
+func (p *LogPlugin) Log(message string) {
+ fmt.Printf("[%s] %s\n", p.level, message)
+}
+
+func main() {
+ // 创建插件管理器
+ pluginsDir := "./plugins"
+ pm := plugin.NewPluginManager(pluginsDir)
+
+ // 检查当前系统是否支持动态加载插件
+ if pm.IsDynamicLoadingSupported() {
+ fmt.Printf("当前系统(%s)支持动态加载插件\n", runtime.GOOS)
+ } else {
+ fmt.Printf("当前系统(%s)不支持动态加载插件\n", runtime.GOOS)
+
+ // 在不支持动态加载的系统上使用内置插件
+ logPlugin := NewLogPlugin()
+ pm.RegisterPlugin(logPlugin)
+ }
+
+ // 加载插件
+ fmt.Println("正在加载插件...")
+ if err := pm.LoadPlugins(); err != nil {
+ fmt.Printf("加载插件失败: %v\n", err)
+ }
+
+ // 配置插件
+ logPluginConfig := map[string]interface{}{
+ "level": "debug",
+ }
+ if err := pm.SetPluginConfig("InternalLogPlugin", logPluginConfig); err != nil {
+ fmt.Printf("配置插件失败: %v\n", err)
+ }
+
+ // 初始化插件
+ ctx := context.Background()
+ fmt.Println("正在初始化插件...")
+ if err := pm.InitPlugins(ctx); err != nil {
+ fmt.Printf("初始化插件失败: %v\n", err)
+ }
+
+ // 启动插件
+ fmt.Println("正在启动插件...")
+ if err := pm.StartPlugins(ctx); err != nil {
+ fmt.Printf("启动插件失败: %v\n", err)
+ }
+
+ // 获取并打印所有插件信息
+ fmt.Println("\n已加载的插件列表:")
+ plugins := pm.GetPluginInfos()
+ for i, info := range plugins {
+ status := "启用"
+ if !info.Enabled {
+ status = "禁用"
+ }
+ fmt.Printf("[%d] %s (v%s) - %s [%s]\n作者: %s\n",
+ i, info.Name, info.Version, info.Description, status, info.Author)
+
+ // 打印插件配置
+ if len(info.Config) > 0 {
+ fmt.Println("配置:")
+ for k, v := range info.Config {
+ fmt.Printf(" %s: %v\n", k, v)
+ }
+ }
+ fmt.Println()
+ }
+
+ // 使用第一个启用的插件(这里只是示例)
+ if len(plugins) > 0 {
+ for _, info := range plugins {
+ if info.Enabled {
+ fmt.Printf("正在使用插件: %s\n", info.Name)
+ if p, ok := pm.GetPlugin(info.Name); ok {
+ // 这里可以根据插件类型执行特定操作
+ if logPlugin, ok := p.(*LogPlugin); ok {
+ logPlugin.Log("这是一条测试日志消息")
+ }
+ }
+ break
+ }
+ }
+ }
+
+ // 停止插件
+ fmt.Println("\n正在停止插件...")
+ if err := pm.StopPlugins(ctx); err != nil {
+ fmt.Printf("停止插件失败: %v\n", err)
+ }
+
+ fmt.Println("示例结束")
+}
diff --git a/examples/plugin/plugin.go b/examples/plugin/plugin.go
new file mode 100644
index 0000000..6c366bf
--- /dev/null
+++ b/examples/plugin/plugin.go
@@ -0,0 +1,410 @@
+package plugin
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "plugin"
+ "runtime"
+ "sync"
+)
+
+// Plugin 插件接口
+type Plugin interface {
+ // Name 插件名称
+ Name() string
+ // Version 插件版本
+ Version() string
+ // Description 插件描述
+ Description() string
+ // Author 插件作者
+ Author() string
+ // Init 初始化插件
+ Init(ctx context.Context, config map[string]interface{}) error
+ // Start 启动插件
+ Start(ctx context.Context) error
+ // Stop 停止插件
+ Stop(ctx context.Context) error
+ // IsEnabled 插件是否启用
+ IsEnabled() bool
+ // SetEnabled 设置插件启用状态
+ SetEnabled(enabled bool)
+}
+
+// PluginInfo 插件信息
+type PluginInfo struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Description string `json:"description"`
+ Author string `json:"author"`
+ Enabled bool `json:"enabled"`
+ Config map[string]interface{} `json:"config,omitempty"`
+}
+
+// PluginManager 插件管理器
+type PluginManager struct {
+ pluginsDir string
+ plugins map[string]Plugin
+ configs map[string]map[string]interface{}
+ mu sync.RWMutex
+ dynamicLoadingSupported bool // 是否支持动态加载插件
+}
+
+// NewPluginManager 创建插件管理器
+func NewPluginManager(pluginsDir string) *PluginManager {
+ // 检查当前系统是否支持动态加载插件
+ dynamicLoadingSupported := runtime.GOOS == "linux" || runtime.GOOS == "darwin"
+
+ return &PluginManager{
+ pluginsDir: pluginsDir,
+ plugins: make(map[string]Plugin),
+ configs: make(map[string]map[string]interface{}),
+ dynamicLoadingSupported: dynamicLoadingSupported,
+ }
+}
+
+// IsDynamicLoadingSupported 检查是否支持动态加载插件
+func (pm *PluginManager) IsDynamicLoadingSupported() bool {
+ return pm.dynamicLoadingSupported
+}
+
+// LoadPlugins 加载插件
+func (pm *PluginManager) LoadPlugins() error {
+ // 确保插件目录存在
+ if err := os.MkdirAll(pm.pluginsDir, 0o755); err != nil {
+ return fmt.Errorf("创建插件目录失败: %v", err)
+ }
+
+ // 加载插件配置
+ if err := pm.loadPluginConfigs(); err != nil {
+ return fmt.Errorf("加载插件配置失败: %v", err)
+ }
+
+ // 如果不支持动态加载,则返回
+ if !pm.dynamicLoadingSupported {
+ fmt.Printf("警告: 当前系统(%s)不支持动态加载插件,跳过加载\n", runtime.GOOS)
+ return nil
+ }
+
+ // 遍历插件目录
+ err := filepath.Walk(pm.pluginsDir, func(path string, info fs.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // 跳过目录和非.so文件
+ if info.IsDir() || filepath.Ext(path) != ".so" {
+ return nil
+ }
+
+ // 加载插件
+ if err := pm.loadPlugin(path); err != nil {
+ fmt.Printf("加载插件 %s 失败: %v\n", path, err)
+ }
+
+ return nil
+ })
+
+ return err
+}
+
+// loadPluginConfigs 加载插件配置
+func (pm *PluginManager) loadPluginConfigs() error {
+ configPath := filepath.Join(pm.pluginsDir, "plugins.json")
+
+ // 如果配置文件不存在,创建一个空的
+ if _, err := os.Stat(configPath); os.IsNotExist(err) {
+ file, err := os.Create(configPath)
+ if err != nil {
+ return fmt.Errorf("创建插件配置文件失败: %v", err)
+ }
+ file.Write([]byte("{}"))
+ file.Close()
+ return nil
+ }
+
+ // 读取配置文件
+ data, err := os.ReadFile(configPath)
+ if err != nil {
+ return fmt.Errorf("读取插件配置文件失败: %v", err)
+ }
+
+ // 解析配置
+ var configs map[string]map[string]interface{}
+ if err := json.Unmarshal(data, &configs); err != nil {
+ return fmt.Errorf("解析插件配置文件失败: %v", err)
+ }
+
+ pm.configs = configs
+ return nil
+}
+
+// savePluginConfigs 保存插件配置
+func (pm *PluginManager) savePluginConfigs() error {
+ configPath := filepath.Join(pm.pluginsDir, "plugins.json")
+
+ // 创建配置数据
+ configs := make(map[string]map[string]interface{})
+ pm.mu.RLock()
+ for name, plugin := range pm.plugins {
+ config := pm.configs[name]
+ if config == nil {
+ config = make(map[string]interface{})
+ }
+ config["enabled"] = plugin.IsEnabled()
+ configs[name] = config
+ }
+ pm.mu.RUnlock()
+
+ // 序列化配置
+ data, err := json.MarshalIndent(configs, "", " ")
+ if err != nil {
+ return fmt.Errorf("序列化插件配置失败: %v", err)
+ }
+
+ // 写入文件
+ if err := os.WriteFile(configPath, data, 0o644); err != nil {
+ return fmt.Errorf("写入插件配置文件失败: %v", err)
+ }
+
+ return nil
+}
+
+// loadPlugin 加载单个插件
+func (pm *PluginManager) loadPlugin(path string) error {
+ // 打开插件文件
+ p, err := plugin.Open(path)
+ if err != nil {
+ return fmt.Errorf("打开插件失败: %v", err)
+ }
+
+ // 查找Plugin变量
+ symPlugin, err := p.Lookup("Plugin")
+ if err != nil {
+ return fmt.Errorf("查找Plugin变量失败: %v", err)
+ }
+
+ // 类型断言
+ plugin, ok := symPlugin.(Plugin)
+ if !ok {
+ return errors.New("插件类型错误")
+ }
+
+ // 检查插件名称是否已存在
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if _, exists := pm.plugins[plugin.Name()]; exists {
+ return fmt.Errorf("插件 %s 已存在", plugin.Name())
+ }
+
+ // 设置插件启用状态
+ if config, exists := pm.configs[plugin.Name()]; exists {
+ if enabled, ok := config["enabled"].(bool); ok {
+ plugin.SetEnabled(enabled)
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+
+ pm.plugins[plugin.Name()] = plugin
+ return nil
+}
+
+// RegisterPlugin 注册内置插件
+// 用于在不支持动态加载的平台上注册插件
+func (pm *PluginManager) RegisterPlugin(plugin Plugin) error {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if _, exists := pm.plugins[plugin.Name()]; exists {
+ return fmt.Errorf("插件 %s 已存在", plugin.Name())
+ }
+
+ // 设置插件启用状态
+ if config, exists := pm.configs[plugin.Name()]; exists {
+ if enabled, ok := config["enabled"].(bool); ok {
+ plugin.SetEnabled(enabled)
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+ } else {
+ plugin.SetEnabled(true) // 默认启用
+ }
+
+ pm.plugins[plugin.Name()] = plugin
+ return nil
+}
+
+// GetPlugin 获取插件
+func (pm *PluginManager) GetPlugin(name string) (Plugin, bool) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugin, exists := pm.plugins[name]
+ return plugin, exists
+}
+
+// GetAllPlugins 获取所有插件
+func (pm *PluginManager) GetAllPlugins() []Plugin {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ plugins := make([]Plugin, 0, len(pm.plugins))
+ for _, plugin := range pm.plugins {
+ plugins = append(plugins, plugin)
+ }
+ return plugins
+}
+
+// GetPluginInfos 获取所有插件信息
+func (pm *PluginManager) GetPluginInfos() []PluginInfo {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ infos := make([]PluginInfo, 0, len(pm.plugins))
+ for name, plugin := range pm.plugins {
+ info := PluginInfo{
+ Name: plugin.Name(),
+ Version: plugin.Version(),
+ Description: plugin.Description(),
+ Author: plugin.Author(),
+ Enabled: plugin.IsEnabled(),
+ Config: pm.configs[name],
+ }
+ infos = append(infos, info)
+ }
+ return infos
+}
+
+// EnablePlugin 启用插件
+func (pm *PluginManager) EnablePlugin(name string) error {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ plugin, exists := pm.plugins[name]
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ plugin.SetEnabled(true)
+
+ // 更新配置
+ if pm.configs[name] == nil {
+ pm.configs[name] = make(map[string]interface{})
+ }
+ pm.configs[name]["enabled"] = true
+
+ // 保存配置
+ return pm.savePluginConfigs()
+}
+
+// DisablePlugin 禁用插件
+func (pm *PluginManager) DisablePlugin(name string) error {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ plugin, exists := pm.plugins[name]
+ if !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ plugin.SetEnabled(false)
+
+ // 更新配置
+ if pm.configs[name] == nil {
+ pm.configs[name] = make(map[string]interface{})
+ }
+ pm.configs[name]["enabled"] = false
+
+ // 保存配置
+ return pm.savePluginConfigs()
+}
+
+// InitPlugins 初始化所有插件
+func (pm *PluginManager) InitPlugins(ctx context.Context) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ config := pm.configs[name]
+ if err := plugin.Init(ctx, config); err != nil {
+ return fmt.Errorf("初始化插件 %s 失败: %v", name, err)
+ }
+ }
+ return nil
+}
+
+// StartPlugins 启动所有插件
+func (pm *PluginManager) StartPlugins(ctx context.Context) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Start(ctx); err != nil {
+ return fmt.Errorf("启动插件 %s 失败: %v", name, err)
+ }
+ }
+ return nil
+}
+
+// StopPlugins 停止所有插件
+func (pm *PluginManager) StopPlugins(ctx context.Context) error {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ for name, plugin := range pm.plugins {
+ if !plugin.IsEnabled() {
+ continue
+ }
+
+ if err := plugin.Stop(ctx); err != nil {
+ return fmt.Errorf("停止插件 %s 失败: %v", name, err)
+ }
+ }
+ return nil
+}
+
+// SetPluginConfig 设置插件配置
+func (pm *PluginManager) SetPluginConfig(name string, config map[string]interface{}) error {
+ pm.mu.Lock()
+ defer pm.mu.Unlock()
+
+ if _, exists := pm.plugins[name]; !exists {
+ return fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ pm.configs[name] = config
+ return pm.savePluginConfigs()
+}
+
+// GetPluginConfig 获取插件配置
+func (pm *PluginManager) GetPluginConfig(name string) (map[string]interface{}, error) {
+ pm.mu.RLock()
+ defer pm.mu.RUnlock()
+
+ if _, exists := pm.plugins[name]; !exists {
+ return nil, fmt.Errorf("插件 %s 不存在", name)
+ }
+
+ config := pm.configs[name]
+ if config == nil {
+ config = make(map[string]interface{})
+ }
+
+ return config, nil
+}
diff --git a/examples/plugin/plugins/build.sh b/examples/plugin/plugins/build.sh
new file mode 100644
index 0000000..d58aa12
--- /dev/null
+++ b/examples/plugin/plugins/build.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# 编译插件
+go build -buildmode=plugin -o stats.so ./stats/stats.go
+
+# 检查编译结果
+if [ $? -eq 0 ]; then
+ echo "插件编译成功: stats.so"
+else
+ echo "插件编译失败"
+ exit 1
+fi
+
+
+go build -buildmode=plugin -o logger.so ./logger/logger.go
+# 检查编译结果
+if [ $? -eq 0 ]; then
+ echo "插件编译成功: logger.so"
+else
+ echo "插件编译失败"
+ exit 1
+fi
\ No newline at end of file
diff --git a/examples/plugin/plugins/logger/logger_plugin.go b/examples/plugin/plugins/logger/logger_plugin.go
new file mode 100644
index 0000000..99af421
--- /dev/null
+++ b/examples/plugin/plugins/logger/logger_plugin.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/darkit/goproxy/examples/plugin"
+)
+
+// LoggerPlugin 日志插件
+// 提供文件日志和控制台日志功能
+type LoggerPlugin struct {
+ *plugin.BasePlugin // 嵌入基本插件结构
+ logFile *os.File // 日志文件
+ logger *log.Logger // 日志记录器
+ config map[string]interface{} // 配置
+}
+
+// Plugin 导出的插件变量
+// 注意:变量名必须是Plugin,大小写敏感
+var Plugin = &LoggerPlugin{
+ BasePlugin: plugin.NewBasePlugin(
+ "LoggerPlugin",
+ "1.0.0",
+ "简单的日志记录插件",
+ "开发者",
+ ),
+}
+
+// Init 初始化插件
+func (p *LoggerPlugin) Init(config map[string]interface{}) error {
+ p.config = config
+
+ // 获取日志文件路径
+ logPath, ok := config["log_path"].(string)
+ if !ok {
+ // 使用默认路径
+ logPath = "logs"
+ }
+
+ // 确保日志目录存在
+ if err := os.MkdirAll(logPath, 0o755); err != nil {
+ return fmt.Errorf("创建日志目录失败: %v", err)
+ }
+
+ // 创建日志文件
+ logFilePath := filepath.Join(logPath, fmt.Sprintf("app_%s.log", time.Now().Format("2006-01-02")))
+ logFile, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
+ if err != nil {
+ return fmt.Errorf("打开日志文件失败: %v", err)
+ }
+
+ p.logFile = logFile
+ p.logger = log.New(logFile, "[LoggerPlugin] ", log.LstdFlags)
+
+ p.logger.Println("日志插件初始化完成")
+ fmt.Println("日志插件初始化完成,日志文件:", logFilePath)
+
+ return nil
+}
+
+// Start 启动插件
+func (p *LoggerPlugin) Start() error {
+ if p.logger == nil {
+ return fmt.Errorf("插件未初始化")
+ }
+
+ p.logger.Println("日志插件已启动")
+ fmt.Println("日志插件已启动")
+ return nil
+}
+
+// Stop 停止插件
+func (p *LoggerPlugin) Stop() error {
+ if p.logger != nil {
+ p.logger.Println("日志插件正在停止")
+ }
+
+ if p.logFile != nil {
+ if err := p.logFile.Close(); err != nil {
+ return fmt.Errorf("关闭日志文件失败: %v", err)
+ }
+ }
+
+ fmt.Println("日志插件已停止")
+ return nil
+}
+
+// Log 记录日志
+func (p *LoggerPlugin) Log(level, message string) {
+ if p.logger == nil {
+ fmt.Printf("[%s] %s\n", level, message)
+ return
+ }
+
+ logMsg := fmt.Sprintf("[%s] %s", level, message)
+ p.logger.Println(logMsg)
+
+ // 如果配置了同时输出到控制台
+ if consoleOutput, ok := p.config["console_output"].(bool); ok && consoleOutput {
+ fmt.Println(logMsg)
+ }
+}
+
+// Info 记录信息日志
+func (p *LoggerPlugin) Info(message string) {
+ p.Log("INFO", message)
+}
+
+// Warn 记录警告日志
+func (p *LoggerPlugin) Warn(message string) {
+ p.Log("WARN", message)
+}
+
+// Error 记录错误日志
+func (p *LoggerPlugin) Error(message string) {
+ p.Log("ERROR", message)
+}
+
+// main 函数是必须的,但不会被调用
+func main() {
+ // 不会被执行,仅用于编译插件
+}
diff --git a/examples/plugin/plugins/stats/stats_plugin.go b/examples/plugin/plugins/stats/stats_plugin.go
new file mode 100644
index 0000000..3508eed
--- /dev/null
+++ b/examples/plugin/plugins/stats/stats_plugin.go
@@ -0,0 +1,170 @@
+package main
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/darkit/goproxy/examples/plugin"
+)
+
+// StatsPlugin 统计插件
+// 用于收集和记录系统运行时统计数据
+type StatsPlugin struct {
+ *plugin.BasePlugin
+ stats map[string]int64
+ startTime time.Time
+ mu sync.RWMutex
+ tickerStop chan bool
+ ticker *time.Ticker
+ config map[string]interface{}
+}
+
+// Plugin 导出的插件变量
+var Plugin = &StatsPlugin{
+ BasePlugin: plugin.NewBasePlugin(
+ "StatsPlugin",
+ "1.0.0",
+ "系统运行时统计插件",
+ "开发者",
+ ),
+ stats: make(map[string]int64),
+ tickerStop: make(chan bool),
+}
+
+// Init 初始化插件
+func (p *StatsPlugin) Init(config map[string]interface{}) error {
+ p.config = config
+
+ // 初始化统计指标
+ p.mu.Lock()
+ p.stats["requests"] = 0
+ p.stats["errors"] = 0
+ p.stats["bytes_sent"] = 0
+ p.stats["bytes_received"] = 0
+ p.mu.Unlock()
+
+ fmt.Println("统计插件初始化完成")
+ return nil
+}
+
+// Start 启动插件
+func (p *StatsPlugin) Start() error {
+ p.startTime = time.Now()
+
+ // 启动定时统计任务
+ interval := 60 * time.Second // 默认60秒
+
+ // 从配置中获取统计间隔
+ if intervalSec, ok := p.config["interval_seconds"].(float64); ok {
+ interval = time.Duration(intervalSec) * time.Second
+ }
+
+ p.ticker = time.NewTicker(interval)
+
+ go func() {
+ for {
+ select {
+ case <-p.ticker.C:
+ p.logStats()
+ case <-p.tickerStop:
+ p.ticker.Stop()
+ return
+ }
+ }
+ }()
+
+ fmt.Println("统计插件已启动")
+ return nil
+}
+
+// Stop 停止插件
+func (p *StatsPlugin) Stop() error {
+ if p.ticker != nil {
+ p.tickerStop <- true
+ }
+
+ // 输出最终统计信息
+ p.logStats()
+
+ fmt.Println("统计插件已停止")
+ return nil
+}
+
+// logStats 记录当前统计信息
+func (p *StatsPlugin) logStats() {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ uptime := time.Since(p.startTime).Seconds()
+
+ fmt.Printf("===== 系统统计信息 =====\n")
+ fmt.Printf("运行时间: %.2f 秒\n", uptime)
+ fmt.Printf("总请求数: %d\n", p.stats["requests"])
+ fmt.Printf("错误数: %d\n", p.stats["errors"])
+ fmt.Printf("发送字节: %d\n", p.stats["bytes_sent"])
+ fmt.Printf("接收字节: %d\n", p.stats["bytes_received"])
+
+ if uptime > 0 && p.stats["requests"] > 0 {
+ fmt.Printf("平均请求/秒: %.2f\n", float64(p.stats["requests"])/uptime)
+ fmt.Printf("错误率: %.2f%%\n", float64(p.stats["errors"])*100/float64(p.stats["requests"]))
+ }
+
+ fmt.Printf("=======================\n")
+}
+
+// IncrementStat 增加统计值
+func (p *StatsPlugin) IncrementStat(name string, value int64) {
+ p.mu.Lock()
+ defer p.mu.Unlock()
+
+ if _, exists := p.stats[name]; exists {
+ p.stats[name] += value
+ } else {
+ p.stats[name] = value
+ }
+}
+
+// GetStat 获取统计值
+func (p *StatsPlugin) GetStat(name string) int64 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ if value, exists := p.stats[name]; exists {
+ return value
+ }
+ return 0
+}
+
+// RecordRequest 记录请求
+func (p *StatsPlugin) RecordRequest(bytesReceived, bytesSent int64, isError bool) {
+ p.IncrementStat("requests", 1)
+ p.IncrementStat("bytes_received", bytesReceived)
+ p.IncrementStat("bytes_sent", bytesSent)
+
+ if isError {
+ p.IncrementStat("errors", 1)
+ }
+}
+
+// GetAllStats 获取所有统计数据
+func (p *StatsPlugin) GetAllStats() map[string]int64 {
+ p.mu.RLock()
+ defer p.mu.RUnlock()
+
+ // 创建一个副本
+ statsCopy := make(map[string]int64, len(p.stats))
+ for k, v := range p.stats {
+ statsCopy[k] = v
+ }
+
+ // 添加运行时间
+ statsCopy["uptime_seconds"] = int64(time.Since(p.startTime).Seconds())
+
+ return statsCopy
+}
+
+// main 函数是必须的,但不会被调用
+func main() {
+ // 不会被执行,仅用于编译插件
+}
diff --git a/examples/rewriter/http_server.go b/examples/rewriter/http_server.go
new file mode 100644
index 0000000..e8f1e96
--- /dev/null
+++ b/examples/rewriter/http_server.go
@@ -0,0 +1,121 @@
+package main
+
+import (
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+
+ "github.com/darkit/goproxy/examples/rewriter/rewriter"
+)
+
+// RewriteReverseProxy 重写反向代理
+// 在请求发送到后端服务器前重写URL
+type RewriteReverseProxy struct {
+ // 后端服务器地址
+ Target *url.URL
+ // URL重写器
+ Rewriter *rewriter.Rewriter
+ // 反向代理
+ Proxy *httputil.ReverseProxy
+}
+
+// NewRewriteReverseProxy 创建重写反向代理
+func NewRewriteReverseProxy(target string, rewriteRulesFile string) (*RewriteReverseProxy, error) {
+ // 解析目标URL
+ targetURL, err := url.Parse(target)
+ if err != nil {
+ return nil, err
+ }
+
+ // 创建重写器
+ rw := rewriter.NewRewriter()
+
+ // 加载重写规则
+ if rewriteRulesFile != "" {
+ if err := rw.LoadRulesFromFile(rewriteRulesFile); err != nil {
+ return nil, err
+ }
+ }
+
+ // 创建反向代理
+ proxy := httputil.NewSingleHostReverseProxy(targetURL)
+
+ // 修改默认的Director函数,添加URL重写逻辑
+ defaultDirector := proxy.Director
+ proxy.Director = func(req *http.Request) {
+ // 先执行默认的Director函数
+ defaultDirector(req)
+
+ // 然后执行URL重写
+ rw.Rewrite(req)
+
+ // 记录重写后的URL
+ log.Printf("请求重写: %s -> %s", req.URL.Path, req.URL.String())
+ }
+
+ // 修改响应处理器,重写响应头
+ proxy.ModifyResponse = func(resp *http.Response) error {
+ // 重写响应
+ rw.RewriteResponse(resp, targetURL.Host)
+ return nil
+ }
+
+ return &RewriteReverseProxy{
+ Target: targetURL,
+ Rewriter: rw,
+ Proxy: proxy,
+ }, nil
+}
+
+// ServeHTTP 实现http.Handler接口
+func (rrp *RewriteReverseProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ log.Printf("收到请求: %s %s", r.Method, r.URL.Path)
+ rrp.Proxy.ServeHTTP(w, r)
+}
+
+// 中间件:将重写器应用到处理链中
+func RewriteMiddleware(rw *rewriter.Rewriter, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // 重写URL
+ originalPath := r.URL.Path
+ rw.Rewrite(r)
+
+ if originalPath != r.URL.Path {
+ log.Printf("URL重写: %s -> %s", originalPath, r.URL.Path)
+ }
+
+ // 继续处理链
+ next.ServeHTTP(w, r)
+ })
+}
+
+func main1() {
+ // 创建重写器
+ rw := rewriter.NewRewriter()
+
+ // 加载重写规则
+ if err := rw.LoadRulesFromFile("rules.json"); err != nil {
+ log.Fatalf("加载重写规则失败: %v", err)
+ }
+
+ // 创建反向代理
+ proxy, err := NewRewriteReverseProxy("http://localhost:8081", "rules.json")
+ if err != nil {
+ log.Fatalf("创建反向代理失败: %v", err)
+ }
+
+ // 创建静态文件服务器(模拟普通Web服务器)
+ fileServer := http.FileServer(http.Dir("./static"))
+
+ // 使用中间件包装文件服务器
+ rewrittenFileServer := RewriteMiddleware(rw, fileServer)
+
+ // 设置处理函数
+ http.Handle("/proxy/", http.StripPrefix("/proxy", proxy))
+ http.Handle("/", rewrittenFileServer)
+
+ // 启动服务器
+ log.Println("服务器启动在 :8080...")
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
diff --git a/examples/rewriter/main.go b/examples/rewriter/main.go
new file mode 100644
index 0000000..b32f109
--- /dev/null
+++ b/examples/rewriter/main.go
@@ -0,0 +1,88 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+
+ "github.com/darkit/goproxy/examples/rewriter/rewriter"
+)
+
+func main2() {
+ // 创建URL重写器
+ rw := rewriter.NewRewriter()
+
+ // 添加一些规则
+ rw.AddRule("/api/v1/", "/api/v2/", false)
+ rw.AddRuleWithDescription("/old/(.*)/page", "/new/$1/page", true, "旧页面重定向")
+
+ // 从文件加载规则
+ exampleDir, _ := os.Getwd()
+ jsonRulesPath := filepath.Join(exampleDir, "rules.json")
+ textRulesPath := filepath.Join(exampleDir, "rules.txt")
+
+ // 先加载JSON格式规则
+ fmt.Println("从JSON文件加载规则...")
+ if err := rw.LoadRulesFromFile(jsonRulesPath); err != nil {
+ fmt.Printf("从JSON文件加载规则失败: %v\n", err)
+ }
+
+ // 然后加载文本格式规则
+ fmt.Println("从文本文件加载规则...")
+ if err := rw.LoadRulesFromFile(textRulesPath); err != nil {
+ fmt.Printf("从文本文件加载规则失败: %v\n", err)
+ }
+
+ // 打印所有规则
+ fmt.Println("\n当前规则列表:")
+ for i, rule := range rw.GetRules() {
+ status := "启用"
+ if !rule.Enabled {
+ status = "禁用"
+ }
+ fmt.Printf("[%d] %s -> %s [%s] (%s)\n",
+ i, rule.Pattern, rule.Replacement,
+ map[bool]string{true: "正则", false: "前缀"}[rule.UseRegex],
+ status)
+ }
+
+ // 测试重写
+ fmt.Println("\n测试URL重写:")
+ testURLs := []string{
+ "/api/v1/users",
+ "/old/profile/page",
+ "/legacy-files/document.pdf",
+ "/en/about/company",
+ }
+
+ for _, url := range testURLs {
+ // 创建测试请求
+ req := httptest.NewRequest("GET", "http://example.com"+url, nil)
+
+ // 重写URL
+ fmt.Printf("原始URL: %s\n", req.URL.Path)
+ rw.Rewrite(req)
+ fmt.Printf("重写后: %s\n\n", req.URL.Path)
+ }
+
+ // 测试响应重写
+ fmt.Println("测试响应重写:")
+ resp := &http.Response{
+ Header: http.Header{},
+ }
+ resp.Header.Set("Location", "http://backend.example.com/old/profile/page")
+ fmt.Printf("原始Location: %s\n", resp.Header.Get("Location"))
+ rw.RewriteResponse(resp, "backend.example.com")
+ fmt.Printf("重写后Location: %s\n", resp.Header.Get("Location"))
+
+ // 保存规则到新文件
+ newRulesPath := filepath.Join(exampleDir, "new_rules.json")
+ fmt.Printf("\n保存规则到文件: %s\n", newRulesPath)
+ if err := rw.SaveRulesToFile(newRulesPath); err != nil {
+ fmt.Printf("保存规则失败: %v\n", err)
+ } else {
+ fmt.Println("规则已保存")
+ }
+}
diff --git a/examples/rewriter/rewriter/rewriter.go b/examples/rewriter/rewriter/rewriter.go
new file mode 100644
index 0000000..779d3e3
--- /dev/null
+++ b/examples/rewriter/rewriter/rewriter.go
@@ -0,0 +1,285 @@
+package rewriter
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "regexp"
+ "strings"
+)
+
+// Rewriter URL重写器
+// 用于在反向代理中重写请求URL
+type Rewriter struct {
+ // 重写规则列表
+ rules []*RewriteRule
+}
+
+// RewriteRule 重写规则
+type RewriteRule struct {
+ // 匹配模式
+ Pattern string `json:"pattern"`
+ // 替换模式
+ Replacement string `json:"replacement"`
+ // 是否使用正则表达式
+ UseRegex bool `json:"use_regex"`
+ // 编译后的正则表达式
+ regex *regexp.Regexp `json:"-"`
+ // 规则描述
+ Description string `json:"description,omitempty"`
+ // 规则启用状态
+ Enabled bool `json:"enabled,omitempty"`
+}
+
+// NewRewriter 创建URL重写器
+func NewRewriter() *Rewriter {
+ return &Rewriter{
+ rules: make([]*RewriteRule, 0),
+ }
+}
+
+// AddRule 添加重写规则
+func (r *Rewriter) AddRule(pattern, replacement string, useRegex bool) error {
+ rule := &RewriteRule{
+ Pattern: pattern,
+ Replacement: replacement,
+ UseRegex: useRegex,
+ Enabled: true,
+ }
+
+ if useRegex {
+ regex, err := regexp.Compile(pattern)
+ if err != nil {
+ return err
+ }
+ rule.regex = regex
+ }
+
+ r.rules = append(r.rules, rule)
+ return nil
+}
+
+// AddRuleWithDescription 添加带描述的重写规则
+func (r *Rewriter) AddRuleWithDescription(pattern, replacement string, useRegex bool, description string) error {
+ rule := &RewriteRule{
+ Pattern: pattern,
+ Replacement: replacement,
+ UseRegex: useRegex,
+ Description: description,
+ Enabled: true,
+ }
+
+ if useRegex {
+ regex, err := regexp.Compile(pattern)
+ if err != nil {
+ return err
+ }
+ rule.regex = regex
+ }
+
+ r.rules = append(r.rules, rule)
+ return nil
+}
+
+// Rewrite 重写URL
+func (r *Rewriter) Rewrite(req *http.Request) {
+ path := req.URL.Path
+
+ for _, rule := range r.rules {
+ if !rule.Enabled {
+ continue
+ }
+
+ if rule.UseRegex {
+ if rule.regex.MatchString(path) {
+ req.URL.Path = rule.regex.ReplaceAllString(path, rule.Replacement)
+ break
+ }
+ } else {
+ if strings.HasPrefix(path, rule.Pattern) {
+ req.URL.Path = strings.Replace(path, rule.Pattern, rule.Replacement, 1)
+ break
+ }
+ }
+ }
+}
+
+// RewriteResponse 重写响应
+// 主要用于处理响应中的Location头和内容中的URL
+func (r *Rewriter) RewriteResponse(resp *http.Response, originHost string) {
+ // 处理重定向头
+ location := resp.Header.Get("Location")
+ if location != "" {
+ // 将后端服务器的域名替换成代理服务器的域名
+ for _, rule := range r.rules {
+ if !rule.Enabled {
+ continue
+ }
+
+ if rule.UseRegex && rule.regex != nil {
+ if rule.regex.MatchString(location) {
+ newLocation := rule.regex.ReplaceAllString(location, rule.Replacement)
+ resp.Header.Set("Location", newLocation)
+ break
+ }
+ }
+ }
+ }
+}
+
+// LoadRulesFromFile 从文件加载重写规则
+func (r *Rewriter) LoadRulesFromFile(filename string) error {
+ file, err := os.Open(filename)
+ if err != nil {
+ return fmt.Errorf("打开文件失败: %v", err)
+ }
+ defer file.Close()
+
+ // 检查文件扩展名,决定使用何种方式解析
+ if strings.HasSuffix(filename, ".json") {
+ return r.loadRulesFromJSON(file)
+ } else {
+ return r.loadRulesFromText(file)
+ }
+}
+
+// loadRulesFromJSON 从JSON文件加载规则
+func (r *Rewriter) loadRulesFromJSON(file *os.File) error {
+ var rules []*RewriteRule
+ decoder := json.NewDecoder(file)
+ if err := decoder.Decode(&rules); err != nil {
+ return fmt.Errorf("解析JSON失败: %v", err)
+ }
+
+ // 编译正则表达式
+ for _, rule := range rules {
+ if rule.UseRegex {
+ regex, err := regexp.Compile(rule.Pattern)
+ if err != nil {
+ return fmt.Errorf("编译正则表达式'%s'失败: %v", rule.Pattern, err)
+ }
+ rule.regex = regex
+ }
+ r.rules = append(r.rules, rule)
+ }
+
+ return nil
+}
+
+// loadRulesFromText 从文本文件加载规则
+// 格式: pattern replacement [regex] [#description]
+func (r *Rewriter) loadRulesFromText(file *os.File) error {
+ scanner := bufio.NewScanner(file)
+ lineNum := 0
+
+ for scanner.Scan() {
+ lineNum++
+ line := strings.TrimSpace(scanner.Text())
+
+ // 跳过空行和注释
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ parts := strings.Fields(line)
+ if len(parts) < 2 {
+ return fmt.Errorf("第%d行格式错误: %s", lineNum, line)
+ }
+
+ pattern := parts[0]
+ replacement := parts[1]
+ useRegex := false
+ description := ""
+
+ // 检查是否有额外选项
+ for i := 2; i < len(parts); i++ {
+ if parts[i] == "regex" {
+ useRegex = true
+ } else if strings.HasPrefix(parts[i], "#") {
+ // 获取描述信息
+ description = strings.Join(parts[i:], " ")
+ description = strings.TrimPrefix(description, "#")
+ description = strings.TrimSpace(description)
+ break
+ }
+ }
+
+ if err := r.AddRuleWithDescription(pattern, replacement, useRegex, description); err != nil {
+ return fmt.Errorf("第%d行添加规则失败: %v", lineNum, err)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("读取文件失败: %v", err)
+ }
+
+ return nil
+}
+
+// GetRules 获取所有规则
+func (r *Rewriter) GetRules() []*RewriteRule {
+ return r.rules
+}
+
+// EnableRule 启用规则
+func (r *Rewriter) EnableRule(index int) error {
+ if index < 0 || index >= len(r.rules) {
+ return fmt.Errorf("规则索引越界: %d", index)
+ }
+ r.rules[index].Enabled = true
+ return nil
+}
+
+// DisableRule 禁用规则
+func (r *Rewriter) DisableRule(index int) error {
+ if index < 0 || index >= len(r.rules) {
+ return fmt.Errorf("规则索引越界: %d", index)
+ }
+ r.rules[index].Enabled = false
+ return nil
+}
+
+// RemoveRule 删除规则
+func (r *Rewriter) RemoveRule(index int) error {
+ if index < 0 || index >= len(r.rules) {
+ return fmt.Errorf("规则索引越界: %d", index)
+ }
+ r.rules = append(r.rules[:index], r.rules[index+1:]...)
+ return nil
+}
+
+// SaveRulesToFile 将规则保存到文件
+func (r *Rewriter) SaveRulesToFile(filename string) error {
+ file, err := os.Create(filename)
+ if err != nil {
+ return fmt.Errorf("创建文件失败: %v", err)
+ }
+ defer file.Close()
+
+ // 根据文件扩展名决定保存格式
+ if strings.HasSuffix(filename, ".json") {
+ encoder := json.NewEncoder(file)
+ encoder.SetIndent("", " ")
+ return encoder.Encode(r.rules)
+ } else {
+ writer := bufio.NewWriter(file)
+ for _, rule := range r.rules {
+ line := rule.Pattern + " " + rule.Replacement
+ if rule.UseRegex {
+ line += " regex"
+ }
+ if rule.Description != "" {
+ line += " # " + rule.Description
+ }
+ if !rule.Enabled {
+ line = "# " + line + " (disabled)"
+ }
+ if _, err := writer.WriteString(line + "\n"); err != nil {
+ return fmt.Errorf("写入文件失败: %v", err)
+ }
+ }
+ return writer.Flush()
+ }
+}
diff --git a/examples/rewriter/rules.json b/examples/rewriter/rules.json
new file mode 100644
index 0000000..95a3997
--- /dev/null
+++ b/examples/rewriter/rules.json
@@ -0,0 +1,30 @@
+[
+ {
+ "pattern": "/api/v1/",
+ "replacement": "/api/v2/",
+ "use_regex": false,
+ "description": "将API v1请求重定向到v2",
+ "enabled": true
+ },
+ {
+ "pattern": "/old/(.*)/page",
+ "replacement": "/new/$1/page",
+ "use_regex": true,
+ "description": "旧页面格式重定向到新格式",
+ "enabled": true
+ },
+ {
+ "pattern": "/legacy-files/",
+ "replacement": "/files/",
+ "use_regex": false,
+ "description": "旧文件路径重定向",
+ "enabled": false
+ },
+ {
+ "pattern": "^/(en|zh|ja)/(.*)",
+ "replacement": "/$2?lang=$1",
+ "use_regex": true,
+ "description": "将语言路径转换为查询参数",
+ "enabled": true
+ }
+]
\ No newline at end of file
diff --git a/examples/rewriter/rules.txt b/examples/rewriter/rules.txt
new file mode 100644
index 0000000..dc11818
--- /dev/null
+++ b/examples/rewriter/rules.txt
@@ -0,0 +1,7 @@
+# URL重写规则示例
+# 格式: pattern replacement [regex] [#description]
+
+/api/v1/ /api/v2/ # 将API v1请求重定向到v2
+/old/(.*)/page /new/$1/page regex # 旧页面格式重定向到新格式
+# /legacy-files/ /files/ # 旧文件路径重定向 (已禁用)
+^/(en|zh|ja)/(.*) /$2?lang=$1 regex # 将语言路径转换为查询参数
\ No newline at end of file
diff --git a/examples/rewriter/web_admin.go b/examples/rewriter/web_admin.go
new file mode 100644
index 0000000..0cb3218
--- /dev/null
+++ b/examples/rewriter/web_admin.go
@@ -0,0 +1,259 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "strconv"
+
+ "github.com/darkit/goproxy/examples/rewriter/rewriter"
+)
+
+// 全局重写器实例
+var rw *rewriter.Rewriter
+
+// 规则配置文件路径
+var rulesFile = "rules.json"
+
+// AdminHandler Web管理页面处理器
+func AdminHandler(w http.ResponseWriter, r *http.Request) {
+ // 定义模板
+ tmpl := `
+
+
+
+
+ URL重写规则管理
+
+
+
+ URL重写规则管理
+
+ 当前规则
+
+
+ 索引 |
+ 匹配模式 |
+ 替换模式 |
+ 类型 |
+ 描述 |
+ 状态 |
+ 操作 |
+
+ {{range $i, $rule := .Rules}}
+
+ {{$i}} |
+ {{$rule.Pattern}} |
+ {{$rule.Replacement}} |
+ {{if $rule.UseRegex}}正则表达式{{else}}前缀匹配{{end}} |
+ {{$rule.Description}} |
+
+ {{if $rule.Enabled}}启用{{else}}禁用{{end}}
+ |
+
+ {{if $rule.Enabled}}
+
+ {{else}}
+
+ {{end}}
+
+ |
+
+ {{end}}
+
+
+ 添加新规则
+
+
+ 保存配置
+
+
+
+`
+
+ // 解析模板
+ t, err := template.New("admin").Parse(tmpl)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 渲染模板
+ data := struct {
+ Rules []*rewriter.RewriteRule
+ }{
+ Rules: rw.GetRules(),
+ }
+
+ if err := t.Execute(w, data); err != nil {
+ http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
+ }
+}
+
+// AddRuleHandler 添加规则处理器
+func AddRuleHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ r.ParseForm()
+
+ pattern := r.Form.Get("pattern")
+ replacement := r.Form.Get("replacement")
+ useRegex := r.Form.Get("regex") == "on"
+ description := r.Form.Get("description")
+
+ if pattern == "" || replacement == "" {
+ http.Error(w, "匹配模式和替换模式不能为空", http.StatusBadRequest)
+ return
+ }
+
+ if err := rw.AddRuleWithDescription(pattern, replacement, useRegex, description); err != nil {
+ http.Error(w, fmt.Sprintf("添加规则失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// EnableRuleHandler 启用规则处理器
+func EnableRuleHandler(w http.ResponseWriter, r *http.Request) {
+ // 解析URL中的规则索引
+ indexStr := r.URL.Path[len("/enable/"):]
+ index, err := strconv.Atoi(indexStr)
+ if err != nil {
+ http.Error(w, "无效的规则索引", http.StatusBadRequest)
+ return
+ }
+
+ if err := rw.EnableRule(index); err != nil {
+ http.Error(w, fmt.Sprintf("启用规则失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// DisableRuleHandler 禁用规则处理器
+func DisableRuleHandler(w http.ResponseWriter, r *http.Request) {
+ // 解析URL中的规则索引
+ indexStr := r.URL.Path[len("/disable/"):]
+ index, err := strconv.Atoi(indexStr)
+ if err != nil {
+ http.Error(w, "无效的规则索引", http.StatusBadRequest)
+ return
+ }
+
+ if err := rw.DisableRule(index); err != nil {
+ http.Error(w, fmt.Sprintf("禁用规则失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// RemoveRuleHandler 删除规则处理器
+func RemoveRuleHandler(w http.ResponseWriter, r *http.Request) {
+ // 解析URL中的规则索引
+ indexStr := r.URL.Path[len("/remove/"):]
+ index, err := strconv.Atoi(indexStr)
+ if err != nil {
+ http.Error(w, "无效的规则索引", http.StatusBadRequest)
+ return
+ }
+
+ if err := rw.RemoveRule(index); err != nil {
+ http.Error(w, fmt.Sprintf("删除规则失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// SaveRulesHandler 保存规则处理器
+func SaveRulesHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+ return
+ }
+
+ if err := rw.SaveRulesToFile(rulesFile); err != nil {
+ http.Error(w, fmt.Sprintf("保存规则失败: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ http.Redirect(w, r, "/", http.StatusSeeOther)
+}
+
+// APIHandler API处理器,返回JSON格式的规则列表
+func APIHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ rules := rw.GetRules()
+ json.NewEncoder(w).Encode(rules)
+}
+
+func main() {
+ // 创建重写器
+ rw = rewriter.NewRewriter()
+
+ // 尝试从文件加载规则
+ if err := rw.LoadRulesFromFile(rulesFile); err != nil {
+ log.Printf("加载规则失败: %v, 将使用空规则集", err)
+ }
+
+ // 注册处理器
+ http.HandleFunc("/", AdminHandler)
+ http.HandleFunc("/add", AddRuleHandler)
+ http.HandleFunc("/enable/", EnableRuleHandler)
+ http.HandleFunc("/disable/", DisableRuleHandler)
+ http.HandleFunc("/remove/", RemoveRuleHandler)
+ http.HandleFunc("/save", SaveRulesHandler)
+ http.HandleFunc("/api/rules", APIHandler)
+
+ // 启动服务器
+ log.Println("管理界面启动在 :8080...")
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}