forked from aegis/pyserveX
- Introduced IgnoreRequestPath option in proxy configuration to allow exact match routing. - Implemented proxy_pass directive in routing extension to handle backend requests. - Enhanced error handling for backend unavailability and timeouts. - Added integration tests for reverse proxy, including basic requests, exact match routes, regex routes, header forwarding, and query string preservation. - Created helper functions for setting up test servers and backends, along with assertion utilities for response validation. - Updated server initialization to support extension management and middleware chaining. - Improved logging for debugging purposes during request handling.
235 lines
5.4 KiB
Go
235 lines
5.4 KiB
Go
package extension
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/konduktor/konduktor/internal/logging"
|
|
)
|
|
|
|
// Manager manages all loaded extensions
|
|
type Manager struct {
|
|
extensions []Extension
|
|
registry map[string]ExtensionFactory
|
|
logger *logging.Logger
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewManager creates a new extension manager
|
|
func NewManager(logger *logging.Logger) *Manager {
|
|
m := &Manager{
|
|
extensions: make([]Extension, 0),
|
|
registry: make(map[string]ExtensionFactory),
|
|
logger: logger,
|
|
}
|
|
|
|
// Register built-in extensions
|
|
m.RegisterFactory("routing", NewRoutingExtension)
|
|
m.RegisterFactory("security", NewSecurityExtension)
|
|
m.RegisterFactory("caching", NewCachingExtension)
|
|
|
|
return m
|
|
}
|
|
|
|
// RegisterFactory registers an extension factory
|
|
func (m *Manager) RegisterFactory(name string, factory ExtensionFactory) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.registry[name] = factory
|
|
}
|
|
|
|
// LoadExtension loads an extension by type and config
|
|
func (m *Manager) LoadExtension(extType string, config map[string]interface{}) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
factory, ok := m.registry[extType]
|
|
if !ok {
|
|
return fmt.Errorf("unknown extension type: %s", extType)
|
|
}
|
|
|
|
ext, err := factory(config, m.logger)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create extension %s: %w", extType, err)
|
|
}
|
|
|
|
if err := ext.Initialize(); err != nil {
|
|
return fmt.Errorf("failed to initialize extension %s: %w", extType, err)
|
|
}
|
|
|
|
m.extensions = append(m.extensions, ext)
|
|
|
|
// Sort by priority (lower first)
|
|
sort.Slice(m.extensions, func(i, j int) bool {
|
|
return m.extensions[i].Priority() < m.extensions[j].Priority()
|
|
})
|
|
|
|
m.logger.Info("Loaded extension", "type", extType, "name", ext.Name(), "priority", ext.Priority())
|
|
return nil
|
|
}
|
|
|
|
// AddExtension adds a pre-created extension
|
|
func (m *Manager) AddExtension(ext Extension) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if err := ext.Initialize(); err != nil {
|
|
return fmt.Errorf("failed to initialize extension %s: %w", ext.Name(), err)
|
|
}
|
|
|
|
m.extensions = append(m.extensions, ext)
|
|
|
|
// Sort by priority
|
|
sort.Slice(m.extensions, func(i, j int) bool {
|
|
return m.extensions[i].Priority() < m.extensions[j].Priority()
|
|
})
|
|
|
|
m.logger.Info("Added extension", "name", ext.Name(), "priority", ext.Priority())
|
|
return nil
|
|
}
|
|
|
|
// ProcessRequest runs all extensions' ProcessRequest in order
|
|
// Returns true if any extension handled the request
|
|
func (m *Manager) ProcessRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
|
|
m.mu.RLock()
|
|
extensions := m.extensions
|
|
m.mu.RUnlock()
|
|
|
|
for _, ext := range extensions {
|
|
if !ext.Enabled() {
|
|
continue
|
|
}
|
|
|
|
handled, err := ext.ProcessRequest(ctx, w, r)
|
|
if err != nil {
|
|
m.logger.Error("Extension error", "extension", ext.Name(), "error", err)
|
|
// Continue to next extension on error
|
|
continue
|
|
}
|
|
|
|
if handled {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// ProcessResponse runs all extensions' ProcessResponse in reverse order
|
|
func (m *Manager) ProcessResponse(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
|
m.mu.RLock()
|
|
extensions := m.extensions
|
|
m.mu.RUnlock()
|
|
|
|
// Process in reverse order for response
|
|
for i := len(extensions) - 1; i >= 0; i-- {
|
|
ext := extensions[i]
|
|
if !ext.Enabled() {
|
|
continue
|
|
}
|
|
|
|
ext.ProcessResponse(ctx, w, r)
|
|
}
|
|
}
|
|
|
|
// Cleanup cleans up all extensions
|
|
func (m *Manager) Cleanup() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
for _, ext := range m.extensions {
|
|
if err := ext.Cleanup(); err != nil {
|
|
m.logger.Error("Extension cleanup error", "extension", ext.Name(), "error", err)
|
|
}
|
|
}
|
|
|
|
m.extensions = nil
|
|
}
|
|
|
|
// GetExtension returns an extension by name
|
|
func (m *Manager) GetExtension(name string) Extension {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
for _, ext := range m.extensions {
|
|
if ext.Name() == name {
|
|
return ext
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Extensions returns all loaded extensions
|
|
func (m *Manager) Extensions() []Extension {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
result := make([]Extension, len(m.extensions))
|
|
copy(result, m.extensions)
|
|
return result
|
|
}
|
|
|
|
// Handler returns an http.Handler that processes requests through all extensions
|
|
func (m *Manager) Handler(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
// Create response wrapper to capture response for ProcessResponse
|
|
wrapper := newResponseWrapper(w)
|
|
|
|
// Process request through extensions
|
|
handled, err := m.ProcessRequest(ctx, wrapper, r)
|
|
if err != nil {
|
|
m.logger.Error("Error processing request", "error", err)
|
|
}
|
|
|
|
if handled {
|
|
// Extension handled the request, process response
|
|
m.ProcessResponse(ctx, wrapper, r)
|
|
return
|
|
}
|
|
|
|
// No extension handled, pass to next handler
|
|
next.ServeHTTP(wrapper, r)
|
|
|
|
// Process response
|
|
m.ProcessResponse(ctx, wrapper, r)
|
|
})
|
|
}
|
|
|
|
// responseWrapper wraps http.ResponseWriter to allow response modification
|
|
type responseWrapper struct {
|
|
http.ResponseWriter
|
|
statusCode int
|
|
written bool
|
|
}
|
|
|
|
func newResponseWrapper(w http.ResponseWriter) *responseWrapper {
|
|
return &responseWrapper{
|
|
ResponseWriter: w,
|
|
statusCode: http.StatusOK,
|
|
}
|
|
}
|
|
|
|
func (rw *responseWrapper) WriteHeader(code int) {
|
|
if !rw.written {
|
|
rw.statusCode = code
|
|
rw.ResponseWriter.WriteHeader(code)
|
|
rw.written = true
|
|
}
|
|
}
|
|
|
|
func (rw *responseWrapper) Write(b []byte) (int, error) {
|
|
if !rw.written {
|
|
rw.WriteHeader(http.StatusOK)
|
|
}
|
|
return rw.ResponseWriter.Write(b)
|
|
}
|
|
|
|
func (rw *responseWrapper) StatusCode() int {
|
|
return rw.statusCode
|
|
}
|