mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 09:18:51 +02:00
docs: add per package README for implementation details (AI generated with human review)
This commit is contained in:
360
internal/route/rules/README.md
Normal file
360
internal/route/rules/README.md
Normal file
@@ -0,0 +1,360 @@
|
||||
# Route Rules
|
||||
|
||||
Implements a rule engine for HTTP request/response processing, enabling conditional routing, header manipulation, authentication, and more.
|
||||
|
||||
## Overview
|
||||
|
||||
The `internal/route/rules` package provides a powerful rule engine for GoDoxy. Rules allow conditional processing of HTTP requests and responses based on various matchers (headers, path, method, IP, etc.). Matching rules can modify requests, route to different backends, or terminate processing.
|
||||
|
||||
### Primary Consumers
|
||||
|
||||
- **Route layer**: Applies rules during request processing
|
||||
- **Configuration system**: Parses rule YAML
|
||||
- **Middleware integration**: Extends rule capabilities
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Does not implement proxy transport (delegates to reverse proxy)
|
||||
- Does not handle TLS/SSL (handled at entrypoint)
|
||||
- Does not perform health checking
|
||||
|
||||
### Stability
|
||||
|
||||
Internal package with stable YAML schema. Backward-compatible additions to rule types are allowed.
|
||||
|
||||
## Public API
|
||||
|
||||
### Exported Types
|
||||
|
||||
```go
|
||||
type Rules []Rule
|
||||
|
||||
type Rule struct {
|
||||
Name string // Rule identifier for debugging
|
||||
On RuleOn // Condition matcher
|
||||
Do Command // Action to execute
|
||||
}
|
||||
|
||||
type RuleOn struct {
|
||||
raw string
|
||||
checker Checker
|
||||
isResponseChecker bool
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
raw string
|
||||
exec CommandHandler
|
||||
isResponseHandler bool
|
||||
}
|
||||
```
|
||||
|
||||
### Exported Functions
|
||||
|
||||
```go
|
||||
// BuildHandler converts rules to an HTTP handler
|
||||
func (rules Rules) BuildHandler(up http.HandlerFunc) http.HandlerFunc
|
||||
|
||||
// ParseRules parses rule configuration
|
||||
func ParseRules(config string) (Rules, error)
|
||||
|
||||
// ValidateRules validates rule syntax
|
||||
func ValidateRules(config string) error
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Rules {
|
||||
+BuildHandler(up) http.HandlerFunc
|
||||
}
|
||||
|
||||
class Rule {
|
||||
+Name string
|
||||
+On RuleOn
|
||||
+Do Command
|
||||
+IsResponseRule() bool
|
||||
}
|
||||
|
||||
class RuleOn {
|
||||
+raw string
|
||||
+checker Checker
|
||||
+isResponseChecker bool
|
||||
}
|
||||
|
||||
class Command {
|
||||
+raw string
|
||||
+exec CommandHandler
|
||||
+isResponseHandler bool
|
||||
}
|
||||
|
||||
class Checker {
|
||||
<<interface>>
|
||||
+Check(r *http.Request) bool
|
||||
+CheckResponse(w ResponseWriter, r *http.Request) bool
|
||||
}
|
||||
|
||||
class CommandHandler {
|
||||
<<interface>>
|
||||
+Execute(w ResponseWriter, r *http.Request, rm *ResponseModifier) gperr.Error
|
||||
}
|
||||
|
||||
Rules --> Rule
|
||||
Rule --> RuleOn
|
||||
Rule --> Command
|
||||
RuleOn --> Checker
|
||||
Command --> CommandHandler
|
||||
```
|
||||
|
||||
### Request Processing Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Req as Request
|
||||
participant Pre as Pre Rules
|
||||
participant Proxy as Upstream
|
||||
participant Post as Post Rules
|
||||
|
||||
Req->>Pre: Check pre-rules
|
||||
alt Rule matches
|
||||
Pre->>Pre: Execute handler
|
||||
alt Terminating action
|
||||
Pre-->>Req: Response
|
||||
Return-->>Req: Return immediately
|
||||
end
|
||||
end
|
||||
Req->>Proxy: Forward request
|
||||
Proxy-->>Req: Response
|
||||
Req->>Post: Check post-rules
|
||||
Post->>Post: Execute handlers
|
||||
Post-->>Req: Modified response
|
||||
```
|
||||
|
||||
### Condition Matchers
|
||||
|
||||
| Matcher | Type | Description |
|
||||
| ------------- | -------- | ---------------------------- |
|
||||
| `header` | Request | Match request header value |
|
||||
| `query` | Request | Match query parameter |
|
||||
| `cookie` | Request | Match cookie value |
|
||||
| `form` | Request | Match form field |
|
||||
| `method` | Request | Match HTTP method |
|
||||
| `host` | Request | Match virtual host |
|
||||
| `path` | Request | Match request path |
|
||||
| `proto` | Request | Match protocol (http/https) |
|
||||
| `remote` | Request | Match remote IP/CIDR |
|
||||
| `basic_auth` | Request | Match basic auth credentials |
|
||||
| `route` | Request | Match route name |
|
||||
| `resp_header` | Response | Match response header |
|
||||
| `status` | Response | Match status code range |
|
||||
|
||||
### Matcher Types
|
||||
|
||||
```sh
|
||||
# String: exact match (default)
|
||||
# Glob: shell-style wildcards (*, ?)
|
||||
# Regex: regular expressions
|
||||
|
||||
path /api/users // exact match
|
||||
path glob("/api/*") // glob pattern
|
||||
path regex("/api/v[0-9]+/.*") // regex pattern
|
||||
```
|
||||
|
||||
### Actions
|
||||
|
||||
**Terminating Actions** (stop processing):
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------ | ---------------------- |
|
||||
| `error <code> <message>` | Return HTTP error |
|
||||
| `redirect <url>` | Redirect to URL |
|
||||
| `serve <path>` | Serve local files |
|
||||
| `route <name>` | Route to another route |
|
||||
| `proxy <url>` | Proxy to upstream |
|
||||
|
||||
**Non-Terminating Actions** (modify and continue):
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------ | ---------------------- |
|
||||
| `pass` / `bypass` | Pass through unchanged |
|
||||
| `rewrite <from> <to>` | Rewrite request path |
|
||||
| `require_auth` | Require authentication |
|
||||
| `require_basic_auth <realm>` | Basic auth challenge |
|
||||
| `set <target> <field> <value>` | Set header/variable |
|
||||
| `add <target> <field> <value>` | Add header/variable |
|
||||
| `remove <target> <field>` | Remove header/variable |
|
||||
|
||||
**Response Actions**:
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------------------ | ----------------- |
|
||||
| `log <level> <path> <template>` | Log response |
|
||||
| `notify <level> <provider> <title> <body>` | Send notification |
|
||||
|
||||
## Configuration Surface
|
||||
|
||||
### Rule Configuration (YAML)
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
- name: rule name
|
||||
on: |
|
||||
condition1
|
||||
& condition2
|
||||
do: |
|
||||
action1
|
||||
action2
|
||||
```
|
||||
|
||||
### Condition Syntax
|
||||
|
||||
```yaml
|
||||
# Simple condition
|
||||
on: path /api/users
|
||||
|
||||
# Multiple conditions (AND)
|
||||
on: |
|
||||
header Authorization Bearer
|
||||
& path /api/admin/*
|
||||
|
||||
# Negation
|
||||
on: !path /public/*
|
||||
|
||||
# OR within a line
|
||||
on: method GET | method POST
|
||||
```
|
||||
|
||||
### Variable Substitution
|
||||
|
||||
```go
|
||||
// Static variables
|
||||
$req_method // Request method
|
||||
$req_host // Request host
|
||||
$req_path // Request path
|
||||
$status_code // Response status
|
||||
$remote_host // Client IP
|
||||
|
||||
// Dynamic variables
|
||||
$header(Name) // Request header
|
||||
$header(Name, index) // Header at index
|
||||
$arg(Name) // Query argument
|
||||
$form(Name) // Form field
|
||||
|
||||
// Environment variables
|
||||
${ENV_VAR}
|
||||
```
|
||||
|
||||
## Dependency and Integration Map
|
||||
|
||||
| Dependency | Purpose |
|
||||
| ---------------------------- | ------------------------ |
|
||||
| `internal/route` | Route type definitions |
|
||||
| `internal/auth` | Authentication handlers |
|
||||
| `internal/acl` | IP-based access control |
|
||||
| `internal/notif` | Notification integration |
|
||||
| `internal/logging/accesslog` | Response logging |
|
||||
| `pkg/gperr` | Error handling |
|
||||
| `golang.org/x/net/http2` | HTTP/2 support |
|
||||
|
||||
## Observability
|
||||
|
||||
### Logs
|
||||
|
||||
- **DEBUG**: Rule matching details, variable substitution
|
||||
- **INFO**: Rule execution, terminating actions
|
||||
- **ERROR**: Rule parse errors, execution failures
|
||||
|
||||
Log context includes: `rule`, `alias`, `match_result`
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- `require_auth` enforces authentication
|
||||
- `remote` matcher supports IP/CIDR for access control
|
||||
- Variables are sanitized to prevent injection
|
||||
- Path rewrites are validated to prevent traversal
|
||||
|
||||
## Failure Modes and Recovery
|
||||
|
||||
| Failure | Behavior | Recovery |
|
||||
| ------------------- | ------------------------- | ---------------------------------- |
|
||||
| Invalid rule syntax | Route validation fails | Fix YAML syntax |
|
||||
| Missing variables | Variable renders as empty | Check variable sources |
|
||||
| Rule timeout | Request times out | Increase timeout or simplify rules |
|
||||
| Auth failure | Returns 401/403 | Fix credentials |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Pass-Through
|
||||
|
||||
```yaml
|
||||
- name: default
|
||||
do: pass
|
||||
```
|
||||
|
||||
### Path-Based Routing
|
||||
|
||||
```yaml
|
||||
- name: api proxy
|
||||
on: path /api/*
|
||||
do: proxy http://api-backend:8080
|
||||
|
||||
- name: static files
|
||||
on: path /static/*
|
||||
do: serve /var/www/static
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
```yaml
|
||||
- name: admin protection
|
||||
on: path /admin/*
|
||||
do: require_auth
|
||||
|
||||
- name: basic auth for API
|
||||
on: path /api/*
|
||||
do: require_basic_auth "API Access"
|
||||
```
|
||||
|
||||
### Path Rewriting
|
||||
|
||||
```yaml
|
||||
- name: rewrite API v1
|
||||
on: path /v1/*
|
||||
do: |
|
||||
rewrite /v1 /api/v1
|
||||
proxy http://backend:8080
|
||||
```
|
||||
|
||||
### IP-Based Access Control
|
||||
|
||||
```yaml
|
||||
- name: allow internal
|
||||
on: remote 10.0.0.0/8
|
||||
do: pass
|
||||
|
||||
- name: block external
|
||||
on: |
|
||||
!remote 10.0.0.0/8
|
||||
!remote 192.168.0.0/16
|
||||
do: error 403 "Access Denied"
|
||||
```
|
||||
|
||||
### WebSocket Support
|
||||
|
||||
```yaml
|
||||
- name: websocket upgrade
|
||||
on: |
|
||||
header Connection Upgrade
|
||||
header Upgrade websocket
|
||||
do: bypass
|
||||
```
|
||||
|
||||
## Testing Notes
|
||||
|
||||
- Unit tests for all matchers and actions
|
||||
- Integration tests with real HTTP requests
|
||||
- Parser tests for YAML syntax
|
||||
- Variable substitution tests
|
||||
- Performance benchmarks for hot paths
|
||||
202
internal/route/rules/presets/README.md
Normal file
202
internal/route/rules/presets/README.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Rule Presets
|
||||
|
||||
Provides embedded, pre-configured rule sets for common routing patterns.
|
||||
|
||||
## Overview
|
||||
|
||||
The `internal/route/rules/presets` package provides embedded YAML rule configurations that can be reused across routes. Presets are compiled into the binary and loaded at runtime via `sync.Once` initialization.
|
||||
|
||||
### Primary Consumers
|
||||
|
||||
- **Route configuration**: Applies preset rules to routes
|
||||
- **WebUI**: Provides default rules for web applications
|
||||
- **API gateway**: Common patterns for API routes
|
||||
|
||||
### Non-goals
|
||||
|
||||
- Does not modify presets at runtime
|
||||
- Does not provide dynamic preset loading
|
||||
- Does not support preset inheritance/overriding
|
||||
|
||||
### Stability
|
||||
|
||||
Internal package. Preset content is stable but may change between versions.
|
||||
|
||||
## Public API
|
||||
|
||||
### Exported Functions
|
||||
|
||||
```go
|
||||
// GetRulePreset retrieves a preset by name
|
||||
func GetRulePreset(name string) (rules.Rules, bool)
|
||||
```
|
||||
|
||||
**Contract:**
|
||||
|
||||
- Uses `sync.Once` for one-time initialization
|
||||
- Returns a copy of the preset rules
|
||||
- Second return value indicates if preset exists
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class GetRulePreset {
|
||||
<<function>>
|
||||
+sync.Once
|
||||
+rulePresets map
|
||||
}
|
||||
|
||||
class RulePreset {
|
||||
<<embedded>>
|
||||
+fs.FS
|
||||
}
|
||||
|
||||
class webui {
|
||||
<<preset>>
|
||||
+Login page rule
|
||||
+Protected routes rule
|
||||
+API proxy rule
|
||||
+Auth proxy rule
|
||||
}
|
||||
|
||||
GetRulePreset --> RulePreset : loads
|
||||
RulePreset --> webui : contains
|
||||
```
|
||||
|
||||
### Preset Loading Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as Application
|
||||
participant Once as sync.Once
|
||||
participant FS as Embedded FS
|
||||
participant Parser as YAML Parser
|
||||
participant Map as rulePresets
|
||||
|
||||
App->>Once: GetRulePreset("webui")
|
||||
Once->>Once: once.Do(initPresets)
|
||||
Once->>FS: ReadDir("presets/")
|
||||
FS-->>Once: List files
|
||||
loop Each file
|
||||
Once->>FS: ReadFile(filename)
|
||||
FS-->>Once: YAML content
|
||||
Once->>Parser: Parse YAML
|
||||
Parser-->>Once: rules.Rules
|
||||
Once->>Map: Store in map
|
||||
end
|
||||
Map-->>Once: Loaded presets
|
||||
Once-->>App: Rules copy
|
||||
```
|
||||
|
||||
## Preset Files
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
internal/route/rules/presets/
|
||||
├── embed.go //go:embed *.yml
|
||||
├── webui.yml // WebUI preset
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### webui Preset
|
||||
|
||||
The default preset for GoDoxy WebUI:
|
||||
|
||||
**What it does:**
|
||||
|
||||
1. Allows unauthenticated access to `/login`
|
||||
1. Requires auth for most paths, excluding static assets and auth endpoints
|
||||
1. Proxies `/api/v1/*` to backend
|
||||
1. Rewrites and proxies `/auth/*` to backend
|
||||
|
||||
## Dependency and Integration Map
|
||||
|
||||
| Dependency | Purpose |
|
||||
| ------------------------ | ------------------------------- |
|
||||
| `internal/route/rules` | Rules engine for preset content |
|
||||
| `internal/serialization` | YAML parsing |
|
||||
| `sync` | One-time initialization |
|
||||
|
||||
## Observability
|
||||
|
||||
### Logs
|
||||
|
||||
- DEBUG: Preset loading errors
|
||||
- WARN: Missing preset files
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Presets are compiled into binary (immutable at runtime)
|
||||
- Environment variable substitution (`${VAR}`) supports secure configuration
|
||||
- No runtime preset modification possible
|
||||
|
||||
## Failure Modes and Recovery
|
||||
|
||||
| Failure | Behavior | Recovery |
|
||||
| ------------------- | -------------------- | --------------------- |
|
||||
| Preset file missing | Returns (nil, false) | Check preset exists |
|
||||
| YAML parse error | Panic during init | Fix preset YAML |
|
||||
| Unknown preset name | Returns (nil, false) | Use valid preset name |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Loading a Preset
|
||||
|
||||
```go
|
||||
import "github.com/yusing/godoxy/internal/route/rules/presets"
|
||||
|
||||
rules, ok := presets.GetRulePreset("webui")
|
||||
if !ok {
|
||||
return fmt.Errorf("preset not found")
|
||||
}
|
||||
|
||||
// Apply rules to a handler
|
||||
handler := rules.BuildHandler(upstreamHandler)
|
||||
```
|
||||
|
||||
### Creating a Custom Preset
|
||||
|
||||
```yaml
|
||||
# internal/route/rules/presets/api-gateway.yml
|
||||
- name: cors headers
|
||||
on: method OPTIONS
|
||||
do: |
|
||||
set header Access-Control-Allow-Origin *
|
||||
set header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
|
||||
error 204 "No Content"
|
||||
|
||||
- name: auth required
|
||||
on: !path /health
|
||||
do: require_auth
|
||||
```
|
||||
|
||||
Then load it:
|
||||
|
||||
```go
|
||||
rules, ok := presets.GetRulePreset("api-gateway")
|
||||
```
|
||||
|
||||
### Using Preset in Route Config
|
||||
|
||||
```yaml
|
||||
routes:
|
||||
webapp:
|
||||
target: http://localhost:3000
|
||||
rules: |
|
||||
- name: include webui preset
|
||||
do: include webui
|
||||
- name: additional rule
|
||||
on: path /custom/*
|
||||
do: proxy http://custom:8080
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Presets are read-only after initialization
|
||||
- No runtime preset modification
|
||||
- All presets loaded at first access (no lazy loading)
|
||||
- No preset merging (caller must handle)
|
||||
Reference in New Issue
Block a user