httpprefix

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

httpprefix

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

Install

go get github.com/containeroo/httpprefix

Quick Start

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 -> inner GET /health

Custom Redirect Codes

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.

API

NormalizeRoutePrefix(input string) string

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")
MountUnderPrefix(h http.Handler, prefix string) http.Handler

Returns a handler that:

  • serves h under prefix + "/" via http.StripPrefix
  • redirects bare prefix to prefix + "/"
  • returns h unchanged when prefix normalizes 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 h unchanged
  • full URLs use only their path
  • full URLs without a path return h unchanged
  • missing leading slash is added
  • trailing slashes are removed
MountUnderPrefixWithOptions(h http.Handler, prefix string, opts ...Option) http.Handler

Same behavior as MountUnderPrefix, but lets you override redirect status codes.

Options and Option
  • Options.GetHeadRedirectCode: code for GET and HEAD redirects
  • Options.OtherRedirectCode: code for non-GET/HEAD redirects
  • WithGetHeadRedirectCode(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).

Behavior Notes

  • 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.
  • Routing behavior relies on net/http ServeMux path patterns.

Versioning

Follow semantic versioning (vMAJOR.MINOR.PATCH).

  • Breaking API change -> major bump
  • Backward-compatible feature -> minor bump
  • Fix/documentation-only -> patch

Development

go test ./...

License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

Documentation

Overview

Package httpprefix 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:

  • 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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MountUnderPrefix

func MountUnderPrefix(h http.Handler, prefix string) http.Handler

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). Use MountUnderPrefixWithOptions to override redirect codes.

Example
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/containeroo/httpprefix"
)

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

func MountUnderPrefixWithOptions(h http.Handler, prefix string, opts ...Option) http.Handler

MountUnderPrefixWithOptions behaves like MountUnderPrefix and accepts optional redirect status code overrides.

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"

	"github.com/containeroo/httpprefix"
)

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

func NormalizeRoutePrefix(input string) string

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"

	"github.com/containeroo/httpprefix"
)

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

Types

type Option

type Option func(*Options)

Option mutates Options used by MountUnderPrefixWithOptions.

func WithGetHeadRedirectCode

func WithGetHeadRedirectCode(code int) Option

WithGetHeadRedirectCode sets the redirect status code for GET and HEAD requests.

func WithOptions

func WithOptions(opts Options) Option

WithOptions overwrites all redirect options used by MountUnderPrefixWithOptions.

func WithOtherRedirectCode

func WithOtherRedirectCode(code int) Option

WithOtherRedirectCode sets the redirect status code for non-GET/HEAD requests.

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
}

Options configures redirect status codes used by MountUnderPrefixWithOptions.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL