Warning
Deprecated: use github.com/containeroo/httpkit/prefix for new projects.
httpprefix remains available for existing consumers, but receives no new features.
httpprefix is a small Go package for mounting net/http handlers under a configurable route prefix.
Use it when the same service should work both:
- at root (
/) in local/dev - under a sub-path (for example
/app) in staging/production
go get github.com/containeroo/httpkit/prefixReplace:
import "github.com/containeroo/httpprefix"with:
import "github.com/containeroo/httpkit/prefix"The prefix API is equivalent; rename the imported package from httpprefix to prefix.
package main
import (
"io"
"log"
"net/http"
"github.com/containeroo/httpprefix"
)
func main() {
// Could come from env/config/flag: "", "/app", "app", or full URL.
prefix := httpprefix.NormalizeRoutePrefix("https://example.com/app/")
inner := http.NewServeMux()
inner.HandleFunc("GET /", func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, "ok")
})
inner.HandleFunc("GET /health", func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, "healthy")
})
h := httpprefix.MountUnderPrefix(inner, prefix)
log.Fatal(http.ListenAndServe(":8080", h))
}With prefix == "/app":
GET /app->308 Location: /app/POST /app->307 Location: /app/GET /app/health-> innerGET /health
Mounting supplies request context for URLForRequest(r, "/login"), which returns
"/app/login" when mounted at /app. Use Redirect(w, r, "/login", http.StatusFound)
for explicit application redirects.
To also rewrite redirect Location headers emitted by handlers such as ServeMux
and FileServer, enable the opt-in option:
h := httpprefix.MountUnderPrefixWithOptions(inner, "/app", httpprefix.WithRedirectRewriting())Existing mounts leave handler redirects unchanged unless this option is enabled.
Rewriting applies to 301, 302, 303, 307, and 308 responses. External URLs and
relative references are unchanged; unsafe local targets follow RouteURL's
deployment-root fallback. Use http.ResponseController for optional transport
capabilities through the response wrapper.
Pass prefix-free paths to these helpers and to raw Location headers. A path
starting with the mount name can still be an application path: mounting at
/pages turns /pages/foo into /pages/pages/foo. Redirect explicitly prevents
double rewriting; do not feed URLForRequest output into http.Redirect when
rewriting is enabled. Use Redirect instead.
h := httpprefix.MountUnderPrefixWithOptions(
inner,
"/app",
httpprefix.WithGetHeadRedirectCode(http.StatusMovedPermanently), // 301
httpprefix.WithOtherRedirectCode(http.StatusFound), // 302
)WithOptions(httpprefix.Options{...}) is also available when you want to set both values at once.
Optionally validate strict deployment configuration before normalization:
input := "/app/" // from a flag or environment variable
if err := httpprefix.ValidateRoutePrefix(input); err != nil {
return err
}
prefix := httpprefix.NormalizeRoutePrefix(input) // "/app"Accepts empty or / for root deployment, absolute paths with ASCII unreserved
characters (A-Z, a-z, 0-9, -, ., _, ~), and one trailing slash.
Rejects dot segments, empty segments, URLs, percent escapes, whitespace,
backslashes, query strings, and fragments. Validation uses no regular expressions.
Existing normalization and mounting remain permissive for compatibility.
Generate links, form actions, asset URLs, or redirect destinations using the same prefix as the mount:
httpprefix.RouteURL("/app", "/") // "/app/"
httpprefix.RouteURL("/app", "/pages/foo?q=1#top") // "/app/pages/foo?q=1#top"
httpprefix.RouteURL("", "/pages/foo") // "/pages/foo"The prefix is normalized. Pass prefix-free application paths; do not call
this helper again on a generated URL. A prefix can match an internal route name:
RouteURL("/pages", "/pages/foo") correctly returns /pages/pages/foo.
Local paths retain escaping, query strings, and fragments. Malformed local paths,
network-path references (//host), backslashes, line breaks, and decoded dot
segments fall back to the deployment root. External URLs and relative references
remain unchanged. This is not a general URL sanitizer or a redirect authorization
check; apply your application's destination policy separately.
Normalizes configuration input into a canonical prefix:
- returns
""for empty/root-like values (""," ","/","///") - trims trailing slashes (
"/app///"->"/app") - adds leading slash when needed (
"app"->"/app") - accepts full URLs and uses only path (
"https://x.io/app/"->"/app")
Returns a handler that:
- serves
hunderprefix + "/"viahttp.StripPrefix - redirects bare
prefixtoprefix + "/" - returns
hunchanged whenprefixnormalizes to""
Redirect status codes:
GET,HEAD->308 Permanent Redirect- all others ->
307 Temporary Redirect
Normalization inside mount matches NormalizeRoutePrefix:
- empty, whitespace-only, and root-like values return
hunchanged - full URLs use only their path
- full URLs without a path return
hunchanged - missing leading slash is added
- trailing slashes are removed
Same behavior as MountUnderPrefix, but lets you override redirect status codes.
Options.GetHeadRedirectCode: code forGETandHEADredirectsOptions.OtherRedirectCode: code for non-GET/HEADredirectsWithGetHeadRedirectCode(code int)WithOtherRedirectCode(code int)WithOptions(opts Options)to replace both values in one call
Allowed redirect codes are: 301, 302, 303, 307, 308.
If you pass anything else, defaults are used (308 for GET/HEAD, 307 otherwise).
- If the normalized prefix is empty, the original handler is returned unchanged.
- The package does not modify query strings when redirecting.
- Redirection is path-based and method-aware to preserve semantics for non-GET requests.
- Mounting does not rewrite redirects emitted by the inner handler or
ServeMux; applications must account for those separately. - Routing behavior relies on
net/httpServeMuxpath patterns.
Follow semantic versioning (vMAJOR.MINOR.PATCH).
- Breaking API change -> major bump
- Backward-compatible feature -> minor bump
- Fix/documentation-only -> patch
go test ./...This project is licensed under the Apache 2.0 License. See the LICENSE file for details.