chore(docs): update package docs

This commit is contained in:
yusing
2026-02-07 20:24:47 +08:00
parent f73ed38115
commit 7785383245
3 changed files with 363 additions and 246 deletions

View File

@@ -54,7 +54,7 @@ type State interface {
Task() *task.Task Task() *task.Task
Context() context.Context Context() context.Context
Value() *Config Value() *Config
EntrypointHandler() http.Handler Entrypoint() entrypoint.Entrypoint
ShortLinkMatcher() config.ShortLinkMatcher ShortLinkMatcher() config.ShortLinkMatcher
AutoCertProvider() server.CertProvider AutoCertProvider() server.CertProvider
LoadOrStoreProvider(key string, value types.RouteProvider) (actual types.RouteProvider, loaded bool) LoadOrStoreProvider(key string, value types.RouteProvider) (actual types.RouteProvider, loaded bool)
@@ -62,6 +62,12 @@ type State interface {
IterProviders() iter.Seq2[string, types.RouteProvider] IterProviders() iter.Seq2[string, types.RouteProvider]
StartProviders() error StartProviders() error
NumProviders() int NumProviders() int
// Lifecycle management
StartAPIServers()
StartMetrics()
FlushTmpLog()
} }
``` ```
@@ -214,12 +220,15 @@ Configuration supports hot-reloading via editing `config/config.yml`.
- `internal/acl` - Access control configuration - `internal/acl` - Access control configuration
- `internal/autocert` - SSL certificate management - `internal/autocert` - SSL certificate management
- `internal/entrypoint` - HTTP entrypoint setup - `internal/entrypoint` - HTTP entrypoint setup (now via interface)
- `internal/route/provider` - Route providers (Docker, file, agent) - `internal/route/provider` - Route providers (Docker, file, agent)
- `internal/maxmind` - GeoIP configuration - `internal/maxmind` - GeoIP configuration
- `internal/notif` - Notification providers - `internal/notif` - Notification providers
- `internal/proxmox` - LXC container management - `internal/proxmox` - LXC container management
- `internal/homepage/types` - Dashboard configuration - `internal/homepage/types` - Dashboard configuration
- `internal/api` - REST API servers
- `internal/metrics/systeminfo` - System metrics polling
- `internal/metrics/uptime` - Uptime tracking
- `github.com/yusing/goutils/task` - Object lifecycle management - `github.com/yusing/goutils/task` - Object lifecycle management
### External dependencies ### External dependencies
@@ -312,5 +321,8 @@ for name, provider := range config.GetState().IterProviders() {
```go ```go
state := config.GetState() state := config.GetState()
http.Handle("/", state.EntrypointHandler()) // Get entrypoint interface for route management
ep := state.Entrypoint()
// Add routes directly to entrypoint
ep.AddRoute(route)
``` ```

View File

@@ -1,10 +1,10 @@
# Entrypoint # Entrypoint
The entrypoint package provides the main HTTP entry point for GoDoxy, handling domain-based routing, middleware application, short link matching, and access logging. The entrypoint package provides the main HTTP entry point for GoDoxy, handling domain-based routing, middleware application, short link matching, access logging, and HTTP/TCP/UDP server lifecycle management.
## Overview ## Overview
The entrypoint package implements the primary HTTP handler that receives all incoming requests, determines the target route based on hostname, applies middleware, and forwards requests to the appropriate route handler. The entrypoint package implements the primary HTTP handler that receives all incoming requests, manages the lifecycle of HTTP/TCP/UDP servers, determines the target route based on hostname, applies middleware, and forwards requests to the appropriate route handler.
### Key Features ### Key Features
@@ -14,103 +14,310 @@ The entrypoint package implements the primary HTTP handler that receives all inc
- Access logging for all requests - Access logging for all requests
- Configurable not-found handling - Configurable not-found handling
- Per-domain route resolution - Per-domain route resolution
- Multi-protocol server management (HTTP/HTTPS/TCP/UDP)
- Route pool abstractions via [`PoolLike`](internal/entrypoint/types/entrypoint.go:27) and [`RWPoolLike`](internal/entrypoint/types/entrypoint.go:33) interfaces
## Architecture ### Primary Consumers
```mermaid - **HTTP servers**: Per-listen-addr servers dispatch requests to routes
graph TD - **Route providers**: Register routes via [`AddRoute`](internal/entrypoint/routes.go:48)
A[HTTP Request] --> B[Entrypoint Handler] - **Configuration layer**: Validates and applies middleware/access-logging config
B --> C{Access Logger?}
C -->|Yes| D[Wrap Response Recorder]
C -->|No| E[Skip Logging]
D --> F[Find Route by Host] ### Non-goals
E --> F
F --> G{Route Found?} - Does not implement route discovery (delegates to providers)
G -->|Yes| H{Middleware?} - Does not handle TLS certificate management (delegates to autocert)
G -->|No| I{Short Link?} - Does not implement health checks (delegates to `internal/health/monitor`)
I -->|Yes| J[Short Link Handler]
I -->|No| K{Not Found Handler?}
K -->|Yes| L[Not Found Handler]
K -->|No| M[Serve 404]
H -->|Yes| N[Apply Middleware] ### Stability
H -->|No| O[Direct Route]
N --> O
O --> P[Route ServeHTTP] Internal package with stable core interfaces. The [`Entrypoint`](internal/entrypoint/types/entrypoint.go:7) interface is the public contract.
P --> Q[Response]
L --> R[404 Response]
J --> Q
M --> R
```
## Core Components
### Entrypoint Structure
```go
type Entrypoint struct {
middleware *middleware.Middleware
notFoundHandler http.Handler
accessLogger accesslog.AccessLogger
findRouteFunc func(host string) types.HTTPRoute
shortLinkTree *ShortLinkMatcher
}
```
### Active Config
```go
var ActiveConfig atomic.Pointer[entrypoint.Config]
```
## Public API ## Public API
### Creation ### Entrypoint Interface
```go ```go
// NewEntrypoint creates a new entrypoint instance. type Entrypoint interface {
func NewEntrypoint() Entrypoint // Server capabilities
SupportProxyProtocol() bool
DisablePoolsLog(v bool)
// Route registry access
GetRoute(alias string) (types.Route, bool)
AddRoute(r types.Route)
IterRoutes(yield func(r types.Route) bool)
NumRoutes() int
RoutesByProvider() map[string][]types.Route
// Route pool accessors
HTTPRoutes() PoolLike[types.HTTPRoute]
StreamRoutes() PoolLike[types.StreamRoute]
ExcludedRoutes() RWPoolLike[types.Route]
// Health info queries
GetHealthInfo() map[string]types.HealthInfo
GetHealthInfoWithoutDetail() map[string]types.HealthInfoWithoutDetail
GetHealthInfoSimple() map[string]types.HealthStatus
}
```
### Pool Interfaces
```go
type PoolLike[Route types.Route] interface {
Get(alias string) (Route, bool)
Iter(yield func(alias string, r Route) bool)
Size() int
}
type RWPoolLike[Route types.Route] interface {
PoolLike[Route]
Add(r Route)
Del(r Route)
}
``` ```
### Configuration ### Configuration
```go ```go
// SetFindRouteDomains configures domain-based route lookup. type Config struct {
func (ep *Entrypoint) SetFindRouteDomains(domains []string) SupportProxyProtocol bool `json:"support_proxy_protocol"`
}
// SetMiddlewares loads and configures middleware chain.
func (ep *Entrypoint) SetMiddlewares(mws []map[string]any) error
// SetNotFoundRules configures the not-found handler.
func (ep *Entrypoint) SetNotFoundRules(rules rules.Rules)
// SetAccessLogger initializes access logging.
func (ep *Entrypoint) SetAccessLogger(parent task.Parent, cfg *accesslog.RequestLoggerConfig) error
// ShortLinkMatcher returns the short link matcher.
func (ep *Entrypoint) ShortLinkMatcher() *ShortLinkMatcher
``` ```
### Request Handling ## Architecture
### Core Components
```mermaid
classDiagram
class Entrypoint {
+task *task.Task
+cfg *Config
+middleware *middleware.Middleware
+shortLinkMatcher *ShortLinkMatcher
+streamRoutes *pool.Pool[types.StreamRoute]
+excludedRoutes *pool.Pool[types.Route]
+servers *xsync.Map[string, *httpServer]
+tcpListeners *xsync.Map[string, net.Listener]
+udpListeners *xsync.Map[string, net.PacketConn]
+SupportProxyProtocol() bool
+AddRoute(r)
+IterRoutes(yield)
+HTTPRoutes() PoolLike
}
class httpServer {
+routes *routePool
+ServeHTTP(w, r)
+AddRoute(route)
+DelRoute(route)
}
class routePool {
+Get(alias) (HTTPRoute, bool)
+AddRoute(route)
+DelRoute(route)
}
class PoolLike {
<<interface>>
+Get(alias) (Route, bool)
+Iter(yield) bool
+Size() int
}
class RWPoolLike {
<<interface>>
+PoolLike
+Add(r Route)
+Del(r Route)
}
Entrypoint --> httpServer : manages
Entrypoint --> routePool : HTTPRoutes()
Entrypoint --> PoolLike : returns
Entrypoint --> RWPoolLike : ExcludedRoutes()
```
### Request Processing Pipeline
```mermaid
flowchart TD
A[HTTP Request] --> B[Find Route by Host]
B --> C{Route Found?}
C -->|Yes| D{Middleware?}
C -->|No| E{Short Link?}
E -->|Yes| F[Short Link Handler]
E -->|No| G{Not Found Handler?}
G -->|Yes| H[Not Found Handler]
G -->|No| I[Serve 404]
D -->|Yes| J[Apply Middleware Chain]
D -->|No| K[Direct Route Handler]
J --> K
K --> L[Route ServeHTTP]
L --> M[Response]
F --> M
H --> N[404 Response]
I --> N
```
### Server Lifecycle
```mermaid
stateDiagram-v2
[*] --> Empty: NewEntrypoint()
Empty --> Listening: AddRoute()
Listening --> Listening: AddRoute()
Listening --> Listening: delHTTPRoute()
Listening --> [*]: Cancel()
Listening --> AddingServer: addHTTPRoute()
AddingServer --> Listening: Server starts
note right of Listening
servers map: addr -> httpServer
tcpListeners map: addr -> Listener
udpListeners map: addr -> PacketConn
end note
```
## Data Flow
```mermaid
sequenceDiagram
participant Client
participant httpServer
participant Entrypoint
participant Middleware
participant Route
Client->>httpServer: GET /path
httpServer->>Entrypoint: FindRoute(host)
alt Route Found
Entrypoint-->>httpServer: HTTPRoute
httpServer->>Middleware: ServeHTTP(routeHandler)
alt Has Middleware
Middleware->>Middleware: Process Chain
end
Middleware->>Route: Forward Request
Route-->>Middleware: Response
Middleware-->>httpServer: Response
else Short Link
httpServer->>ShortLinkMatcher: Match short code
ShortLinkMatcher-->>httpServer: Redirect
else Not Found
httpServer->>NotFoundHandler: Serve 404
NotFoundHandler-->>httpServer: 404 Page
end
httpServer-->>Client: Response
```
## Route Registry
Routes are now managed per-entrypoint instead of global registry:
```go ```go
// ServeHTTP is the main HTTP handler. // Adding a route
func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) ep.AddRoute(route)
// FindRoute looks up a route by hostname. // Iterating all routes
func (ep *Entrypoint) FindRoute(s string) types.HTTPRoute ep.IterRoutes(func(r types.Route) bool {
log.Info().Str("alias", r.Name()).Msg("route")
return true // continue iteration
})
// Querying by alias
route, ok := ep.GetRoute("myapp")
// Grouping by provider
byProvider := ep.RoutesByProvider()
``` ```
## Usage ## Configuration Surface
### Config Source
Environment variables and YAML config file:
```yaml
entrypoint:
support_proxy_protocol: true
```
### Environment Variables
| Variable | Description |
| ------------------------------ | ----------------------------- |
| `PROXY_SUPPORT_PROXY_PROTOCOL` | Enable PROXY protocol support |
## Dependency and Integration Map
| Dependency | Purpose |
| -------------------------------- | -------------------------- |
| `internal/route` | Route types and handlers |
| `internal/route/rules` | Not-found rules processing |
| `internal/logging/accesslog` | Request logging |
| `internal/net/gphttp/middleware` | Middleware chain |
| `internal/types` | Route and health types |
| `github.com/puzpuzpuz/xsync/v4` | Concurrent server map |
| `github.com/yusing/goutils/pool` | Route pool implementations |
| `github.com/yusing/goutils/task` | Lifecycle management |
## Observability
### Logs
| Level | Context | Description |
| ------- | --------------------- | ----------------------- |
| `DEBUG` | `route`, `listen_url` | Route addition/removal |
| `DEBUG` | `addr`, `proto` | Server lifecycle |
| `ERROR` | `route`, `listen_url` | Server startup failures |
### Metrics
Route metrics exposed via [`GetHealthInfo`](internal/entrypoint/query.go:10) methods:
```go
// Health info for all routes
healthMap := ep.GetHealthInfo()
// {
// "myapp": {Status: "healthy", Uptime: 3600, Latency: 5ms},
// "excluded-route": {Status: "unknown", Detail: "n/a"},
// }
```
## Security Considerations
- Route lookup is read-only from route pools
- Middleware chain is applied per-request
- Proxy protocol support must be explicitly enabled
- Access logger captures request metadata before processing
## Failure Modes and Recovery
| Failure | Behavior | Recovery |
| --------------------- | ------------------------------ | ---------------------------- |
| Server bind fails | Error logged, route not added | Fix port/address conflict |
| Route start fails | Route excluded, error logged | Fix route configuration |
| Middleware load fails | AddRoute returns error | Fix middleware configuration |
| Context cancelled | All servers stopped gracefully | Restart entrypoint |
## Usage Examples
### Basic Setup ### Basic Setup
```go ```go
ep := entrypoint.NewEntrypoint() ep := entrypoint.NewEntrypoint(parent, &entrypoint.Config{
SupportProxyProtocol: false,
})
// Configure domain matching // Configure domain matching
ep.SetFindRouteDomains([]string{".example.com", "example.com"}) ep.SetFindRouteDomains([]string{".example.com", "example.com"})
@@ -120,7 +327,7 @@ err := ep.SetMiddlewares([]map[string]any{
{"rate_limit": map[string]any{"requests_per_second": 100}}, {"rate_limit": map[string]any{"requests_per_second": 100}},
}) })
if err != nil { if err != nil {
log.Fatal(err) return err
} }
// Configure access logging // Configure access logging
@@ -128,181 +335,59 @@ err = ep.SetAccessLogger(parent, &accesslog.RequestLoggerConfig{
Path: "/var/log/godoxy/access.log", Path: "/var/log/godoxy/access.log",
}) })
if err != nil { if err != nil {
log.Fatal(err) return err
} }
// Start server
http.ListenAndServe(":80", &ep)
``` ```
### Route Lookup Logic ### Route Querying
The entrypoint uses multiple strategies to find routes:
1. **Subdomain Matching**: For `sub.domain.com`, looks for `sub`
1. **Exact Match**: Looks for the full hostname
1. **Port Stripping**: Strips port from host if present
```go ```go
func findRouteAnyDomain(host string) types.HTTPRoute { // Iterate all routes including excluded
// Try subdomain (everything before first dot) for r := range ep.IterRoutes {
idx := strings.IndexByte(host, '.') log.Info().
if idx != -1 { Str("alias", r.Name()).
target := host[:idx] Str("provider", r.ProviderName()).
if r, ok := routes.HTTP.Get(target); ok { Bool("excluded", r.ShouldExclude()).
return r Msg("route")
} }
}
// Try exact match // Get health info for all routes
if r, ok := routes.HTTP.Get(host); ok { healthMap := ep.GetHealthInfoSimple()
return r for alias, status := range healthMap {
} log.Info().Str("alias", alias).Str("status", string(status)).Msg("health")
// Try stripping port
if before, _, ok := strings.Cut(host, ":"); ok {
if r, ok := routes.HTTP.Get(before); ok {
return r
}
}
return nil
} }
``` ```
### Short Links ### Route Addition
Short links use a special `.short` domain:
```go ```go
// Request to: https://abc.short.example.com route := &route.Route{
// Looks for route with alias "abc" Alias: "myapp",
if strings.EqualFold(host, common.ShortLinkPrefix) { Scheme: route.SchemeHTTP,
// Handle short link Host: "myapp",
ep.shortLinkTree.ServeHTTP(w, r) Port: route.Port{Proxy: 80, Target: 3000},
} }
ep.AddRoute(route)
``` ```
## Data Flow ## Context Integration
```mermaid Routes can access the entrypoint from request context:
sequenceDiagram
participant Client
participant Entrypoint
participant Middleware
participant Route
participant Logger
Client->>Entrypoint: GET /path
Entrypoint->>Entrypoint: FindRoute(host)
alt Route Found
Entrypoint->>Logger: Get ResponseRecorder
Logger-->>Entrypoint: Recorder
Entrypoint->>Middleware: ServeHTTP(routeHandler)
alt Has Middleware
Middleware->>Middleware: Process Chain
end
Middleware->>Route: Forward Request
Route-->>Middleware: Response
Middleware-->>Entrypoint: Response
else Short Link
Entrypoint->>ShortLinkTree: Match short code
ShortLinkTree-->>Entrypoint: Redirect
else Not Found
Entrypoint->>NotFoundHandler: Serve 404
NotFoundHandler-->>Entrypoint: 404 Page
end
Entrypoint->>Logger: Log Request
Logger-->>Entrypoint: Complete
Entrypoint-->>Client: Response
```
## Not-Found Handling
When no route is found, the entrypoint:
1. Attempts to serve a static error page file
1. Logs the 404 request
1. Falls back to the configured error page
1. Returns 404 status code
```go ```go
func (ep *Entrypoint) serveNotFound(w http.ResponseWriter, r *http.Request) { // Set entrypoint in context
if served := middleware.ServeStaticErrorPageFile(w, r); !served { entrypoint.SetCtx(r.Context(), ep)
log.Error().
Str("method", r.Method).
Str("url", r.URL.String()).
Str("remote", r.RemoteAddr).
Msgf("not found: %s", r.Host)
errorPage, ok := errorpage.GetErrorPageByStatus(http.StatusNotFound) // Get entrypoint from context
if ok { if ep := entrypoint.FromCtx(r.Context()); ep != nil {
w.WriteHeader(http.StatusNotFound) route, ok := ep.GetRoute("alias")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(errorPage)
} else {
http.NotFound(w, r)
}
}
} }
``` ```
## Configuration Structure ## Testing Notes
```go - Benchmark tests in [`entrypoint_benchmark_test.go`](internal/entrypoint/entrypoint_benchmark_test.go)
type Config struct { - Integration tests in [`entrypoint_test.go`](internal/entrypoint/entrypoint_test.go)
Middlewares []map[string]any `json:"middlewares"` - Mock route pools for unit testing
Rules rules.Rules `json:"rules"` - Short link tests in [`shortlink_test.go`](internal/entrypoint/shortlink_test.go)
AccessLog *accesslog.RequestLoggerConfig `json:"access_log"`
}
```
## Middleware Integration
The entrypoint supports middleware chains configured via YAML:
```yaml
entrypoint:
middlewares:
- use: rate_limit
average: 100
burst: 200
bypass:
- remote 192.168.1.0/24
- use: redirect_http
```
## Access Logging
Access logging wraps the response recorder to capture:
- Request method and URL
- Response status code
- Response size
- Request duration
- Client IP address
```go
func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if ep.accessLogger != nil {
rec := accesslog.GetResponseRecorder(w)
w = rec
defer func() {
ep.accessLogger.Log(r, rec.Response())
accesslog.PutResponseRecorder(rec)
}()
}
// ... handle request
}
```
## Integration Points
The entrypoint integrates with:
- **Route Registry**: HTTP route lookup
- **Middleware**: Request processing chain
- **AccessLog**: Request logging
- **ErrorPage**: 404 error pages
- **ShortLink**: Short link handling

View File

@@ -30,9 +30,11 @@ Internal package with stable core types. Route configuration schema is versioned
type Route struct { type Route struct {
Alias string // Unique route identifier Alias string // Unique route identifier
Scheme Scheme // http, https, h2c, tcp, udp, fileserver Scheme Scheme // http, https, h2c, tcp, udp, fileserver
Host string // Virtual host Host string // Virtual host / target address
Port Port // Listen and target ports Port Port // Listen and target ports
Bind string // Bind address for listening (IP address, optional)
// File serving // File serving
Root string // Document root Root string // Document root
SPA bool // Single-page app mode SPA bool // Single-page app mode
@@ -196,6 +198,7 @@ type Route struct {
Alias string `json:"alias"` Alias string `json:"alias"`
Scheme Scheme `json:"scheme"` Scheme Scheme `json:"scheme"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Bind string `json:"bind,omitempty"` // Listen bind address
Port Port `json:"port"` Port Port `json:"port"`
Root string `json:"root,omitempty"` Root string `json:"root,omitempty"`
SPA bool `json:"spa,omitempty"` SPA bool `json:"spa,omitempty"`
@@ -218,23 +221,28 @@ labels:
routes: routes:
myapp: myapp:
scheme: http scheme: http
root: /var/www/myapp host: myapp.local
spa: true bind: 192.168.1.100 # Optional: bind to specific address
port:
proxy: 80
target: 3000
``` ```
### Route with Custom Bind Address
## Dependency and Integration Map ## Dependency and Integration Map
| Dependency | Purpose | | Dependency | Purpose |
| -------------------------------- | -------------------------------- | | ---------------------------------- | --------------------------------- |
| `internal/route/routes` | Route registry and lookup | | `internal/route/routes/context.go` | Route context helpers (only file) |
| `internal/route/rules` | Request/response rule processing | | `internal/route/rules` | Request/response rule processing |
| `internal/route/stream` | TCP/UDP stream proxying | | `internal/route/stream` | TCP/UDP stream proxying |
| `internal/route/provider` | Route discovery and loading | | `internal/route/provider` | Route discovery and loading |
| `internal/health/monitor` | Health checking | | `internal/health/monitor` | Health checking |
| `internal/idlewatcher` | Idle container management | | `internal/idlewatcher` | Idle container management |
| `internal/logging/accesslog` | Request logging | | `internal/logging/accesslog` | Request logging |
| `internal/homepage` | Dashboard integration | | `internal/homepage` | Dashboard integration |
| `github.com/yusing/goutils/errs` | Error handling | | `github.com/yusing/goutils/errs` | Error handling |
## Observability ## Observability
@@ -305,6 +313,18 @@ route := &route.Route{
} }
``` ```
### Route with Custom Bind Address
```go
route := &route.Route{
Alias: "myapp",
Scheme: route.SchemeHTTP,
Host: "myapp.local",
Bind: "192.168.1.100", // Bind to specific interface
Port: route.Port{Proxy: 80, Target: 3000, Listening: 8443},
}
```
### File Server Route ### File Server Route
```go ```go