diff --git a/API.md b/API.md deleted file mode 100644 index 6730d62..0000000 --- a/API.md +++ /dev/null @@ -1,1490 +0,0 @@ -# API examples - -_Generated from annotated public API comments. Do not edit this file directly._ - -This guide covers functions and methods with repository examples. See [pkg.go.dev](https://pkg.go.dev/github.com/goforj/web) for the complete API, including exported types, configuration fields, constants, and variables. - -## API Index - -| Group | Functions | -|------:|:-----------| -| **Adapter** | [Adapter.Echo](#echoweb-adapter-echo) · [Adapter.Router](#echoweb-adapter-router) · [Adapter.ServeHTTP](#echoweb-adapter-servehttp) · [New](#echoweb-new) · [NewServer](#echoweb-newserver) · [Server.Router](#echoweb-server-router) · [Server.Serve](#echoweb-server-serve) · [Server.ServeHTTP](#echoweb-server-servehttp) · [UnwrapContext](#echoweb-unwrapcontext) · [UnwrapWebSocketConn](#echoweb-unwrapwebsocketconn) · [Wrap](#echoweb-wrap) | -| **Indexing** | [Run](#webindex-run) | -| **Middleware
Auth** | [BasicAuth](#webmiddleware-basicauth) · [BasicAuthWithConfig](#webmiddleware-basicauthwithconfig) · [CSRF](#webmiddleware-csrf) · [CSRFWithConfig](#webmiddleware-csrfwithconfig) · [CreateExtractors](#webmiddleware-createextractors) · [KeyAuth](#webmiddleware-keyauth) · [KeyAuthWithConfig](#webmiddleware-keyauthwithconfig) | -| **Middleware
Compression** | [Compress](#webmiddleware-compress) · [Decompress](#webmiddleware-decompress) · [DecompressWithConfig](#webmiddleware-decompresswithconfig) · [Gzip](#webmiddleware-gzip) · [GzipWithConfig](#webmiddleware-gzipwithconfig) | -| **Middleware
Method Override** | [MethodFromForm](#webmiddleware-methodfromform) · [MethodFromHeader](#webmiddleware-methodfromheader) · [MethodFromQuery](#webmiddleware-methodfromquery) · [MethodOverride](#webmiddleware-methodoverride) · [MethodOverrideWithConfig](#webmiddleware-methodoverridewithconfig) | -| **Middleware
Path Rewriting** | [AddTrailingSlash](#webmiddleware-addtrailingslash) · [AddTrailingSlashWithConfig](#webmiddleware-addtrailingslashwithconfig) · [RemoveTrailingSlash](#webmiddleware-removetrailingslash) · [RemoveTrailingSlashWithConfig](#webmiddleware-removetrailingslashwithconfig) · [Rewrite](#webmiddleware-rewrite) · [RewriteWithConfig](#webmiddleware-rewritewithconfig) | -| **Middleware
Payloads** | [BodyDump](#webmiddleware-bodydump) · [BodyDumpWithConfig](#webmiddleware-bodydumpwithconfig) · [BodyLimit](#webmiddleware-bodylimit) · [BodyLimitWithConfig](#webmiddleware-bodylimitwithconfig) · [ErrorBodyDump](#webmiddleware-errorbodydump) · [ErrorBodyDumpWithConfig](#webmiddleware-errorbodydumpwithconfig) | -| **Middleware
Proxying** | [NewRandomBalancer](#webmiddleware-newrandombalancer) · [NewRoundRobinBalancer](#webmiddleware-newroundrobinbalancer) · [Proxy](#webmiddleware-proxy) · [ProxyWithConfig](#webmiddleware-proxywithconfig) | -| **Middleware
Rate Limiting** | [NewRateLimiterMemoryStore](#webmiddleware-newratelimitermemorystore) · [NewRateLimiterMemoryStoreWithConfig](#webmiddleware-newratelimitermemorystorewithconfig) · [RateLimiter](#webmiddleware-ratelimiter) · [RateLimiterMemoryStore.Allow](#webmiddleware-ratelimitermemorystore-allow) · [RateLimiterWithConfig](#webmiddleware-ratelimiterwithconfig) | -| **Middleware
Redirects** | [HTTPSNonWWWRedirect](#webmiddleware-httpsnonwwwredirect) · [HTTPSNonWWWRedirectWithConfig](#webmiddleware-httpsnonwwwredirectwithconfig) · [HTTPSRedirect](#webmiddleware-httpsredirect) · [HTTPSRedirectWithConfig](#webmiddleware-httpsredirectwithconfig) · [HTTPSWWWRedirect](#webmiddleware-httpswwwredirect) · [HTTPSWWWRedirectWithConfig](#webmiddleware-httpswwwredirectwithconfig) · [NonWWWRedirect](#webmiddleware-nonwwwredirect) · [NonWWWRedirectWithConfig](#webmiddleware-nonwwwredirectwithconfig) · [WWWRedirect](#webmiddleware-wwwredirect) · [WWWRedirectWithConfig](#webmiddleware-wwwredirectwithconfig) | -| **Middleware
Reliability** | [Recover](#webmiddleware-recover) · [RecoverWithConfig](#webmiddleware-recoverwithconfig) | -| **Middleware
Request Lifecycle** | [ContextTimeout](#webmiddleware-contexttimeout) · [ContextTimeoutWithConfig](#webmiddleware-contexttimeoutwithconfig) · [DefaultSkipper](#webmiddleware-defaultskipper) · [RequestID](#webmiddleware-requestid) · [RequestIDWithConfig](#webmiddleware-requestidwithconfig) · [RequestLoggerWithConfig](#webmiddleware-requestloggerwithconfig) · [Timeout](#webmiddleware-timeout) · [TimeoutWithConfig](#webmiddleware-timeoutwithconfig) | -| **Middleware
Security** | [CORS](#webmiddleware-cors) · [CORSWithConfig](#webmiddleware-corswithconfig) · [Secure](#webmiddleware-secure) · [SecureWithConfig](#webmiddleware-securewithconfig) | -| **Middleware
Static Files** | [Static](#webmiddleware-static) · [StaticWithConfig](#webmiddleware-staticwithconfig) | -| **Prometheus** | [Default](#webprometheus-default) · [Handler](#webprometheus-handler) · [Metrics.Handler](#webprometheus-metrics-handler) · [Metrics.Middleware](#webprometheus-metrics-middleware) · [Middleware](#webprometheus-middleware) · [MustNew](#webprometheus-mustnew) · [New](#webprometheus-new) · [RunPushGatewayGatherer](#webprometheus-runpushgatewaygatherer) · [WriteGatheredMetrics](#webprometheus-writegatheredmetrics) | -| **Route Reporting** | [BuildRouteEntries](#buildrouteentries) · [RenderRouteTable](#renderroutetable) | -| **Routing** | [MountRouter](#mountrouter) · [NewRoute](#newroute) · [NewRouteGroup](#newroutegroup) · [NewWebSocketRoute](#newwebsocketroute) · [RegisterRoutes](#registerroutes) · [Route.Handler](#route-handler) · [Route.HandlerName](#route-handlername) · [Route.IsWebSocket](#route-iswebsocket) · [Route.Method](#route-method) · [Route.MiddlewareNames](#route-middlewarenames) · [Route.Middlewares](#route-middlewares) · [Route.Path](#route-path) · [Route.WebSocketHandler](#route-websockethandler) · [Route.WithMiddlewareNames](#route-withmiddlewarenames) · [RouteGroup.MiddlewareNames](#routegroup-middlewarenames) · [RouteGroup.Middlewares](#routegroup-middlewares) · [RouteGroup.RoutePrefix](#routegroup-routeprefix) · [RouteGroup.Routes](#routegroup-routes) · [RouteGroup.WithMiddlewareNames](#routegroup-withmiddlewarenames) | -| **Testing** | [NewContext](#webtest-newcontext) | - - -## Examples - -_Generated from public API comments and examples._ - -### Adapter - -#### echoweb.Adapter.Echo - -Echo returns the underlying Echo engine. - -```go -adapter := echoweb.New() -fmt.Println(adapter.Echo() != nil) -// true -``` - -#### echoweb.Adapter.Router - -Router returns the app-facing router contract. - -```go -adapter := echoweb.New() -fmt.Println(adapter.Router() != nil) -// true -``` - -#### echoweb.Adapter.ServeHTTP - -ServeHTTP exposes the adapter as a standard http.Handler. - -```go -adapter := echoweb.New() -adapter.Router().GET("/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }) -rr := httptest.NewRecorder() -req := httptest.NewRequest(http.MethodGet, "/healthz", nil) -adapter.ServeHTTP(rr, req) -fmt.Println(rr.Code) -// 204 -``` - -#### echoweb.New - -New creates a new Echo-backed web adapter. - -```go -adapter := echoweb.New() -fmt.Println(adapter.Router() != nil, adapter.Echo() != nil) -// true true -``` - -#### echoweb.NewServer - -NewServer creates an Echo-backed server from web route groups and mounts. - -```go -server, err := echoweb.NewServer(echoweb.ServerConfig{ - RouteGroups: []web.RouteGroup{ - web.NewRouteGroup("/api", []web.Route{ - web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }), - }), - }, -}) - -fmt.Println(err == nil, server.Router() != nil) -// true true -``` - -#### echoweb.Server.Router - -Router exposes the app-facing router contract. - -```go -server, _ := echoweb.NewServer(echoweb.ServerConfig{}) -fmt.Println(server.Router() != nil) -// true -``` - -#### echoweb.Server.Serve - -Serve starts the server and gracefully shuts it down when ctx is cancelled. - -```go -server, _ := echoweb.NewServer(echoweb.ServerConfig{Addr: "127.0.0.1:0"}) -ctx, cancel := context.WithCancel(context.Background()) -cancel() -fmt.Println(server.Serve(ctx) == nil) -// true -``` - -#### echoweb.Server.ServeHTTP - -ServeHTTP exposes the server as an http.Handler for tests and local probing. - -```go -server, _ := echoweb.NewServer(echoweb.ServerConfig{ - RouteGroups: []web.RouteGroup{ - web.NewRouteGroup("/api", []web.Route{ - web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }), - }), - }, -}) - -rr := httptest.NewRecorder() -req := httptest.NewRequest(http.MethodGet, "/api/healthz", nil) -server.ServeHTTP(rr, req) -fmt.Println(rr.Code) -// 204 -``` - -#### echoweb.UnwrapContext - -UnwrapContext returns the underlying Echo context when the web.Context came from this adapter. - -```go -adapter := echoweb.New() - -adapter.Router().GET("/healthz", func(c web.Context) error { - _, ok := echoweb.UnwrapContext(c) - fmt.Println(ok) - return c.NoContent(http.StatusOK) -}) - -rr := httptest.NewRecorder() -req := httptest.NewRequest(http.MethodGet, "/healthz", nil) -adapter.ServeHTTP(rr, req) -// true -``` - -#### echoweb.UnwrapWebSocketConn - -UnwrapWebSocketConn returns the underlying gorilla websocket connection. - -```go -_, ok := echoweb.UnwrapWebSocketConn(nil) -fmt.Println(ok) -// false -``` - -#### echoweb.Wrap - -Wrap exposes an existing Echo engine through the web.Router contract. - -```go -adapter := echoweb.Wrap(nil) -fmt.Println(adapter.Echo() != nil) -// true -``` - -### Indexing - -#### webindex.Run - -Run indexes API metadata from source and writes artifacts. - -```go -manifest, err := webindex.Run(context.Background(), webindex.IndexOptions{ - Root: ".", - OutPath: "webindex.json", -}) - -fmt.Println(err == nil, manifest.Version != "") -// true true -``` - -### Auth Middleware - -#### webmiddleware.BasicAuth - -BasicAuth returns basic auth middleware. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.BasicAuth(func(user, pass string, c web.Context) (bool, error) { - return user == "demo" && pass == "secret", nil -})) - -router.GET("/admin", func(c web.Context) error { - return c.Text(200, "welcome") -}) -``` - -#### webmiddleware.BasicAuthWithConfig - -BasicAuthWithConfig returns basic auth middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.BasicAuthWithConfig(webmiddleware.BasicAuthConfig{ - Realm: "Admin", - Validator: func(user, pass string, c web.Context) (bool, error) { - return user == "demo" && pass == "secret", nil - }, -})) - -router.GET("/admin", func(c web.Context) error { - return c.Text(200, "welcome") -}) -``` - -#### webmiddleware.CSRF - -CSRF enables token-based CSRF protection. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.CSRF()) - -router.POST("/settings", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.CSRFWithConfig - -CSRFWithConfig enables token-based CSRF protection with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.CSRFWithConfig(webmiddleware.CSRFConfig{ - CookieName: "_csrf", - TokenLookup: "header:X-CSRF-Token", -})) - -router.POST("/settings", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.CreateExtractors - -CreateExtractors creates extractors from a lookup definition. - -```go -extractors, err := webmiddleware.CreateExtractors("header:X-API-Key,query:token") -fmt.Println(err == nil, len(extractors)) -// true 2 -``` - -#### webmiddleware.KeyAuth - -KeyAuth returns key auth middleware. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.KeyAuth(func(key string, c web.Context) (bool, error) { - return key == "demo-key", nil -})) - -router.GET("/api/reports", func(c web.Context) error { - return c.JSON(200, map[string]any{"ready": true}) -}) -``` - -#### webmiddleware.KeyAuthWithConfig - -KeyAuthWithConfig returns key auth middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.KeyAuthWithConfig(webmiddleware.KeyAuthConfig{ - KeyLookup: "query:api_key", - Validator: func(key string, c web.Context) (bool, error) { - return key == "demo-key", nil - }, -})) - -router.GET("/api/reports", func(c web.Context) error { - return c.JSON(200, map[string]any{"ready": true}) -}) -``` - -### Compression Middleware - -#### webmiddleware.Compress - -Compress enables gzip response compression for clients that support it. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.Compress()) - -router.GET("/reports", func(c web.Context) error { - return c.Text(200, "large report response") -}) -``` - -#### webmiddleware.Decompress - -Decompress inflates gzip-encoded request bodies before handlers read them. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.Decompress()) - -router.POST("/ingest", func(c web.Context) error { - data, _ := io.ReadAll(c.Request().Body) - return c.JSON(200, map[string]int{"bytes": len(data)}) -}) -``` - -#### webmiddleware.DecompressWithConfig - -DecompressWithConfig inflates gzip-encoded request bodies with custom options. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.DecompressWithConfig(webmiddleware.DecompressConfig{ - Skipper: func(c web.Context) bool { - return c.Path() == "/webhooks/raw" - }, -})) - -router.POST("/ingest", func(c web.Context) error { - return c.NoContent(202) -}) -``` - -#### webmiddleware.Gzip - -Gzip enables gzip response compression for clients that support it. - -```go -router := echoweb.New().Router() - -router.GET("/feed", func(c web.Context) error { - return c.Text(200, "large feed response") -}, webmiddleware.Gzip()) -``` - -#### webmiddleware.GzipWithConfig - -GzipWithConfig enables gzip response compression with custom options. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.GzipWithConfig(webmiddleware.GzipConfig{ - MinLength: 1024, -})) -``` - -### Method Override Middleware - -#### webmiddleware.MethodFromForm - -MethodFromForm gets an override method from a form field. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ - Getter: webmiddleware.MethodFromForm("_method"), -})) -``` - -#### webmiddleware.MethodFromHeader - -MethodFromHeader gets an override method from a request header. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ - Getter: webmiddleware.MethodFromHeader("X-HTTP-Method-Override"), -})) -``` - -#### webmiddleware.MethodFromQuery - -MethodFromQuery gets an override method from a query parameter. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ - Getter: webmiddleware.MethodFromQuery("_method"), -})) -``` - -#### webmiddleware.MethodOverride - -MethodOverride returns method override middleware. - -```go -router := echoweb.New().Router() -router.Pre(webmiddleware.MethodOverride()) - -router.PATCH("/articles/:id", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.MethodOverrideWithConfig - -MethodOverrideWithConfig returns method override middleware with config. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ - Getter: webmiddleware.MethodFromQuery("_method"), -})) - -router.DELETE("/articles/:id", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -### Path Rewriting Middleware - -#### webmiddleware.AddTrailingSlash - -AddTrailingSlash adds a trailing slash to the request path. - -```go -router := echoweb.New().Router() -router.Pre(webmiddleware.AddTrailingSlash()) - -router.GET("/docs/", func(c web.Context) error { - return c.Text(200, "docs") -}) -``` - -#### webmiddleware.AddTrailingSlashWithConfig - -AddTrailingSlashWithConfig returns trailing-slash middleware with config. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.AddTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{ - RedirectCode: 308, -})) - -router.GET("/docs/", func(c web.Context) error { - return c.Text(200, "docs") -}) -``` - -#### webmiddleware.RemoveTrailingSlash - -RemoveTrailingSlash removes the trailing slash from the request path. - -```go -router := echoweb.New().Router() -router.Pre(webmiddleware.RemoveTrailingSlash()) - -router.GET("/docs", func(c web.Context) error { - return c.Text(200, "docs") -}) -``` - -#### webmiddleware.RemoveTrailingSlashWithConfig - -RemoveTrailingSlashWithConfig returns remove-trailing-slash middleware with config. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.RemoveTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{ - RedirectCode: 308, -})) - -router.GET("/docs", func(c web.Context) error { - return c.Text(200, "docs") -}) -``` - -#### webmiddleware.Rewrite - -Rewrite rewrites the request path using wildcard rules. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.Rewrite(map[string]string{ - "/old/*": "/new/$1", -})) - -router.GET("/new/:name", func(c web.Context) error { - return c.Text(200, c.Param("name")) -}) -``` - -#### webmiddleware.RewriteWithConfig - -RewriteWithConfig rewrites the request path using wildcard and regex rules. - -```go -router := echoweb.New().Router() - -router.Pre(webmiddleware.RewriteWithConfig(webmiddleware.RewriteConfig{ - Rules: map[string]string{"/old/*": "/v2/$1"}, -})) - -router.GET("/v2/:name", func(c web.Context) error { - return c.Text(200, c.Param("name")) -}) -``` - -### Payloads Middleware - -#### webmiddleware.BodyDump - -BodyDump captures request and response payloads. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.BodyDump(func(c web.Context, reqBody, resBody []byte) { - log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody)) -})) - -router.POST("/webhooks", func(c web.Context) error { - return c.JSON(202, map[string]any{"queued": true}) -}) -``` - -#### webmiddleware.BodyDumpWithConfig - -BodyDumpWithConfig captures request and response payloads with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.BodyDumpWithConfig(webmiddleware.BodyDumpConfig{ - Skipper: func(c web.Context) bool { - return c.Path() == "/healthz" - }, - Handler: func(c web.Context, reqBody, resBody []byte) { - log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody)) - }, -})) -``` - -#### webmiddleware.BodyLimit - -BodyLimit returns middleware that limits request body size. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.BodyLimit("2MB")) - -router.POST("/uploads", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.BodyLimitWithConfig - -BodyLimitWithConfig returns body limit middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.BodyLimitWithConfig(webmiddleware.BodyLimitConfig{ - Limit: "10MB", -})) - -router.POST("/imports", func(c web.Context) error { - return c.NoContent(202) -}) -``` - -#### webmiddleware.ErrorBodyDump - -ErrorBodyDump captures response bodies for non-2xx and non-3xx responses. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.ErrorBodyDump(func(c web.Context, status int, body []byte) { - log.Printf("%s %s failed with %d", c.Method(), c.URI(), status) -})) - -router.GET("/reports/:id", func(c web.Context) error { - return c.Text(404, "report not found") -}) -``` - -#### webmiddleware.ErrorBodyDumpWithConfig - -ErrorBodyDumpWithConfig captures response bodies for non-success responses with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.ErrorBodyDumpWithConfig(webmiddleware.ErrorBodyDumpConfig{ - Skipper: func(c web.Context) bool { - return c.Path() == "/healthz" - }, - Handler: func(c web.Context, status int, body []byte) { - log.Printf("%s %s failed with %d", c.Method(), c.URI(), status) - }, -})) -``` - -### Proxying Middleware - -#### webmiddleware.NewRandomBalancer - -NewRandomBalancer creates a random proxy balancer. - -```go -target, _ := url.Parse("http://localhost:8080") -balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) -fmt.Println(balancer.Next(nil).URL.Host) -// localhost:8080 -``` - -#### webmiddleware.NewRoundRobinBalancer - -NewRoundRobinBalancer creates a round-robin proxy balancer. - -```go -target, _ := url.Parse("http://localhost:8080") -balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) -fmt.Println(balancer.Next(nil).URL.Host) -// localhost:8080 -``` - -#### webmiddleware.Proxy - -Proxy creates a proxy middleware. - -```go -target, _ := url.Parse("http://localhost:8080") -balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) - -router := echoweb.New().Router() -router.Use(webmiddleware.Proxy(balancer)) -``` - -#### webmiddleware.ProxyWithConfig - -ProxyWithConfig creates a proxy middleware with config. - -```go -target, _ := url.Parse("http://localhost:8080") -balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) - -router := echoweb.New().Router() - -router.Use(webmiddleware.ProxyWithConfig(webmiddleware.ProxyConfig{ - Balancer: balancer, - Rewrite: map[string]string{ - "/api/*": "/$1", - }, -})) -``` - -### Rate Limiting Middleware - -#### webmiddleware.NewRateLimiterMemoryStore - -NewRateLimiterMemoryStore creates an in-memory rate limiter store. - -```go -store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) -allowed1, _ := store.Allow("192.0.2.1") -allowed2, _ := store.Allow("192.0.2.1") -fmt.Println(allowed1, allowed2) -// true false -``` - -#### webmiddleware.NewRateLimiterMemoryStoreWithConfig - -NewRateLimiterMemoryStoreWithConfig creates an in-memory rate limiter store with config. - -```go -store := webmiddleware.NewRateLimiterMemoryStoreWithConfig(webmiddleware.RateLimiterMemoryStoreConfig{Rate: rate.Every(time.Second)}) -allowed, _ := store.Allow("192.0.2.1") -fmt.Println(allowed) -// true -``` - -#### webmiddleware.RateLimiter - -RateLimiter creates a rate limiting middleware. - -```go -store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) - -router := echoweb.New().Router() -router.Use(webmiddleware.RateLimiter(store)) - -router.POST("/api/messages", func(c web.Context) error { - return c.NoContent(202) -}) -``` - -#### webmiddleware.RateLimiterMemoryStore.Allow - -Allow checks whether the given identifier is allowed through. - -```go -store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) -allowed, err := store.Allow("127.0.0.1") -fmt.Println(err == nil, allowed) -// true true -``` - -#### webmiddleware.RateLimiterWithConfig - -RateLimiterWithConfig creates a rate limiting middleware with config. - -```go -store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) - -router := echoweb.New().Router() - -router.Use(webmiddleware.RateLimiterWithConfig(webmiddleware.RateLimiterConfig{ - Store: store, - IdentifierExtractor: func(c web.Context) (string, error) { - return c.Header("X-Account-ID"), nil - }, -})) -``` - -### Redirects Middleware - -#### webmiddleware.HTTPSNonWWWRedirect - -HTTPSNonWWWRedirect redirects to https without www. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.HTTPSNonWWWRedirect()) -``` - -#### webmiddleware.HTTPSNonWWWRedirectWithConfig - -HTTPSNonWWWRedirectWithConfig returns HTTPS non-WWW redirect middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.HTTPSNonWWWRedirectWithConfig(webmiddleware.RedirectConfig{ - Code: 307, -})) -``` - -#### webmiddleware.HTTPSRedirect - -HTTPSRedirect redirects http requests to https. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.HTTPSRedirect()) - -router.GET("/docs", func(c web.Context) error { - return c.Text(200, "docs") -}) -``` - -#### webmiddleware.HTTPSRedirectWithConfig - -HTTPSRedirectWithConfig returns HTTPS redirect middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.HTTPSRedirectWithConfig(webmiddleware.RedirectConfig{ - Code: 307, -})) -``` - -#### webmiddleware.HTTPSWWWRedirect - -HTTPSWWWRedirect redirects to https + www. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.HTTPSWWWRedirect()) -``` - -#### webmiddleware.HTTPSWWWRedirectWithConfig - -HTTPSWWWRedirectWithConfig returns HTTPS+WWW redirect middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.HTTPSWWWRedirectWithConfig(webmiddleware.RedirectConfig{ - Code: 307, -})) -``` - -#### webmiddleware.NonWWWRedirect - -NonWWWRedirect redirects to the non-www host. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.NonWWWRedirect()) -``` - -#### webmiddleware.NonWWWRedirectWithConfig - -NonWWWRedirectWithConfig returns non-WWW redirect middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.NonWWWRedirectWithConfig(webmiddleware.RedirectConfig{ - Code: 307, -})) -``` - -#### webmiddleware.WWWRedirect - -WWWRedirect redirects to the www host. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.WWWRedirect()) -``` - -#### webmiddleware.WWWRedirectWithConfig - -WWWRedirectWithConfig returns WWW redirect middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.WWWRedirectWithConfig(webmiddleware.RedirectConfig{ - Code: 307, -})) -``` - -### Reliability Middleware - -#### webmiddleware.Recover - -Recover returns middleware that recovers panics from the handler chain. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.Recover()) - -router.GET("/panic", func(c web.Context) error { - panic("boom") -}) -``` - -#### webmiddleware.RecoverWithConfig - -RecoverWithConfig returns recover middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.RecoverWithConfig(webmiddleware.RecoverConfig{ - DisableStack: true, - HandleError: func(c web.Context, err error, stack []byte) error { - return c.JSON(500, map[string]any{"error": "internal server error"}) - }, -})) -``` - -### Request Lifecycle Middleware - -#### webmiddleware.ContextTimeout - -ContextTimeout sets a timeout on the request context. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.ContextTimeout(2 * time.Second)) - -router.GET("/reports", func(c web.Context) error { - return c.JSON(200, map[string]any{"ready": true}) -}) -``` - -#### webmiddleware.ContextTimeoutWithConfig - -ContextTimeoutWithConfig sets a timeout on the request context with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.ContextTimeoutWithConfig(webmiddleware.ContextTimeoutConfig{ - Timeout: time.Second, -})) -``` - -#### webmiddleware.DefaultSkipper - -DefaultSkipper always runs the middleware. - -```go -fmt.Println(webmiddleware.DefaultSkipper(nil)) -// false -``` - -#### webmiddleware.RequestID - -RequestID returns middleware that sets a request id header and context value. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.RequestID()) - -router.GET("/healthz", func(c web.Context) error { - return c.JSON(200, map[string]any{ - "request_id": c.Get("request_id"), - }) -}) -``` - -#### webmiddleware.RequestIDWithConfig - -RequestIDWithConfig returns RequestID middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.RequestIDWithConfig(webmiddleware.RequestIDConfig{ - TargetHeader: "X-Correlation-ID", - ContextKey: "correlation_id", -})) -``` - -#### webmiddleware.RequestLoggerWithConfig - -RequestLoggerWithConfig returns request logger middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.RequestLoggerWithConfig(webmiddleware.RequestLoggerConfig{ - LogValuesFunc: func(c web.Context, values webmiddleware.RequestLoggerValues) error { - log.Printf("%s %s %d %s", values.Method, values.URI, values.Status, values.Latency) - return nil - }, -})) - -router.GET("/users/:id", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.Timeout - -Timeout returns a response-timeout middleware. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.Timeout()) - -router.GET("/healthz", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.TimeoutWithConfig - -TimeoutWithConfig returns a response-timeout middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.TimeoutWithConfig(webmiddleware.TimeoutConfig{ - Timeout: time.Second, - ErrorMessage: "request timed out", -})) -``` - -### Security Middleware - -#### webmiddleware.CORS - -CORS returns Cross-Origin Resource Sharing middleware. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.CORS()) - -router.GET("/api/healthz", func(c web.Context) error { - return c.JSON(200, map[string]any{"ok": true}) -}) -``` - -#### webmiddleware.CORSWithConfig - -CORSWithConfig returns CORS middleware with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.CORSWithConfig(webmiddleware.CORSConfig{ - AllowOrigins: []string{"https://app.example.com"}, - AllowMethods: []string{"GET", "POST", "PATCH"}, -})) - -router.GET("/api/healthz", func(c web.Context) error { - return c.JSON(200, map[string]any{"ok": true}) -}) -``` - -#### webmiddleware.Secure - -Secure sets security-oriented response headers. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.Secure()) - -router.GET("/", func(c web.Context) error { - return c.Text(200, "home") -}) -``` - -#### webmiddleware.SecureWithConfig - -SecureWithConfig sets security-oriented response headers with config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.SecureWithConfig(webmiddleware.SecureConfig{ - ReferrerPolicy: "same-origin", - ContentSecurityPolicy: "default-src 'self'", -})) -``` - -### Static Files Middleware - -#### webmiddleware.Static - -Static serves static content from the provided root. - -```go -router := echoweb.New().Router() -router.Use(webmiddleware.Static("public")) - -router.GET("/healthz", func(c web.Context) error { - return c.NoContent(204) -}) -``` - -#### webmiddleware.StaticWithConfig - -StaticWithConfig serves static content using config. - -```go -router := echoweb.New().Router() - -router.Use(webmiddleware.StaticWithConfig(webmiddleware.StaticConfig{ - Root: "public", - HTML5: true, -})) -``` - -### Prometheus - -#### webprometheus.Default - -Default returns the package-level Prometheus metrics instance. - -```go -fmt.Println(webprometheus.Default() == webprometheus.Default()) -// true -``` - -#### webprometheus.Handler - -Handler returns the package-level Prometheus scrape handler. - -```go -registry := prometheus.NewRegistry() -counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"}) -registry.MustRegister(counter) -counter.Inc() -metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry}) -recorder := httptest.NewRecorder() -ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil) -_ = metrics.Handler()(ctx) -fmt.Println(strings.Contains(recorder.Body.String(), "demo_total")) -// true -``` - -#### webprometheus.Metrics.Handler - -Handler exposes the configured Prometheus metrics as a web.Handler. - -```go -registry := prometheus.NewRegistry() -counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"}) -registry.MustRegister(counter) -counter.Inc() -metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry}) -recorder := httptest.NewRecorder() -ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil) -_ = metrics.Handler()(ctx) -fmt.Println(strings.Contains(recorder.Body.String(), "demo_total")) -// true -``` - -#### webprometheus.Metrics.Middleware - -Middleware records Prometheus metrics for each request. - -```go -registry := prometheus.NewRegistry() -metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"}) -handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) }) -ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil) -_ = handler(ctx) -out := &bytes.Buffer{} -_ = webprometheus.WriteGatheredMetrics(out, registry) -fmt.Println(strings.Contains(out.String(), "example_requests_total")) -// true -``` - -#### webprometheus.Middleware - -Middleware returns the package-level Prometheus middleware. - -```go -registry := prometheus.NewRegistry() -metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"}) -handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) }) -ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil) -_ = handler(ctx) -out := &bytes.Buffer{} -_ = webprometheus.WriteGatheredMetrics(out, registry) -fmt.Println(strings.Contains(out.String(), "example_requests_total")) -// true -``` - -#### webprometheus.MustNew - -MustNew creates a Metrics instance and panics on registration errors. - -```go -metrics := webprometheus.MustNew(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: prometheus.NewRegistry()}) -fmt.Println(metrics != nil) -// true -``` - -#### webprometheus.New - -New creates a Metrics instance backed by Prometheus collectors. - -```go -metrics, err := webprometheus.New(webprometheus.Config{Namespace: "app"}) -_ = metrics -fmt.Println(err == nil) -// true -``` - -#### webprometheus.RunPushGatewayGatherer - -RunPushGatewayGatherer starts pushing collected metrics until the context finishes. - -```go -err := webprometheus.RunPushGatewayGatherer(context.Background(), webprometheus.PushGatewayConfig{}) -fmt.Println(err != nil) -// true -``` - -#### webprometheus.WriteGatheredMetrics - -WriteGatheredMetrics gathers collected metrics and writes them to the given writer. - -```go -var buf bytes.Buffer -err := webprometheus.WriteGatheredMetrics(&buf, prometheus.NewRegistry()) -fmt.Println(err == nil) -// true -``` - -### Route Reporting - -#### BuildRouteEntries - -BuildRouteEntries builds a sorted slice of route entries from registered groups and extra entries. - -```go -entries := web.BuildRouteEntries([]web.RouteGroup{ - web.NewRouteGroup("/api", []web.Route{ - web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), - }), -}) -fmt.Println(entries[0].Path, entries[0].Methods[0]) -// /api/healthz GET -``` - -#### RenderRouteTable - -RenderRouteTable renders a route table using simple ASCII borders and ANSI colors. - -```go -table := web.RenderRouteTable([]web.RouteEntry{{ - Path: "/api/healthz", - Handler: "monitoring.Healthz", - Methods: []string{"GET"}, -}}) -fmt.Println(strings.Contains(table, "/api/healthz")) -// true -``` - -### Routing - -#### MountRouter - -MountRouter applies mount-style router configuration in declaration order. - -```go -adapter := echoweb.New() - -err := web.MountRouter(adapter.Router(), []web.RouterMount{ - func(r web.Router) error { - r.GET("/healthz", func(c web.Context) error { return nil }) - return nil - }, -}) - -fmt.Println(err == nil) -// true -``` - -#### NewRoute - -NewRoute creates a new route using the app-facing web handler contract directly. - -```go -route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { - return c.NoContent(http.StatusOK) -}) - -fmt.Println(route.Method(), route.Path()) -// GET /healthz -``` - -#### NewRouteGroup - -NewRouteGroup wraps routes and their accompanied web middleware. - -```go -group := web.NewRouteGroup("/api", []web.Route{ - web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), -}) - -fmt.Println(group.RoutePrefix(), len(group.Routes())) -// /api 1 -``` - -#### NewWebSocketRoute - -NewWebSocketRoute creates a websocket route using the app-facing websocket handler contract. - -```go -route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { - return nil -}) - -fmt.Println(route.IsWebSocket()) -// true -``` - -#### RegisterRoutes - -RegisterRoutes registers route groups onto a router. - -```go -adapter := echoweb.New() - -groups := []web.RouteGroup{ - web.NewRouteGroup("/api", []web.Route{ - web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), - }), -} - -err := web.RegisterRoutes(adapter.Router(), groups) -fmt.Println(err == nil) -// true -``` - -#### Route.Handler - -Handler returns the route handler. - -```go -route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { - return c.NoContent(http.StatusCreated) -}) - -ctx := webtest.NewContext(nil, nil, "/healthz", nil) -_ = route.Handler()(ctx) -fmt.Println(ctx.StatusCode()) -// 201 -``` - -#### Route.HandlerName - -HandlerName returns the original handler name for route reporting. - -```go -route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }) -fmt.Println(route.HandlerName() != "") -// true -``` - -#### Route.IsWebSocket - -IsWebSocket reports whether this route upgrades to a websocket connection. - -```go -route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { return nil }) -fmt.Println(route.IsWebSocket()) -// true -``` - -#### Route.Method - -Method returns the HTTP method. - -```go -route := web.NewRoute(http.MethodPost, "/users", func(c web.Context) error { return nil }) -fmt.Println(route.Method()) -// POST -``` - -#### Route.MiddlewareNames - -MiddlewareNames returns original middleware names for route reporting. - -```go -route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth") -fmt.Println(route.MiddlewareNames()[0]) -// auth -``` - -#### Route.Middlewares - -Middlewares returns the route middleware slice. - -```go -route := web.NewRoute( - -http.MethodGet, -"/healthz", -func(c web.Context) error { return nil }, -func(next web.Handler) web.Handler { return next }, - -) -fmt.Println(len(route.Middlewares())) -// 1 -``` - -#### Route.Path - -Path returns the path of the route. - -```go -route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }) -fmt.Println(route.Path()) -// /healthz -``` - -#### Route.WebSocketHandler - -WebSocketHandler returns the websocket route handler. - -```go -route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { - c.Set("ready", true) - return nil -}) - -ctx := webtest.NewContext(nil, nil, "/ws", nil) -err := route.WebSocketHandler()(ctx, nil) -fmt.Println(err == nil, ctx.Get("ready")) -// true true -``` - -#### Route.WithMiddlewareNames - -WithMiddlewareNames attaches reporting-only middleware names to the route. - -```go -route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth", "trace") -fmt.Println(len(route.MiddlewareNames())) -// 2 -``` - -#### RouteGroup.MiddlewareNames - -MiddlewareNames returns original middleware names for route reporting. - -```go -group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth") -fmt.Println(group.MiddlewareNames()[0]) -// auth -``` - -#### RouteGroup.Middlewares - -Middlewares returns the middleware slice for the group. - -```go -group := web.NewRouteGroup("/api", nil, func(next web.Handler) web.Handler { return next }) -fmt.Println(len(group.Middlewares())) -// 1 -``` - -#### RouteGroup.RoutePrefix - -RoutePrefix returns the group prefix. - -```go -group := web.NewRouteGroup("/api", nil) -fmt.Println(group.RoutePrefix()) -// /api -``` - -#### RouteGroup.Routes - -Routes returns the routes in the group. - -```go -group := web.NewRouteGroup("/api", []web.Route{ - web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), -}) - -fmt.Println(len(group.Routes())) -// 1 -``` - -#### RouteGroup.WithMiddlewareNames - -WithMiddlewareNames attaches reporting-only middleware names to the group. - -```go -group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth", "trace") -fmt.Println(len(group.MiddlewareNames())) -// 2 -``` - -### Testing - -#### webtest.NewContext - -NewContext creates a new test context around the provided request/recorder pair. - -```go -req := httptest.NewRequest(http.MethodGet, "/users/42?expand=roles", nil) -ctx := webtest.NewContext(req, nil, "/users/:id", webtest.PathParams{"id": "42"}) -fmt.Println(ctx.Param("id"), ctx.Query("expand")) -// 42 roles -``` diff --git a/README.md b/README.md index 1c17858..eacec4a 100644 --- a/README.md +++ b/README.md @@ -274,10 +274,1491 @@ adapter.Echo().IPExtractor = echo.ExtractIPDirect() Behind a trusted proxy, configure `echo.ExtractIPFromXFFHeader` or `echo.ExtractIPFromRealIPHeader` with trust options that match the deployment, and ensure the edge proxy removes client-supplied forwarding headers before adding its own. -## Reference +## API -The [complete API documentation](https://pkg.go.dev/github.com/goforj/web) includes every exported type, field, function, and method. +## API Index -The [generated API examples](API.md) collect the functions and methods annotated with runnable examples in this repository. +| Group | Functions | +|------:|:-----------| +| **Adapter** | [Adapter.Echo](#echoweb-adapter-echo) · [Adapter.Router](#echoweb-adapter-router) · [Adapter.ServeHTTP](#echoweb-adapter-servehttp) · [New](#echoweb-new) · [NewServer](#echoweb-newserver) · [Server.Router](#echoweb-server-router) · [Server.Serve](#echoweb-server-serve) · [Server.ServeHTTP](#echoweb-server-servehttp) · [UnwrapContext](#echoweb-unwrapcontext) · [UnwrapWebSocketConn](#echoweb-unwrapwebsocketconn) · [Wrap](#echoweb-wrap) | +| **Indexing** | [Run](#webindex-run) | +| **Middleware
Auth** | [BasicAuth](#webmiddleware-basicauth) · [BasicAuthWithConfig](#webmiddleware-basicauthwithconfig) · [CSRF](#webmiddleware-csrf) · [CSRFWithConfig](#webmiddleware-csrfwithconfig) · [CreateExtractors](#webmiddleware-createextractors) · [KeyAuth](#webmiddleware-keyauth) · [KeyAuthWithConfig](#webmiddleware-keyauthwithconfig) | +| **Middleware
Compression** | [Compress](#webmiddleware-compress) · [Decompress](#webmiddleware-decompress) · [DecompressWithConfig](#webmiddleware-decompresswithconfig) · [Gzip](#webmiddleware-gzip) · [GzipWithConfig](#webmiddleware-gzipwithconfig) | +| **Middleware
Method Override** | [MethodFromForm](#webmiddleware-methodfromform) · [MethodFromHeader](#webmiddleware-methodfromheader) · [MethodFromQuery](#webmiddleware-methodfromquery) · [MethodOverride](#webmiddleware-methodoverride) · [MethodOverrideWithConfig](#webmiddleware-methodoverridewithconfig) | +| **Middleware
Path Rewriting** | [AddTrailingSlash](#webmiddleware-addtrailingslash) · [AddTrailingSlashWithConfig](#webmiddleware-addtrailingslashwithconfig) · [RemoveTrailingSlash](#webmiddleware-removetrailingslash) · [RemoveTrailingSlashWithConfig](#webmiddleware-removetrailingslashwithconfig) · [Rewrite](#webmiddleware-rewrite) · [RewriteWithConfig](#webmiddleware-rewritewithconfig) | +| **Middleware
Payloads** | [BodyDump](#webmiddleware-bodydump) · [BodyDumpWithConfig](#webmiddleware-bodydumpwithconfig) · [BodyLimit](#webmiddleware-bodylimit) · [BodyLimitWithConfig](#webmiddleware-bodylimitwithconfig) · [ErrorBodyDump](#webmiddleware-errorbodydump) · [ErrorBodyDumpWithConfig](#webmiddleware-errorbodydumpwithconfig) | +| **Middleware
Proxying** | [NewRandomBalancer](#webmiddleware-newrandombalancer) · [NewRoundRobinBalancer](#webmiddleware-newroundrobinbalancer) · [Proxy](#webmiddleware-proxy) · [ProxyWithConfig](#webmiddleware-proxywithconfig) | +| **Middleware
Rate Limiting** | [NewRateLimiterMemoryStore](#webmiddleware-newratelimitermemorystore) · [NewRateLimiterMemoryStoreWithConfig](#webmiddleware-newratelimitermemorystorewithconfig) · [RateLimiter](#webmiddleware-ratelimiter) · [RateLimiterMemoryStore.Allow](#webmiddleware-ratelimitermemorystore-allow) · [RateLimiterWithConfig](#webmiddleware-ratelimiterwithconfig) | +| **Middleware
Redirects** | [HTTPSNonWWWRedirect](#webmiddleware-httpsnonwwwredirect) · [HTTPSNonWWWRedirectWithConfig](#webmiddleware-httpsnonwwwredirectwithconfig) · [HTTPSRedirect](#webmiddleware-httpsredirect) · [HTTPSRedirectWithConfig](#webmiddleware-httpsredirectwithconfig) · [HTTPSWWWRedirect](#webmiddleware-httpswwwredirect) · [HTTPSWWWRedirectWithConfig](#webmiddleware-httpswwwredirectwithconfig) · [NonWWWRedirect](#webmiddleware-nonwwwredirect) · [NonWWWRedirectWithConfig](#webmiddleware-nonwwwredirectwithconfig) · [WWWRedirect](#webmiddleware-wwwredirect) · [WWWRedirectWithConfig](#webmiddleware-wwwredirectwithconfig) | +| **Middleware
Reliability** | [Recover](#webmiddleware-recover) · [RecoverWithConfig](#webmiddleware-recoverwithconfig) | +| **Middleware
Request Lifecycle** | [ContextTimeout](#webmiddleware-contexttimeout) · [ContextTimeoutWithConfig](#webmiddleware-contexttimeoutwithconfig) · [DefaultSkipper](#webmiddleware-defaultskipper) · [RequestID](#webmiddleware-requestid) · [RequestIDWithConfig](#webmiddleware-requestidwithconfig) · [RequestLoggerWithConfig](#webmiddleware-requestloggerwithconfig) · [Timeout](#webmiddleware-timeout) · [TimeoutWithConfig](#webmiddleware-timeoutwithconfig) | +| **Middleware
Security** | [CORS](#webmiddleware-cors) · [CORSWithConfig](#webmiddleware-corswithconfig) · [Secure](#webmiddleware-secure) · [SecureWithConfig](#webmiddleware-securewithconfig) | +| **Middleware
Static Files** | [Static](#webmiddleware-static) · [StaticWithConfig](#webmiddleware-staticwithconfig) | +| **Prometheus** | [Default](#webprometheus-default) · [Handler](#webprometheus-handler) · [Metrics.Handler](#webprometheus-metrics-handler) · [Metrics.Middleware](#webprometheus-metrics-middleware) · [Middleware](#webprometheus-middleware) · [MustNew](#webprometheus-mustnew) · [New](#webprometheus-new) · [RunPushGatewayGatherer](#webprometheus-runpushgatewaygatherer) · [WriteGatheredMetrics](#webprometheus-writegatheredmetrics) | +| **Route Reporting** | [BuildRouteEntries](#buildrouteentries) · [RenderRouteTable](#renderroutetable) | +| **Routing** | [MountRouter](#mountrouter) · [NewRoute](#newroute) · [NewRouteGroup](#newroutegroup) · [NewWebSocketRoute](#newwebsocketroute) · [RegisterRoutes](#registerroutes) · [Route.Handler](#route-handler) · [Route.HandlerName](#route-handlername) · [Route.IsWebSocket](#route-iswebsocket) · [Route.Method](#route-method) · [Route.MiddlewareNames](#route-middlewarenames) · [Route.Middlewares](#route-middlewares) · [Route.Path](#route-path) · [Route.WebSocketHandler](#route-websockethandler) · [Route.WithMiddlewareNames](#route-withmiddlewarenames) · [RouteGroup.MiddlewareNames](#routegroup-middlewarenames) · [RouteGroup.Middlewares](#routegroup-middlewares) · [RouteGroup.RoutePrefix](#routegroup-routeprefix) · [RouteGroup.Routes](#routegroup-routes) · [RouteGroup.WithMiddlewareNames](#routegroup-withmiddlewarenames) | +| **Testing** | [NewContext](#webtest-newcontext) | + + +## API Reference + +_Generated from public API comments and examples._ + +### Adapter + +#### echoweb.Adapter.Echo + +Echo returns the underlying Echo engine. + +```go +adapter := echoweb.New() +fmt.Println(adapter.Echo() != nil) +// true +``` + +#### echoweb.Adapter.Router + +Router returns the app-facing router contract. + +```go +adapter := echoweb.New() +fmt.Println(adapter.Router() != nil) +// true +``` + +#### echoweb.Adapter.ServeHTTP + +ServeHTTP exposes the adapter as a standard http.Handler. + +```go +adapter := echoweb.New() +adapter.Router().GET("/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }) +rr := httptest.NewRecorder() +req := httptest.NewRequest(http.MethodGet, "/healthz", nil) +adapter.ServeHTTP(rr, req) +fmt.Println(rr.Code) +// 204 +``` + +#### echoweb.New + +New creates a new Echo-backed web adapter. + +```go +adapter := echoweb.New() +fmt.Println(adapter.Router() != nil, adapter.Echo() != nil) +// true true +``` + +#### echoweb.NewServer + +NewServer creates an Echo-backed server from web route groups and mounts. + +```go +server, err := echoweb.NewServer(echoweb.ServerConfig{ + RouteGroups: []web.RouteGroup{ + web.NewRouteGroup("/api", []web.Route{ + web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }), + }), + }, +}) + +fmt.Println(err == nil, server.Router() != nil) +// true true +``` + +#### echoweb.Server.Router + +Router exposes the app-facing router contract. + +```go +server, _ := echoweb.NewServer(echoweb.ServerConfig{}) +fmt.Println(server.Router() != nil) +// true +``` + +#### echoweb.Server.Serve + +Serve starts the server and gracefully shuts it down when ctx is cancelled. + +```go +server, _ := echoweb.NewServer(echoweb.ServerConfig{Addr: "127.0.0.1:0"}) +ctx, cancel := context.WithCancel(context.Background()) +cancel() +fmt.Println(server.Serve(ctx) == nil) +// true +``` + +#### echoweb.Server.ServeHTTP + +ServeHTTP exposes the server as an http.Handler for tests and local probing. + +```go +server, _ := echoweb.NewServer(echoweb.ServerConfig{ + RouteGroups: []web.RouteGroup{ + web.NewRouteGroup("/api", []web.Route{ + web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusNoContent) }), + }), + }, +}) + +rr := httptest.NewRecorder() +req := httptest.NewRequest(http.MethodGet, "/api/healthz", nil) +server.ServeHTTP(rr, req) +fmt.Println(rr.Code) +// 204 +``` + +#### echoweb.UnwrapContext + +UnwrapContext returns the underlying Echo context when the web.Context came from this adapter. + +```go +adapter := echoweb.New() + +adapter.Router().GET("/healthz", func(c web.Context) error { + _, ok := echoweb.UnwrapContext(c) + fmt.Println(ok) + return c.NoContent(http.StatusOK) +}) + +rr := httptest.NewRecorder() +req := httptest.NewRequest(http.MethodGet, "/healthz", nil) +adapter.ServeHTTP(rr, req) +// true +``` + +#### echoweb.UnwrapWebSocketConn + +UnwrapWebSocketConn returns the underlying gorilla websocket connection. + +```go +_, ok := echoweb.UnwrapWebSocketConn(nil) +fmt.Println(ok) +// false +``` + +#### echoweb.Wrap + +Wrap exposes an existing Echo engine through the web.Router contract. + +```go +adapter := echoweb.Wrap(nil) +fmt.Println(adapter.Echo() != nil) +// true +``` + +### Indexing + +#### webindex.Run + +Run indexes API metadata from source and writes artifacts. + +```go +manifest, err := webindex.Run(context.Background(), webindex.IndexOptions{ + Root: ".", + OutPath: "webindex.json", +}) + +fmt.Println(err == nil, manifest.Version != "") +// true true +``` + +### Auth Middleware + +#### webmiddleware.BasicAuth + +BasicAuth returns basic auth middleware. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.BasicAuth(func(user, pass string, c web.Context) (bool, error) { + return user == "demo" && pass == "secret", nil +})) + +router.GET("/admin", func(c web.Context) error { + return c.Text(200, "welcome") +}) +``` + +#### webmiddleware.BasicAuthWithConfig + +BasicAuthWithConfig returns basic auth middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.BasicAuthWithConfig(webmiddleware.BasicAuthConfig{ + Realm: "Admin", + Validator: func(user, pass string, c web.Context) (bool, error) { + return user == "demo" && pass == "secret", nil + }, +})) + +router.GET("/admin", func(c web.Context) error { + return c.Text(200, "welcome") +}) +``` + +#### webmiddleware.CSRF + +CSRF enables token-based CSRF protection. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.CSRF()) + +router.POST("/settings", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.CSRFWithConfig + +CSRFWithConfig enables token-based CSRF protection with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.CSRFWithConfig(webmiddleware.CSRFConfig{ + CookieName: "_csrf", + TokenLookup: "header:X-CSRF-Token", +})) + +router.POST("/settings", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.CreateExtractors + +CreateExtractors creates extractors from a lookup definition. + +```go +extractors, err := webmiddleware.CreateExtractors("header:X-API-Key,query:token") +fmt.Println(err == nil, len(extractors)) +// true 2 +``` + +#### webmiddleware.KeyAuth + +KeyAuth returns key auth middleware. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.KeyAuth(func(key string, c web.Context) (bool, error) { + return key == "demo-key", nil +})) + +router.GET("/api/reports", func(c web.Context) error { + return c.JSON(200, map[string]any{"ready": true}) +}) +``` + +#### webmiddleware.KeyAuthWithConfig + +KeyAuthWithConfig returns key auth middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.KeyAuthWithConfig(webmiddleware.KeyAuthConfig{ + KeyLookup: "query:api_key", + Validator: func(key string, c web.Context) (bool, error) { + return key == "demo-key", nil + }, +})) + +router.GET("/api/reports", func(c web.Context) error { + return c.JSON(200, map[string]any{"ready": true}) +}) +``` + +### Compression Middleware + +#### webmiddleware.Compress + +Compress enables gzip response compression for clients that support it. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.Compress()) + +router.GET("/reports", func(c web.Context) error { + return c.Text(200, "large report response") +}) +``` + +#### webmiddleware.Decompress + +Decompress inflates gzip-encoded request bodies before handlers read them. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.Decompress()) + +router.POST("/ingest", func(c web.Context) error { + data, _ := io.ReadAll(c.Request().Body) + return c.JSON(200, map[string]int{"bytes": len(data)}) +}) +``` + +#### webmiddleware.DecompressWithConfig + +DecompressWithConfig inflates gzip-encoded request bodies with custom options. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.DecompressWithConfig(webmiddleware.DecompressConfig{ + Skipper: func(c web.Context) bool { + return c.Path() == "/webhooks/raw" + }, +})) + +router.POST("/ingest", func(c web.Context) error { + return c.NoContent(202) +}) +``` + +#### webmiddleware.Gzip + +Gzip enables gzip response compression for clients that support it. + +```go +router := echoweb.New().Router() + +router.GET("/feed", func(c web.Context) error { + return c.Text(200, "large feed response") +}, webmiddleware.Gzip()) +``` + +#### webmiddleware.GzipWithConfig + +GzipWithConfig enables gzip response compression with custom options. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.GzipWithConfig(webmiddleware.GzipConfig{ + MinLength: 1024, +})) +``` + +### Method Override Middleware + +#### webmiddleware.MethodFromForm + +MethodFromForm gets an override method from a form field. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ + Getter: webmiddleware.MethodFromForm("_method"), +})) +``` + +#### webmiddleware.MethodFromHeader + +MethodFromHeader gets an override method from a request header. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ + Getter: webmiddleware.MethodFromHeader("X-HTTP-Method-Override"), +})) +``` + +#### webmiddleware.MethodFromQuery + +MethodFromQuery gets an override method from a query parameter. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ + Getter: webmiddleware.MethodFromQuery("_method"), +})) +``` + +#### webmiddleware.MethodOverride + +MethodOverride returns method override middleware. + +```go +router := echoweb.New().Router() +router.Pre(webmiddleware.MethodOverride()) + +router.PATCH("/articles/:id", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.MethodOverrideWithConfig + +MethodOverrideWithConfig returns method override middleware with config. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{ + Getter: webmiddleware.MethodFromQuery("_method"), +})) + +router.DELETE("/articles/:id", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +### Path Rewriting Middleware + +#### webmiddleware.AddTrailingSlash + +AddTrailingSlash adds a trailing slash to the request path. + +```go +router := echoweb.New().Router() +router.Pre(webmiddleware.AddTrailingSlash()) + +router.GET("/docs/", func(c web.Context) error { + return c.Text(200, "docs") +}) +``` + +#### webmiddleware.AddTrailingSlashWithConfig + +AddTrailingSlashWithConfig returns trailing-slash middleware with config. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.AddTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{ + RedirectCode: 308, +})) + +router.GET("/docs/", func(c web.Context) error { + return c.Text(200, "docs") +}) +``` + +#### webmiddleware.RemoveTrailingSlash + +RemoveTrailingSlash removes the trailing slash from the request path. + +```go +router := echoweb.New().Router() +router.Pre(webmiddleware.RemoveTrailingSlash()) + +router.GET("/docs", func(c web.Context) error { + return c.Text(200, "docs") +}) +``` + +#### webmiddleware.RemoveTrailingSlashWithConfig + +RemoveTrailingSlashWithConfig returns remove-trailing-slash middleware with config. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.RemoveTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{ + RedirectCode: 308, +})) + +router.GET("/docs", func(c web.Context) error { + return c.Text(200, "docs") +}) +``` + +#### webmiddleware.Rewrite + +Rewrite rewrites the request path using wildcard rules. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.Rewrite(map[string]string{ + "/old/*": "/new/$1", +})) + +router.GET("/new/:name", func(c web.Context) error { + return c.Text(200, c.Param("name")) +}) +``` + +#### webmiddleware.RewriteWithConfig + +RewriteWithConfig rewrites the request path using wildcard and regex rules. + +```go +router := echoweb.New().Router() + +router.Pre(webmiddleware.RewriteWithConfig(webmiddleware.RewriteConfig{ + Rules: map[string]string{"/old/*": "/v2/$1"}, +})) + +router.GET("/v2/:name", func(c web.Context) error { + return c.Text(200, c.Param("name")) +}) +``` + +### Payloads Middleware + +#### webmiddleware.BodyDump + +BodyDump captures request and response payloads. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.BodyDump(func(c web.Context, reqBody, resBody []byte) { + log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody)) +})) + +router.POST("/webhooks", func(c web.Context) error { + return c.JSON(202, map[string]any{"queued": true}) +}) +``` + +#### webmiddleware.BodyDumpWithConfig + +BodyDumpWithConfig captures request and response payloads with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.BodyDumpWithConfig(webmiddleware.BodyDumpConfig{ + Skipper: func(c web.Context) bool { + return c.Path() == "/healthz" + }, + Handler: func(c web.Context, reqBody, resBody []byte) { + log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody)) + }, +})) +``` + +#### webmiddleware.BodyLimit + +BodyLimit returns middleware that limits request body size. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.BodyLimit("2MB")) + +router.POST("/uploads", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.BodyLimitWithConfig + +BodyLimitWithConfig returns body limit middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.BodyLimitWithConfig(webmiddleware.BodyLimitConfig{ + Limit: "10MB", +})) + +router.POST("/imports", func(c web.Context) error { + return c.NoContent(202) +}) +``` + +#### webmiddleware.ErrorBodyDump + +ErrorBodyDump captures response bodies for non-2xx and non-3xx responses. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.ErrorBodyDump(func(c web.Context, status int, body []byte) { + log.Printf("%s %s failed with %d", c.Method(), c.URI(), status) +})) + +router.GET("/reports/:id", func(c web.Context) error { + return c.Text(404, "report not found") +}) +``` + +#### webmiddleware.ErrorBodyDumpWithConfig + +ErrorBodyDumpWithConfig captures response bodies for non-success responses with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.ErrorBodyDumpWithConfig(webmiddleware.ErrorBodyDumpConfig{ + Skipper: func(c web.Context) bool { + return c.Path() == "/healthz" + }, + Handler: func(c web.Context, status int, body []byte) { + log.Printf("%s %s failed with %d", c.Method(), c.URI(), status) + }, +})) +``` + +### Proxying Middleware + +#### webmiddleware.NewRandomBalancer + +NewRandomBalancer creates a random proxy balancer. + +```go +target, _ := url.Parse("http://localhost:8080") +balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) +fmt.Println(balancer.Next(nil).URL.Host) +// localhost:8080 +``` + +#### webmiddleware.NewRoundRobinBalancer + +NewRoundRobinBalancer creates a round-robin proxy balancer. + +```go +target, _ := url.Parse("http://localhost:8080") +balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) +fmt.Println(balancer.Next(nil).URL.Host) +// localhost:8080 +``` + +#### webmiddleware.Proxy + +Proxy creates a proxy middleware. + +```go +target, _ := url.Parse("http://localhost:8080") +balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) + +router := echoweb.New().Router() +router.Use(webmiddleware.Proxy(balancer)) +``` + +#### webmiddleware.ProxyWithConfig + +ProxyWithConfig creates a proxy middleware with config. + +```go +target, _ := url.Parse("http://localhost:8080") +balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}}) + +router := echoweb.New().Router() + +router.Use(webmiddleware.ProxyWithConfig(webmiddleware.ProxyConfig{ + Balancer: balancer, + Rewrite: map[string]string{ + "/api/*": "/$1", + }, +})) +``` + +### Rate Limiting Middleware + +#### webmiddleware.NewRateLimiterMemoryStore + +NewRateLimiterMemoryStore creates an in-memory rate limiter store. + +```go +store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) +allowed1, _ := store.Allow("192.0.2.1") +allowed2, _ := store.Allow("192.0.2.1") +fmt.Println(allowed1, allowed2) +// true false +``` + +#### webmiddleware.NewRateLimiterMemoryStoreWithConfig + +NewRateLimiterMemoryStoreWithConfig creates an in-memory rate limiter store with config. + +```go +store := webmiddleware.NewRateLimiterMemoryStoreWithConfig(webmiddleware.RateLimiterMemoryStoreConfig{Rate: rate.Every(time.Second)}) +allowed, _ := store.Allow("192.0.2.1") +fmt.Println(allowed) +// true +``` + +#### webmiddleware.RateLimiter + +RateLimiter creates a rate limiting middleware. + +```go +store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) + +router := echoweb.New().Router() +router.Use(webmiddleware.RateLimiter(store)) + +router.POST("/api/messages", func(c web.Context) error { + return c.NoContent(202) +}) +``` + +#### webmiddleware.RateLimiterMemoryStore.Allow + +Allow checks whether the given identifier is allowed through. + +```go +store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) +allowed, err := store.Allow("127.0.0.1") +fmt.Println(err == nil, allowed) +// true true +``` + +#### webmiddleware.RateLimiterWithConfig + +RateLimiterWithConfig creates a rate limiting middleware with config. + +```go +store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second)) + +router := echoweb.New().Router() + +router.Use(webmiddleware.RateLimiterWithConfig(webmiddleware.RateLimiterConfig{ + Store: store, + IdentifierExtractor: func(c web.Context) (string, error) { + return c.Header("X-Account-ID"), nil + }, +})) +``` + +### Redirects Middleware + +#### webmiddleware.HTTPSNonWWWRedirect + +HTTPSNonWWWRedirect redirects to https without www. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.HTTPSNonWWWRedirect()) +``` + +#### webmiddleware.HTTPSNonWWWRedirectWithConfig + +HTTPSNonWWWRedirectWithConfig returns HTTPS non-WWW redirect middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.HTTPSNonWWWRedirectWithConfig(webmiddleware.RedirectConfig{ + Code: 307, +})) +``` + +#### webmiddleware.HTTPSRedirect + +HTTPSRedirect redirects http requests to https. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.HTTPSRedirect()) + +router.GET("/docs", func(c web.Context) error { + return c.Text(200, "docs") +}) +``` + +#### webmiddleware.HTTPSRedirectWithConfig + +HTTPSRedirectWithConfig returns HTTPS redirect middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.HTTPSRedirectWithConfig(webmiddleware.RedirectConfig{ + Code: 307, +})) +``` + +#### webmiddleware.HTTPSWWWRedirect + +HTTPSWWWRedirect redirects to https + www. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.HTTPSWWWRedirect()) +``` + +#### webmiddleware.HTTPSWWWRedirectWithConfig + +HTTPSWWWRedirectWithConfig returns HTTPS+WWW redirect middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.HTTPSWWWRedirectWithConfig(webmiddleware.RedirectConfig{ + Code: 307, +})) +``` + +#### webmiddleware.NonWWWRedirect + +NonWWWRedirect redirects to the non-www host. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.NonWWWRedirect()) +``` + +#### webmiddleware.NonWWWRedirectWithConfig + +NonWWWRedirectWithConfig returns non-WWW redirect middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.NonWWWRedirectWithConfig(webmiddleware.RedirectConfig{ + Code: 307, +})) +``` + +#### webmiddleware.WWWRedirect + +WWWRedirect redirects to the www host. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.WWWRedirect()) +``` + +#### webmiddleware.WWWRedirectWithConfig + +WWWRedirectWithConfig returns WWW redirect middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.WWWRedirectWithConfig(webmiddleware.RedirectConfig{ + Code: 307, +})) +``` + +### Reliability Middleware + +#### webmiddleware.Recover + +Recover returns middleware that recovers panics from the handler chain. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.Recover()) + +router.GET("/panic", func(c web.Context) error { + panic("boom") +}) +``` + +#### webmiddleware.RecoverWithConfig + +RecoverWithConfig returns recover middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.RecoverWithConfig(webmiddleware.RecoverConfig{ + DisableStack: true, + HandleError: func(c web.Context, err error, stack []byte) error { + return c.JSON(500, map[string]any{"error": "internal server error"}) + }, +})) +``` + +### Request Lifecycle Middleware + +#### webmiddleware.ContextTimeout + +ContextTimeout sets a timeout on the request context. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.ContextTimeout(2 * time.Second)) + +router.GET("/reports", func(c web.Context) error { + return c.JSON(200, map[string]any{"ready": true}) +}) +``` + +#### webmiddleware.ContextTimeoutWithConfig + +ContextTimeoutWithConfig sets a timeout on the request context with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.ContextTimeoutWithConfig(webmiddleware.ContextTimeoutConfig{ + Timeout: time.Second, +})) +``` + +#### webmiddleware.DefaultSkipper + +DefaultSkipper always runs the middleware. + +```go +fmt.Println(webmiddleware.DefaultSkipper(nil)) +// false +``` + +#### webmiddleware.RequestID + +RequestID returns middleware that sets a request id header and context value. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.RequestID()) + +router.GET("/healthz", func(c web.Context) error { + return c.JSON(200, map[string]any{ + "request_id": c.Get("request_id"), + }) +}) +``` + +#### webmiddleware.RequestIDWithConfig + +RequestIDWithConfig returns RequestID middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.RequestIDWithConfig(webmiddleware.RequestIDConfig{ + TargetHeader: "X-Correlation-ID", + ContextKey: "correlation_id", +})) +``` + +#### webmiddleware.RequestLoggerWithConfig + +RequestLoggerWithConfig returns request logger middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.RequestLoggerWithConfig(webmiddleware.RequestLoggerConfig{ + LogValuesFunc: func(c web.Context, values webmiddleware.RequestLoggerValues) error { + log.Printf("%s %s %d %s", values.Method, values.URI, values.Status, values.Latency) + return nil + }, +})) + +router.GET("/users/:id", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.Timeout + +Timeout returns a response-timeout middleware. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.Timeout()) + +router.GET("/healthz", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.TimeoutWithConfig + +TimeoutWithConfig returns a response-timeout middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.TimeoutWithConfig(webmiddleware.TimeoutConfig{ + Timeout: time.Second, + ErrorMessage: "request timed out", +})) +``` + +### Security Middleware + +#### webmiddleware.CORS + +CORS returns Cross-Origin Resource Sharing middleware. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.CORS()) + +router.GET("/api/healthz", func(c web.Context) error { + return c.JSON(200, map[string]any{"ok": true}) +}) +``` + +#### webmiddleware.CORSWithConfig + +CORSWithConfig returns CORS middleware with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.CORSWithConfig(webmiddleware.CORSConfig{ + AllowOrigins: []string{"https://app.example.com"}, + AllowMethods: []string{"GET", "POST", "PATCH"}, +})) + +router.GET("/api/healthz", func(c web.Context) error { + return c.JSON(200, map[string]any{"ok": true}) +}) +``` + +#### webmiddleware.Secure + +Secure sets security-oriented response headers. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.Secure()) + +router.GET("/", func(c web.Context) error { + return c.Text(200, "home") +}) +``` + +#### webmiddleware.SecureWithConfig + +SecureWithConfig sets security-oriented response headers with config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.SecureWithConfig(webmiddleware.SecureConfig{ + ReferrerPolicy: "same-origin", + ContentSecurityPolicy: "default-src 'self'", +})) +``` + +### Static Files Middleware + +#### webmiddleware.Static + +Static serves static content from the provided root. + +```go +router := echoweb.New().Router() +router.Use(webmiddleware.Static("public")) + +router.GET("/healthz", func(c web.Context) error { + return c.NoContent(204) +}) +``` + +#### webmiddleware.StaticWithConfig + +StaticWithConfig serves static content using config. + +```go +router := echoweb.New().Router() + +router.Use(webmiddleware.StaticWithConfig(webmiddleware.StaticConfig{ + Root: "public", + HTML5: true, +})) +``` + +### Prometheus + +#### webprometheus.Default + +Default returns the package-level Prometheus metrics instance. + +```go +fmt.Println(webprometheus.Default() == webprometheus.Default()) +// true +``` + +#### webprometheus.Handler + +Handler returns the package-level Prometheus scrape handler. + +```go +registry := prometheus.NewRegistry() +counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"}) +registry.MustRegister(counter) +counter.Inc() +metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry}) +recorder := httptest.NewRecorder() +ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil) +_ = metrics.Handler()(ctx) +fmt.Println(strings.Contains(recorder.Body.String(), "demo_total")) +// true +``` + +#### webprometheus.Metrics.Handler + +Handler exposes the configured Prometheus metrics as a web.Handler. + +```go +registry := prometheus.NewRegistry() +counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"}) +registry.MustRegister(counter) +counter.Inc() +metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry}) +recorder := httptest.NewRecorder() +ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil) +_ = metrics.Handler()(ctx) +fmt.Println(strings.Contains(recorder.Body.String(), "demo_total")) +// true +``` + +#### webprometheus.Metrics.Middleware + +Middleware records Prometheus metrics for each request. + +```go +registry := prometheus.NewRegistry() +metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"}) +handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) }) +ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil) +_ = handler(ctx) +out := &bytes.Buffer{} +_ = webprometheus.WriteGatheredMetrics(out, registry) +fmt.Println(strings.Contains(out.String(), "example_requests_total")) +// true +``` + +#### webprometheus.Middleware + +Middleware returns the package-level Prometheus middleware. + +```go +registry := prometheus.NewRegistry() +metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"}) +handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) }) +ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil) +_ = handler(ctx) +out := &bytes.Buffer{} +_ = webprometheus.WriteGatheredMetrics(out, registry) +fmt.Println(strings.Contains(out.String(), "example_requests_total")) +// true +``` + +#### webprometheus.MustNew + +MustNew creates a Metrics instance and panics on registration errors. + +```go +metrics := webprometheus.MustNew(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: prometheus.NewRegistry()}) +fmt.Println(metrics != nil) +// true +``` + +#### webprometheus.New + +New creates a Metrics instance backed by Prometheus collectors. + +```go +metrics, err := webprometheus.New(webprometheus.Config{Namespace: "app"}) +_ = metrics +fmt.Println(err == nil) +// true +``` + +#### webprometheus.RunPushGatewayGatherer + +RunPushGatewayGatherer starts pushing collected metrics until the context finishes. + +```go +err := webprometheus.RunPushGatewayGatherer(context.Background(), webprometheus.PushGatewayConfig{}) +fmt.Println(err != nil) +// true +``` + +#### webprometheus.WriteGatheredMetrics + +WriteGatheredMetrics gathers collected metrics and writes them to the given writer. + +```go +var buf bytes.Buffer +err := webprometheus.WriteGatheredMetrics(&buf, prometheus.NewRegistry()) +fmt.Println(err == nil) +// true +``` + +### Route Reporting + +#### BuildRouteEntries + +BuildRouteEntries builds a sorted slice of route entries from registered groups and extra entries. + +```go +entries := web.BuildRouteEntries([]web.RouteGroup{ + web.NewRouteGroup("/api", []web.Route{ + web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), + }), +}) +fmt.Println(entries[0].Path, entries[0].Methods[0]) +// /api/healthz GET +``` + +#### RenderRouteTable + +RenderRouteTable renders a route table using simple ASCII borders and ANSI colors. + +```go +table := web.RenderRouteTable([]web.RouteEntry{{ + Path: "/api/healthz", + Handler: "monitoring.Healthz", + Methods: []string{"GET"}, +}}) +fmt.Println(strings.Contains(table, "/api/healthz")) +// true +``` + +### Routing + +#### MountRouter + +MountRouter applies mount-style router configuration in declaration order. + +```go +adapter := echoweb.New() + +err := web.MountRouter(adapter.Router(), []web.RouterMount{ + func(r web.Router) error { + r.GET("/healthz", func(c web.Context) error { return nil }) + return nil + }, +}) + +fmt.Println(err == nil) +// true +``` + +#### NewRoute + +NewRoute creates a new route using the app-facing web handler contract directly. + +```go +route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { + return c.NoContent(http.StatusOK) +}) + +fmt.Println(route.Method(), route.Path()) +// GET /healthz +``` + +#### NewRouteGroup + +NewRouteGroup wraps routes and their accompanied web middleware. + +```go +group := web.NewRouteGroup("/api", []web.Route{ + web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), +}) + +fmt.Println(group.RoutePrefix(), len(group.Routes())) +// /api 1 +``` + +#### NewWebSocketRoute + +NewWebSocketRoute creates a websocket route using the app-facing websocket handler contract. + +```go +route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { + return nil +}) + +fmt.Println(route.IsWebSocket()) +// true +``` + +#### RegisterRoutes + +RegisterRoutes registers route groups onto a router. + +```go +adapter := echoweb.New() + +groups := []web.RouteGroup{ + web.NewRouteGroup("/api", []web.Route{ + web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), + }), +} + +err := web.RegisterRoutes(adapter.Router(), groups) +fmt.Println(err == nil) +// true +``` + +#### Route.Handler + +Handler returns the route handler. + +```go +route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { + return c.NoContent(http.StatusCreated) +}) + +ctx := webtest.NewContext(nil, nil, "/healthz", nil) +_ = route.Handler()(ctx) +fmt.Println(ctx.StatusCode()) +// 201 +``` + +#### Route.HandlerName + +HandlerName returns the original handler name for route reporting. + +```go +route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }) +fmt.Println(route.HandlerName() != "") +// true +``` + +#### Route.IsWebSocket + +IsWebSocket reports whether this route upgrades to a websocket connection. + +```go +route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { return nil }) +fmt.Println(route.IsWebSocket()) +// true +``` + +#### Route.Method + +Method returns the HTTP method. + +```go +route := web.NewRoute(http.MethodPost, "/users", func(c web.Context) error { return nil }) +fmt.Println(route.Method()) +// POST +``` + +#### Route.MiddlewareNames + +MiddlewareNames returns original middleware names for route reporting. + +```go +route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth") +fmt.Println(route.MiddlewareNames()[0]) +// auth +``` + +#### Route.Middlewares + +Middlewares returns the route middleware slice. + +```go +route := web.NewRoute( + +http.MethodGet, +"/healthz", +func(c web.Context) error { return nil }, +func(next web.Handler) web.Handler { return next }, + +) +fmt.Println(len(route.Middlewares())) +// 1 +``` + +#### Route.Path + +Path returns the path of the route. + +```go +route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }) +fmt.Println(route.Path()) +// /healthz +``` + +#### Route.WebSocketHandler + +WebSocketHandler returns the websocket route handler. + +```go +route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { + c.Set("ready", true) + return nil +}) + +ctx := webtest.NewContext(nil, nil, "/ws", nil) +err := route.WebSocketHandler()(ctx, nil) +fmt.Println(err == nil, ctx.Get("ready")) +// true true +``` + +#### Route.WithMiddlewareNames + +WithMiddlewareNames attaches reporting-only middleware names to the route. + +```go +route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth", "trace") +fmt.Println(len(route.MiddlewareNames())) +// 2 +``` + +#### RouteGroup.MiddlewareNames + +MiddlewareNames returns original middleware names for route reporting. + +```go +group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth") +fmt.Println(group.MiddlewareNames()[0]) +// auth +``` + +#### RouteGroup.Middlewares + +Middlewares returns the middleware slice for the group. + +```go +group := web.NewRouteGroup("/api", nil, func(next web.Handler) web.Handler { return next }) +fmt.Println(len(group.Middlewares())) +// 1 +``` + +#### RouteGroup.RoutePrefix + +RoutePrefix returns the group prefix. + +```go +group := web.NewRouteGroup("/api", nil) +fmt.Println(group.RoutePrefix()) +// /api +``` + +#### RouteGroup.Routes + +Routes returns the routes in the group. + +```go +group := web.NewRouteGroup("/api", []web.Route{ + web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }), +}) + +fmt.Println(len(group.Routes())) +// 1 +``` + +#### RouteGroup.WithMiddlewareNames + +WithMiddlewareNames attaches reporting-only middleware names to the group. + +```go +group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth", "trace") +fmt.Println(len(group.MiddlewareNames())) +// 2 +``` + +### Testing + +#### webtest.NewContext + +NewContext creates a new test context around the provided request/recorder pair. + +```go +req := httptest.NewRequest(http.MethodGet, "/users/42?expand=roles", nil) +ctx := webtest.NewContext(req, nil, "/users/:id", webtest.PathParams{"id": "42"}) +fmt.Println(ctx.Param("id"), ctx.Query("expand")) +// 42 roles +``` diff --git a/docs/readme/main.go b/docs/readme/main.go index 8fa2789..252296a 100644 --- a/docs/readme/main.go +++ b/docs/readme/main.go @@ -25,10 +25,10 @@ func main() { fmt.Println("Error:", err) os.Exit(1) } - fmt.Println("✔ API.md and README reference updated") + fmt.Println("✔ API index and examples updated in README.md") } -// run regenerates the annotated API guide while keeping the README focused on onboarding. +// run regenerates the annotated API index and examples inside the README marker pair. func run() error { root, err := findRoot() if err != nil { @@ -46,28 +46,11 @@ func run() error { return err } - readmeReference := strings.Join([]string{ - "The [complete API documentation](https://pkg.go.dev/github.com/goforj/web) includes every exported type, field, function, and method.", - "The [generated API examples](API.md) collect the functions and methods annotated with runnable examples in this repository.", - }, "\n\n") + "\n" - out, err := replaceSection(string(data), apiStart, apiEnd, readmeReference) + out, err := replaceSection(string(data), apiStart, apiEnd, renderAPI(funcs)) if err != nil { return err } - apiDocument := strings.Join([]string{ - "# API examples", - "", - "_Generated from annotated public API comments. Do not edit this file directly._", - "", - "This guide covers functions and methods with repository examples. See [pkg.go.dev](https://pkg.go.dev/github.com/goforj/web) for the complete API, including exported types, configuration fields, constants, and variables.", - "", - renderAPI(funcs), - }, "\n") - if err := os.WriteFile(filepath.Join(root, "API.md"), []byte(apiDocument), 0o644); err != nil { - return err - } - return os.WriteFile(readmePath, []byte(out), 0o644) } @@ -388,7 +371,7 @@ func renderAPI(funcs []*FuncDoc) string { } buf.WriteString("\n\n") - buf.WriteString("## Examples\n\n") + buf.WriteString("## API Reference\n\n") buf.WriteString("_Generated from public API comments and examples._\n\n") for _, group := range groupNames {