Documentation
¶
Overview ¶
Package prefix provides small helpers for mounting net/http handlers under a configurable URL prefix.
Typical use-case: your service can run at root in local/dev, but under a sub-path in production (for example behind a reverse proxy).
The package exposes:
- ValidateRoutePrefix: optionally validates strict path-only configuration
- RouteURL: generates outbound references from prefix-free application paths
- NormalizeRoutePrefix: converts user-configurable values into a canonical prefix ("" or "/prefix")
- MountUnderPrefix: mounts handlers under that prefix and applies consistent redirect behavior for the bare prefix
- MountUnderPrefixWithOptions: same mount behavior with redirect status code overrides via Option values
Index ¶
- func MountUnderPrefix(h http.Handler, prefix string) http.Handler
- func MountUnderPrefixWithOptions(h http.Handler, prefix string, opts ...Option) http.Handler
- func NormalizeRoutePrefix(input string) string
- func PrefixForRequest(r *http.Request) string
- func Redirect(w http.ResponseWriter, r *http.Request, target string, status int)
- func RouteURL(prefix, target string) string
- func URLForRequest(r *http.Request, target string) string
- func ValidateRoutePrefix(prefix string) error
- type Option
- type Options
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func MountUnderPrefix ¶
MountUnderPrefix mounts h under route prefix and returns a handler that serves:
- prefix + "/" subtree via http.StripPrefix(prefix, h)
- bare prefix redirect to prefix + "/"
If prefix normalizes to "", MountUnderPrefix returns h unchanged. This includes empty, whitespace-only, root-like values, and full URLs without a path.
Redirect status is:
- 308 for GET and HEAD
- 307 for all other methods
Prefix normalization inside this function matches NormalizeRoutePrefix:
- full URLs use only their path
- leading slash is added when missing
- trailing slashes are removed
This function uses default redirect status codes (GET/HEAD: 308, others: 307). Mounted requests carry context for URLForRequest and Redirect. Handler Location headers are unchanged unless WithRedirectRewriting is enabled via MountUnderPrefixWithOptions, which also supports overriding redirect codes.
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
httpprefix "github.com/containeroo/httpkit/prefix"
)
func main() {
inner := http.NewServeMux()
inner.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "root")
})
inner.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "ok")
})
h := httpprefix.MountUnderPrefix(inner, "/app")
rec1 := httptest.NewRecorder()
h.ServeHTTP(rec1, httptest.NewRequest(http.MethodGet, "/app", nil))
fmt.Println(rec1.Code, rec1.Header().Get("Location"))
rec2 := httptest.NewRecorder()
h.ServeHTTP(rec2, httptest.NewRequest(http.MethodGet, "/app/health", nil))
fmt.Println(rec2.Code, rec2.Body.String())
}
Output: 308 /app/ 200 ok
func MountUnderPrefixWithOptions ¶
MountUnderPrefixWithOptions behaves like MountUnderPrefix and accepts optional redirect status code overrides and opt-in handler redirect rewriting.
If prefix normalizes to "", h is returned unchanged.
Allowed redirect codes are 301, 302, 303, 307, and 308. Invalid codes are replaced with defaults (GET/HEAD: 308, others: 307).
Example ¶
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
httpprefix "github.com/containeroo/httpkit/prefix"
)
func main() {
inner := http.NewServeMux()
inner.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "root")
})
h := httpprefix.MountUnderPrefixWithOptions(
inner,
"/app",
httpprefix.WithGetHeadRedirectCode(http.StatusMovedPermanently), // 301
httpprefix.WithOtherRedirectCode(http.StatusFound), // 302
)
rec1 := httptest.NewRecorder()
h.ServeHTTP(rec1, httptest.NewRequest(http.MethodGet, "/app", nil))
fmt.Println(rec1.Code, rec1.Header().Get("Location"))
rec2 := httptest.NewRecorder()
h.ServeHTTP(rec2, httptest.NewRequest(http.MethodPost, "/app", nil))
fmt.Println(rec2.Code, rec2.Header().Get("Location"))
}
Output: 301 /app/ 302 /app/
func NormalizeRoutePrefix ¶
NormalizeRoutePrefix converts user input into a canonical route prefix.
It accepts either a raw path (for example, "api", "/api", "/api///") or a full URL (for example, "https://example.com/api/").
Rules:
- Empty, whitespace-only, root-like values ("/", "///") return "".
- Trailing slashes are removed.
- A leading slash is added when missing.
- For full URLs, only the URL path is used.
The returned value is always either "" or a string beginning with "/".
Example ¶
package main
import (
"fmt"
httpprefix "github.com/containeroo/httpkit/prefix"
)
func main() {
fmt.Println(httpprefix.NormalizeRoutePrefix(""))
fmt.Println(httpprefix.NormalizeRoutePrefix("/"))
fmt.Println(httpprefix.NormalizeRoutePrefix("app"))
fmt.Println(httpprefix.NormalizeRoutePrefix("https://example.com/app/"))
}
Output: /app /app
func PrefixForRequest ¶ added in v0.0.2
PrefixForRequest returns the normalized prefix under which r is mounted.
func Redirect ¶
Redirect redirects to a prefix-free application path using the request's mount. It avoids a second prefix when redirect rewriting is enabled. Do not pass an already-generated URL; external URLs follow RouteURL's rules.
func RouteURL ¶
RouteURL generates an external URL reference for a prefix-free application path. The prefix is normalized with NormalizeRoutePrefix; callers needing strict configuration should first use ValidateRoutePrefix.
Local absolute paths gain the prefix, preserving their escaping, query, and fragment. For example, RouteURL("/app", "/pages/foo?q=bar#top") returns "/app/pages/foo?q=bar#top". RouteURL("/app", "/") returns "/app/".
Pass application-local paths, not URLs already generated by this function. No attempt is made to recognize an already-prefixed path: RouteURL("/pages", "/pages/foo") returns "/pages/pages/foo".
Malformed local paths, network-path references ("//host"), backslashes, line breaks, and dot segments fall back to the deployment root. Escaped path characters are checked after decoding as well. External URLs and relative references are returned unchanged. This function is not a general URL sanitizer or an authorization check for redirect destinations.
Example ¶
package main
import (
"fmt"
httpprefix "github.com/containeroo/httpkit/prefix"
)
func main() {
fmt.Println(httpprefix.RouteURL("/app", "/"))
fmt.Println(httpprefix.RouteURL("/app", "/pages/foo?q=bar#top"))
fmt.Println(httpprefix.RouteURL("", "/pages/foo"))
fmt.Println(httpprefix.RouteURL("/pages", "/pages/foo"))
}
Output: /app/ /app/pages/foo?q=bar#top /pages/foo /pages/pages/foo
func URLForRequest ¶
URLForRequest generates an external URL for a prefix-free application path. It uses the mount installed by MountUnderPrefix or MountUnderPrefixWithOptions. Unmounted requests use an empty prefix. Target handling follows RouteURL.
func ValidateRoutePrefix ¶
ValidateRoutePrefix validates strict path-only deployment configuration. It accepts an empty string or "/" for root deployment, and absolute paths containing non-empty segments of ASCII letters, digits, '-', '.', '_', or '~'. One trailing slash is allowed. Dot segments ("." and "..") are rejected. URLs, escapes, whitespace, queries, fragments, and repeated slashes are rejected.
Validate before calling NormalizeRoutePrefix when malformed configuration should be rejected rather than normalized permissively. Validation does not change the input, and the existing normalizer and mounting helpers remain permissive for backward compatibility.
Example ¶
package main
import (
"fmt"
httpprefix "github.com/containeroo/httpkit/prefix"
)
func main() {
input := "/app/"
if err := httpprefix.ValidateRoutePrefix(input); err != nil {
fmt.Println(err)
return
}
fmt.Println(httpprefix.NormalizeRoutePrefix(input))
fmt.Println(httpprefix.ValidateRoutePrefix("/app/../other") != nil)
}
Output: /app true
Types ¶
type Option ¶
type Option func(*Options)
Option mutates Options used by MountUnderPrefixWithOptions.
func WithGetHeadRedirectCode ¶
WithGetHeadRedirectCode sets the redirect status code for GET and HEAD requests.
func WithOptions ¶
WithOptions overwrites all options used by MountUnderPrefixWithOptions.
func WithOtherRedirectCode ¶
WithOtherRedirectCode sets the redirect status code for non-GET/HEAD requests.
func WithRedirectRewriting ¶
func WithRedirectRewriting() Option
WithRedirectRewriting prefixes application-local redirect destinations. Raw Location paths must be prefix-free; use Redirect for explicit redirects. External URLs and relative references are preserved using RouteURL's rules.
type Options ¶
type Options struct {
// GetHeadRedirectCode is used for redirects on GET and HEAD requests.
GetHeadRedirectCode int
// OtherRedirectCode is used for redirects on non-GET/HEAD requests.
OtherRedirectCode int
// RewriteRedirects prefixes application-local Location headers on redirects.
RewriteRedirects bool
}
Options configures mounting and redirects used by MountUnderPrefixWithOptions.