Routing & Service Mapping
Relevant source files
The following files were used as context for generating this wiki page:
Routing and Service Mapping in kamal-proxy constitutes the core logic for dispatching incoming HTTP requests to the appropriate backend services. This system relies on a combination of host-based and path-prefix-based matching to identify the target service, while supporting complex traffic shaping features such as redirects, rewrites, and dynamic configuration updates.
The architecture is layered, with the Router serving as the high-level orchestrator and entry point for the HTTP server, while the ServiceMap handles the technical indexing and lookups required to resolve a request to a specific Service instance.
Architecture and Core Components
The routing system is built around three primary data structures: the Router, the ServiceMap, and the Service.
- Router: The top-level component that implements
http.Handler. it manages the lifecycle of services, including deployment, removal, and state persistence. - ServiceMap: A specialized index that maps Host headers and URL path prefixes to services. It maintains a
requestServiceMapwhich organizes services by host for efficient lookup. - Service: Represents a logical application deployment, containing its own load balancer, configuration options, and routing rules.
Component Relationship
The following diagram illustrates how these components interact during a request's lifecycle.
This diagram shows the structural hierarchy where the Router holds the ServiceMap, which in turn indexes pathBindings that resolve to specific Services.
Sources: internal/server/router.go:49-62, internal/server/service_map.go:19-27
Service Resolution Logic
When a request arrives at the Router.ServeHTTP method, the proxy must determine which service should handle it. This resolution follows a specific precedence order:
- Host Matching: The proxy first looks for an exact match for the
Hostheader. - Wildcard Matching: If no exact match is found, it checks for wildcard subdomain matches (e.g.,
*.example.com). - Default Host: If no host-specific binding exists, it falls back to a "catch-all" or default host entry (empty string host).
- Path Prefix Matching: Within the matched host category, the proxy iterates through registered path prefixes. It uses longest-prefix matching to ensure that a request for
/api/v1/usersis routed to a service bound to/api/v1rather than one bound to/api.
Longest Prefix Implementation
The ServiceMap ensures longest-prefix matching by sorting bindings for every host by the length of their path prefix in descending order during the map update phase.
// internal/server/service_map.go:153-155
for _, bindings := range requestServiceMap {
slices.SortFunc(bindings, func(a, b *pathBinding) int { return len(b.pathPrefix) - len(a.pathPrefix) })
}
Sources: internal/server/service_map.go:136-161, internal/server/service_map.go:101-134
Request Flow Diagram
The flow shows the hierarchical decision tree used to resolve a request to a service, starting from host specificity down to path prefix matching.
Sources: internal/server/service_map.go:101-134, internal/server/router.go:338-348
Traffic Shaping: Redirects and Rewrites
kamal-proxy supports both static and dynamic traffic shaping rules. These allow the proxy to modify the request path or instruct the client to move to a different URL before the request ever reaches the backend target.
Static Rules
Static rules are defined during deployment via ServiceOptions. They are compiled into regular expressions anchored to the whole path to prevent partial matching.
| Feature | Description | File Reference |
|---|---|---|
| Redirects | Answers the client with a Location header and a status code (e.g., 301, 302). | redirect_rules.go |
| Rewrites | Changes the path the backend target receives without changing the client's URL. | redirect_rules.go |
| Canonical Host | Automatically redirects requests to a specified canonical host (e.g., www to apex). | router.go |
Sources: internal/server/redirect_rules.go:21-41, internal/server/router.go:189-210
Dynamic Redirects
The DynamicRedirectManager allows services to provide a RedirectsSource (a URL or local path). The proxy polls this source and applies the returned JSON map of redirects dynamically.
- Polling: Controlled by
RedirectsInterval(minimum 10 seconds). - Persistence: Last known good redirect maps are persisted in a state file to survive restarts.
- Consistency: ETags are used to prevent unnecessary re-processing of unchanged redirect maps.
Sources: internal/server/dynamic_redirects.go:12-25, internal/server/dynamic_redirects.go:94-150
Configuration and State Persistence
The Router is responsible for maintaining the "source of truth" for all active deployments. This state is serialized to a JSON file (typically state.json) whenever the configuration changes.
State Persistence Fields
When a service is deployed or updated, the following attributes are saved:
| Field | Type | Description |
|---|---|---|
Name | String | Unique identifier for the service. |
Hosts | []String | List of hostnames the service responds to. |
PathPrefixes | []String | List of path prefixes mapped to this service. |
TLSEnabled | Boolean | Whether HTTPS is required for this service. |
Targets | []String | List of active backend target URLs. |
On startup, the Router calls RestoreLastSavedState(), which reads the JSON file, decodes the services, and re-populates the ServiceMap. If the primary state file is corrupt, it attempts to recover from a .bak file.
Sources: internal/server/router.go:117-156, internal/server/router.go:501-523
Summary
The Routing and Service Mapping system provides a robust foundation for multi-tenant application hosting. By combining host matching, wildcard support, and longest-prefix path matching, kamal-proxy ensures precise request delivery. Integration with dynamic redirect management and atomic state persistence allows the system to remain resilient and flexible under frequent deployment changes.
Sources: internal/server/router.go, internal/server/service_map.go, ROADMAP.md