Skip to content

Commit ed4f7ca

Browse files
author
mirkobrombin
committed
feat: switch plugin system to hook-based interface
Introduce OnInit, OnInitForSite, BeforeRequest, HandleRequest, AfterRequest, and OnExit hooks for a cleaner lifecycle. Update all plugins to follow the new interface.
1 parent de0c9be commit ed4f7ca

File tree

8 files changed

+492
-330
lines changed

8 files changed

+492
-330
lines changed

internal/plugin/plugin.go

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package plugin
22

33
import (
4+
"net/http"
45
"sync"
56

67
"github.com/mirkobrombin/goup/internal/config"
@@ -10,15 +11,26 @@ import (
1011

1112
// Plugin defines the interface for GoUP plugins.
1213
type Plugin interface {
14+
// Name returns the plugin's name.
1315
Name() string
14-
Init(mwManager *middleware.MiddlewareManager) error
15-
InitForSite(mwManager *middleware.MiddlewareManager, logger *log.Logger, conf config.SiteConfig) error
16+
// OnInit is called once during the global plugin initialization.
17+
OnInit() error
18+
// OnInitForSite is called for each site configuration.
19+
OnInitForSite(conf config.SiteConfig, logger *log.Logger) error
20+
// BeforeRequest is invoked before serving each request.
21+
BeforeRequest(r *http.Request)
22+
// HandleRequest can fully handle the request, returning true if it does so.
23+
HandleRequest(w http.ResponseWriter, r *http.Request) bool
24+
// AfterRequest is invoked after the request has been served or handled.
25+
AfterRequest(w http.ResponseWriter, r *http.Request)
26+
// OnExit is called when the server is shutting down.
27+
OnExit() error
1628
}
1729

1830
// PluginManager manages loading and initialization of plugins.
1931
type PluginManager struct {
20-
plugins []Plugin
2132
mu sync.Mutex
33+
plugins []Plugin
2234
}
2335

2436
// DefaultPluginManager is the default instance used by the application.
@@ -46,37 +58,32 @@ func GetPluginManagerInstance() *PluginManager {
4658
}
4759

4860
// Register registers a new plugin.
49-
func (pm *PluginManager) Register(plugin Plugin) {
61+
func (pm *PluginManager) Register(p Plugin) {
5062
pm.mu.Lock()
5163
defer pm.mu.Unlock()
52-
pm.plugins = append(pm.plugins, plugin)
64+
pm.plugins = append(pm.plugins, p)
5365
}
5466

55-
// InitPlugins initializes all registered plugins.
56-
func (pm *PluginManager) InitPlugins(mwManager *middleware.MiddlewareManager) error {
67+
// InitPlugins calls OnInit on all registered plugins.
68+
func (pm *PluginManager) InitPlugins() error {
5769
pm.mu.Lock()
5870
defer pm.mu.Unlock()
5971

6072
for _, plugin := range pm.plugins {
61-
if err := plugin.Init(mwManager); err != nil {
73+
if err := plugin.OnInit(); err != nil {
6274
return err
6375
}
6476
}
6577
return nil
6678
}
6779

68-
// InitPluginsForSite initializes all registered plugins for a specific site.
69-
func (pm *PluginManager) InitPluginsForSite(mwManager *middleware.MiddlewareManager, baseLogger *log.Logger, conf config.SiteConfig) error {
80+
// InitPluginsForSite calls OnInitForSite on all plugins.
81+
func (pm *PluginManager) InitPluginsForSite(conf config.SiteConfig, logger *log.Logger) error {
7082
pm.mu.Lock()
7183
defer pm.mu.Unlock()
7284

7385
for _, plugin := range pm.plugins {
74-
pluginLogger := baseLogger.WithFields(log.Fields{
75-
"plugin": plugin.Name(),
76-
"domain": conf.Domain,
77-
})
78-
79-
if err := plugin.InitForSite(mwManager, pluginLogger.Logger, conf); err != nil {
86+
if err := plugin.OnInitForSite(conf, logger); err != nil {
8087
return err
8188
}
8289
}
@@ -94,3 +101,39 @@ func (pm *PluginManager) GetRegisteredPlugins() []string {
94101
}
95102
return names
96103
}
104+
105+
// PluginMiddleware applies the plugin hooks around each HTTP request.
106+
func PluginMiddleware(pm *PluginManager) middleware.MiddlewareFunc {
107+
return func(next http.Handler) http.Handler {
108+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
109+
pm.mu.Lock()
110+
registered := make([]Plugin, len(pm.plugins))
111+
copy(registered, pm.plugins)
112+
pm.mu.Unlock()
113+
114+
// BeforeRequest
115+
for _, plugin := range registered {
116+
plugin.BeforeRequest(r)
117+
}
118+
119+
// HandleRequest (plugins may intercept the request)
120+
var handled bool
121+
for _, plugin := range registered {
122+
if plugin.HandleRequest(w, r) {
123+
handled = true
124+
break
125+
}
126+
}
127+
128+
// Proceed to next if not fully handled
129+
if !handled {
130+
next.ServeHTTP(w, r)
131+
}
132+
133+
// AfterRequest
134+
for _, plugin := range registered {
135+
plugin.AfterRequest(w, r)
136+
}
137+
})
138+
}
139+
}

internal/plugin/plugin_test.go

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,55 @@ import (
55
"net/http/httptest"
66
"testing"
77

8-
log "github.com/sirupsen/logrus"
9-
108
"github.com/mirkobrombin/goup/internal/config"
11-
"github.com/mirkobrombin/goup/internal/server/middleware"
12-
"github.com/mirkobrombin/goup/plugins"
9+
log "github.com/sirupsen/logrus"
1310
)
1411

12+
type MockPlugin struct{}
13+
14+
func (m *MockPlugin) Name() string { return "MockPlugin" }
15+
func (m *MockPlugin) OnInit() error { return nil }
16+
func (m *MockPlugin) OnInitForSite(conf config.SiteConfig, logger *log.Logger) error {
17+
return nil
18+
}
19+
func (m *MockPlugin) BeforeRequest(r *http.Request) {}
20+
func (m *MockPlugin) HandleRequest(w http.ResponseWriter, r *http.Request) bool {
21+
return false
22+
}
23+
func (m *MockPlugin) AfterRequest(w http.ResponseWriter, r *http.Request) {}
24+
func (m *MockPlugin) OnExit() error { return nil }
25+
1526
func TestPluginManager(t *testing.T) {
16-
pluginManager := GetPluginManagerInstance()
17-
pluginManager.Register(&plugins.CustomHeaderPlugin{})
18-
mwManager := middleware.NewMiddlewareManager()
27+
pm := GetPluginManagerInstance()
28+
pm.Register(&MockPlugin{})
1929

20-
conf := config.SiteConfig{
21-
CustomHeaders: map[string]string{
22-
"X-GoUP-Header": "GoUP",
23-
},
30+
if err := pm.InitPlugins(); err != nil {
31+
t.Fatalf("Failed to initialize plugins globally: %v", err)
2432
}
2533

34+
conf := config.SiteConfig{
35+
Domain: "example.com",
36+
}
2637
logger := log.New()
27-
err := pluginManager.InitPluginsForSite(mwManager, logger, conf)
28-
if err != nil {
29-
t.Fatalf("Failed to initialize plugin for site: %v", err)
38+
if err := pm.InitPluginsForSite(conf, logger); err != nil {
39+
t.Fatalf("Failed to initialize plugins for site: %v", err)
3040
}
41+
}
42+
43+
func TestPluginMiddleware(t *testing.T) {
44+
pm := NewPluginManager()
45+
pm.Register(&MockPlugin{})
3146

32-
handler := mwManager.Apply(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47+
mw := PluginMiddleware(pm)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3348
w.WriteHeader(http.StatusOK)
3449
}))
3550

3651
req := httptest.NewRequest("GET", "http://example.com", nil)
3752
rec := httptest.NewRecorder()
3853

39-
handler.ServeHTTP(rec, req)
54+
mw.ServeHTTP(rec, req)
4055

41-
if rec.Header().Get("X-GoUP-Header") != "GoUP" {
42-
t.Errorf("Expected X-GoUP-Header to be 'GoUP', got '%s'", rec.Header().Get("X-GoUP-Header"))
56+
if rec.Code != http.StatusOK {
57+
t.Errorf("Expected 200, got %d", rec.Code)
4358
}
4459
}

internal/server/handler.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,27 @@ func createHandler(conf config.SiteConfig, logger *log.Logger, identifier string
3838
})
3939
}
4040

41-
// Set up middleware manager copy for this site
41+
// Copy the global middleware manager for this site
4242
siteMwManager := globalMwManager.Copy()
4343

4444
// Initialize plugins for this site
4545
pluginManager := plugin.GetPluginManagerInstance()
46-
if err := pluginManager.InitPluginsForSite(siteMwManager, logger, conf); err != nil {
46+
if err := pluginManager.InitPluginsForSite(conf, logger); err != nil {
4747
return nil, fmt.Errorf("error initializing plugins for site %s: %v", conf.Domain, err)
4848
}
4949

5050
// Add per-site middleware
51+
reqTimeout := conf.RequestTimeout
52+
if reqTimeout == 0 {
53+
reqTimeout = 60 // Default to 60 seconds
54+
}
5155
timeout := time.Duration(conf.RequestTimeout) * time.Second
5256
siteMwManager.Use(middleware.TimeoutMiddleware(timeout))
5357

5458
// Add logging middleware last to ensure it wraps the entire request
5559
siteMwManager.Use(middleware.LoggingMiddleware(logger, conf.Domain, identifier))
5660

61+
// Apply the final chain of middleware
5762
handler = siteMwManager.Apply(handler)
5863

5964
return handler, nil

internal/server/server.go

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ func StartServers(configs []config.SiteConfig, enableTUI bool, enableBench bool)
7474
mwManager.Use(middleware.BenchmarkMiddleware())
7575
}
7676

77-
// Initialize the plugins with the global middleware manager
77+
// Initialize the plugins globally
7878
pluginManager := plugin.GetPluginManagerInstance()
79-
if err := pluginManager.InitPlugins(mwManager); err != nil {
79+
if err := pluginManager.InitPlugins(); err != nil {
8080
fmt.Printf("Error initializing plugins: %v\n", err)
8181
return
8282
}
@@ -88,13 +88,10 @@ func StartServers(configs []config.SiteConfig, enableTUI bool, enableBench bool)
8888
go func(port int, confs []config.SiteConfig) {
8989
defer wg.Done()
9090
if len(confs) == 1 {
91-
// Single domain on this port, start dedicated server
9291
conf := confs[0]
93-
startSingleServer(conf, mwManager)
92+
startSingleServer(conf, mwManager, pluginManager)
9493
} else {
95-
// Multiple domains on this port, start server with
96-
// virtual host support.
97-
startVirtualHostServer(port, confs, mwManager)
94+
startVirtualHostServer(port, confs, mwManager, pluginManager)
9895
}
9996
}(port, confs)
10097
}
@@ -109,7 +106,7 @@ func StartServers(configs []config.SiteConfig, enableTUI bool, enableBench bool)
109106
}
110107

111108
// startSingleServer starts a server for a single site configuration.
112-
func startSingleServer(conf config.SiteConfig, mwManager *middleware.MiddlewareManager) {
109+
func startSingleServer(conf config.SiteConfig, mwManager *middleware.MiddlewareManager, pm *plugin.PluginManager) {
113110
identifier := conf.Domain
114111
logger := loggers[identifier]
115112

@@ -122,7 +119,17 @@ func startSingleServer(conf config.SiteConfig, mwManager *middleware.MiddlewareM
122119
}
123120
}
124121

125-
handler, err := createHandler(conf, logger, identifier, mwManager)
122+
// Initialize plugins for this site
123+
if err := pm.InitPluginsForSite(conf, logger); err != nil {
124+
logger.Errorf("Error initializing plugins for site %s: %v", conf.Domain, err)
125+
return
126+
}
127+
128+
// Add plugin middleware
129+
mwManagerCopy := mwManager.Copy()
130+
mwManagerCopy.Use(plugin.PluginMiddleware(pm))
131+
132+
handler, err := createHandler(conf, logger, identifier, mwManagerCopy)
126133
if err != nil {
127134
logger.Errorf("Error creating handler for %s: %v", conf.Domain, err)
128135
return
@@ -133,22 +140,28 @@ func startSingleServer(conf config.SiteConfig, mwManager *middleware.MiddlewareM
133140
}
134141

135142
// startVirtualHostServer starts a server that handles multiple domains on the same port.
136-
func startVirtualHostServer(port int, configs []config.SiteConfig, mwManager *middleware.MiddlewareManager) {
143+
func startVirtualHostServer(port int, configs []config.SiteConfig, mwManager *middleware.MiddlewareManager, pm *plugin.PluginManager) {
137144
identifier := fmt.Sprintf("port_%d", port)
138145
logger := loggers[identifier]
139146

140147
radixTree := radix.New()
141148

142149
for _, conf := range configs {
143-
// We do not want to start a server if the root directory does not exist
144-
// let's fail fast instead.
145150
if conf.ProxyPass == "" {
146151
if _, err := os.Stat(conf.RootDirectory); os.IsNotExist(err) {
147152
logger.Errorf("Root directory does not exist for %s: %v", conf.Domain, err)
148153
}
149154
}
150155

151-
handler, err := createHandler(conf, logger, identifier, mwManager)
156+
if err := pm.InitPluginsForSite(conf, logger); err != nil {
157+
logger.Errorf("Error initializing plugins for site %s: %v", conf.Domain, err)
158+
continue
159+
}
160+
161+
mwManagerCopy := mwManager.Copy()
162+
mwManagerCopy.Use(plugin.PluginMiddleware(pm))
163+
164+
handler, err := createHandler(conf, logger, identifier, mwManagerCopy)
152165
if err != nil {
153166
logger.Errorf("Error creating handler for %s: %v", conf.Domain, err)
154167
continue
@@ -157,7 +170,9 @@ func startVirtualHostServer(port int, configs []config.SiteConfig, mwManager *mi
157170
radixTree.Insert(conf.Domain, handler)
158171
}
159172

160-
// Main handler that routes requests based on the Host header
173+
serverConf := config.SiteConfig{
174+
Port: port,
175+
}
161176
mainHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
162177
host := r.Host
163178
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
@@ -171,9 +186,6 @@ func startVirtualHostServer(port int, configs []config.SiteConfig, mwManager *mi
171186
}
172187
})
173188

174-
serverConf := config.SiteConfig{
175-
Port: port,
176-
}
177189
server := createHTTPServer(serverConf, mainHandler)
178190
startServerInstance(server, serverConf, logger)
179191
}

0 commit comments

Comments
 (0)