Changed AddRoute and ListenAndServe methods to return errors instead of logging them internally and continuing. This allows callers to properly handle and propagate errors, improving error visibility and enabling better error management across the codebase. Updated all callers in fileserver, reverse_proxy, stream routes to handle these errors appropriately.
Entrypoint
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
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
- Domain-based route lookup with subdomain support
- Short link (
go/<alias>domain) handling - Middleware chain application
- Access logging for all requests
- Configurable not-found handling
- Per-domain route resolution
- Multi-protocol server management (HTTP/HTTPS/TCP/UDP)
- Route pool abstractions via
PoolLikeandRWPoolLikeinterfaces
Primary Consumers
- HTTP servers: Per-listen-addr servers dispatch requests to routes
- Route providers: Register routes via
AddRoute - Configuration layer: Validates and applies middleware/access-logging config
Non-goals
- Does not implement route discovery (delegates to providers)
- Does not handle TLS certificate management (delegates to autocert)
- Does not implement health checks (delegates to
internal/health/monitor)
Stability
Internal package with stable core interfaces. The Entrypoint interface is the public contract.
Public API
Entrypoint Interface
type Entrypoint interface {
// 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
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
type Config struct {
SupportProxyProtocol bool `json:"support_proxy_protocol"`
}
Architecture
Core Components
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
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
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
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:
// Adding a route
ep.AddRoute(route)
// Iterating all routes
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()
Configuration Surface
Config Source
Environment variables and YAML config file:
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 methods:
// 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
ep := entrypoint.NewEntrypoint(parent, &entrypoint.Config{
SupportProxyProtocol: false,
})
// Configure domain matching
ep.SetFindRouteDomains([]string{".example.com", "example.com"})
// Configure middleware
err := ep.SetMiddlewares([]map[string]any{
{"rate_limit": map[string]any{"requests_per_second": 100}},
})
if err != nil {
return err
}
// Configure access logging
err = ep.SetAccessLogger(parent, &accesslog.RequestLoggerConfig{
Path: "/var/log/godoxy/access.log",
})
if err != nil {
return err
}
Route Querying
// Iterate all routes including excluded
for r := range ep.IterRoutes {
log.Info().
Str("alias", r.Name()).
Str("provider", r.ProviderName()).
Bool("excluded", r.ShouldExclude()).
Msg("route")
}
// Get health info for all routes
healthMap := ep.GetHealthInfoSimple()
for alias, status := range healthMap {
log.Info().Str("alias", alias).Str("status", string(status)).Msg("health")
}
Route Addition
route := &route.Route{
Alias: "myapp",
Scheme: route.SchemeHTTP,
Host: "myapp",
Port: route.Port{Proxy: 80, Target: 3000},
}
ep.AddRoute(route)
Context Integration
Routes can access the entrypoint from request context:
// Set entrypoint in context
entrypoint.SetCtx(task, ep)
// Get entrypoint from context
if ep := entrypoint.FromCtx(r.Context()); ep != nil {
route, ok := ep.GetRoute("alias")
}
Testing Notes
- Benchmark tests in
entrypoint_benchmark_test.go - Integration tests in
entrypoint_test.go - Mock route pools for unit testing
- Short link tests in
shortlink_test.go