feat: support Echo v5 - #14
Merged
Merged
Conversation
Echo v5 was released on 2026-01-18 and v4 is in security/bugfix-only mode until 2026-12-31, but `echo-etag/v4` requires `github.com/labstack/echo/v4` so it cannot be used from projects that have migrated. Move the module to `github.com/pablor21/echo-etag/v5`, following the layout labstack/echo and labstack/echo-contrib use (default branch tracks the latest major, a `v4` branch keeps the previous one alive). The port itself: - `echo.Context` is a struct in v5, so handlers and `middleware.Skipper` take `*echo.Context`. - `c.Response()` now returns an `http.ResponseWriter` and no longer exposes a `Writer` field, so the buffering writer is installed with `c.SetResponse()` and restored the same way, as `middleware/body_dump.go` does. - `SetResponse` requires the writer to implement `Unwrap() http.ResponseWriter` so that `echo.UnwrapResponse` and `http.NewResponseController` can still reach the underlying `*echo.Response`; `bufferedWriter` now does, along with `Hijack`. `Flush` is a documented no-op because the body is buffered on purpose to compute the Etag. Release tagging now derives the major version from the module path in go.mod, so `main` releases v5.x.y and the `v4` branch keeps releasing v4.x.y. Closes pablor21#13
thomaspoignant
added a commit
to thomaspoignant/go-feature-flag
that referenced
this pull request
Sep 7, 2026
Closes #5294. Echo v5.0.0 shipped 2026-01-18 and is now at v5.3.1, and the ecosystem blockers recorded on the issue have cleared: echo/v4 v4.15.4 -> echo/v5 v5.3.1 echo-contrib v0.50.1 -> echo-contrib/v5 v5.0.1 echo-swagger v1.5.2 -> echo-swagger/v2 v2.0.1 otelecho v0.70.0 -> labstack/echo-opentelemetry v0.0.3 awslabs/aws-lambda-go-api-proxy (archived, no v5) -> mshindle/aws-lambda-go-api-proxy v0.20.0 The etag middleware is the one dependency with no tagged v5 release; the in-repo copy added earlier in this stack is ported here and still tracks pablor21/echo-etag#14 for removal. Main changes beyond the mechanical echo.Context -> *echo.Context rename: - Server lifecycle moves to echo.StartConfig. Echo.Start/StartServer/ Shutdown/Listener/HideBanner/HidePort/Debug are all gone in v5, so shutdown is now driven by cancelling the context passed to Start. The Server keeps a cancel func and a done channel per listener so Stop keeps its previous "shut down and wait" semantics. api.New now returns *Server because Server holds a mutex. - BeforeServeFunc clears the new 30s ReadTimeout default that v5 applies, which would otherwise cut off the long-lived SSE and websocket flag-change connections, and sets ReadHeaderTimeout instead so the slowloris protection behind that default is preserved. - middleware.CORS() takes explicit origins in v5 and panics without them; v4's DefaultCORSConfig implied "*", so the call sites now pass "*" to keep the existing behaviour. - KeyAuth validator and error handler signatures changed, and echo.ErrUnauthorized is now an immutable sentinel rather than an *echo.HTTPError, so status is read with echo.StatusCode. - ZapLogger unwraps the response with echo.UnwrapResponse since Context.Response() returns a plain http.ResponseWriter in v5. User-visible behaviour changes worth noting in the release notes: - Bind failures now surface echo's generic "Bad Request" instead of the underlying parser message, and HTTPError.Error() renders the cause as "err=" rather than "internal=". This affects the errorDetails field of OFREP responses. - A missing API key now returns 401 instead of 400 (echo's ErrKeyMissing changed). Invalid keys were already 401. - Access logs now report 304 responses on the ETag'd endpoints as 304 rather than the handler's 200, because the v5 ETag writer wraps the echo response rather than sitting beneath it. make lint is clean on all three modules and the full test suite passes.
thomaspoignant
added a commit
to thomaspoignant/go-feature-flag
that referenced
this pull request
Sep 7, 2026
Closes #5294. Echo v5.0.0 shipped 2026-01-18 and is now at v5.3.1, and the ecosystem blockers recorded on the issue have cleared: echo/v4 v4.15.4 -> echo/v5 v5.3.1 echo-contrib v0.50.1 -> echo-contrib/v5 v5.0.1 echo-swagger v1.5.2 -> echo-swagger/v2 v2.0.1 otelecho v0.70.0 -> labstack/echo-opentelemetry v0.0.3 awslabs/aws-lambda-go-api-proxy (archived, no v5) -> mshindle/aws-lambda-go-api-proxy v0.20.0 The etag middleware is the one dependency with no tagged v5 release; the in-repo copy added earlier in this stack is ported here and still tracks pablor21/echo-etag#14 for removal. Main changes beyond the mechanical echo.Context -> *echo.Context rename: - Server lifecycle moves to echo.StartConfig. Echo.Start/StartServer/ Shutdown/Listener/HideBanner/HidePort/Debug are all gone in v5, so shutdown is now driven by cancelling the context passed to Start. The Server keeps a cancel func and a done channel per listener so Stop keeps its previous "shut down and wait" semantics. api.New now returns *Server because Server holds a mutex. - BeforeServeFunc clears the new 30s ReadTimeout default that v5 applies, which would otherwise cut off the long-lived SSE and websocket flag-change connections, and sets ReadHeaderTimeout instead so the slowloris protection behind that default is preserved. - middleware.CORS() takes explicit origins in v5 and panics without them; v4's DefaultCORSConfig implied "*", so the call sites now pass "*" to keep the existing behaviour. - KeyAuth validator and error handler signatures changed, and echo.ErrUnauthorized is now an immutable sentinel rather than an *echo.HTTPError, so status is read with echo.StatusCode. - ZapLogger unwraps the response with echo.UnwrapResponse since Context.Response() returns a plain http.ResponseWriter in v5. User-visible behaviour changes worth noting in the release notes: - Bind failures now surface echo's generic "Bad Request" instead of the underlying parser message, and HTTPError.Error() renders the cause as "err=" rather than "internal=". This affects the errorDetails field of OFREP responses. - A missing API key now returns 401 instead of 400 (echo's ErrKeyMissing changed). Invalid keys were already 401. - Access logs now report 304 responses on the ETag'd endpoints as 304 rather than the handler's 200, because the v5 ETag writer wraps the echo response rather than sitting beneath it. make lint is clean on all three modules and the full test suite passes.
thomaspoignant
added a commit
to thomaspoignant/go-feature-flag
that referenced
this pull request
Sep 7, 2026
Closes #5294. Echo v5.0.0 shipped 2026-01-18 and is now at v5.3.1, and the ecosystem blockers recorded on the issue have cleared: echo/v4 v4.15.4 -> echo/v5 v5.3.1 echo-contrib v0.50.1 -> echo-contrib/v5 v5.0.1 echo-swagger v1.5.2 -> echo-swagger/v2 v2.0.1 otelecho v0.70.0 -> labstack/echo-opentelemetry v0.0.3 awslabs/aws-lambda-go-api-proxy (archived, no v5) -> mshindle/aws-lambda-go-api-proxy v0.20.0 The etag middleware is the one dependency with no tagged v5 release; the in-repo copy added earlier in this stack is ported here and still tracks pablor21/echo-etag#14 for removal. Main changes beyond the mechanical echo.Context -> *echo.Context rename: - Server lifecycle moves to echo.StartConfig. Echo.Start/StartServer/ Shutdown/Listener/HideBanner/HidePort/Debug are all gone in v5, so shutdown is now driven by cancelling the context passed to Start. The Server keeps a cancel func and a done channel per listener so Stop keeps its previous "shut down and wait" semantics. api.New now returns *Server because Server holds a mutex. - BeforeServeFunc clears the new 30s ReadTimeout default that v5 applies, which would otherwise cut off the long-lived SSE and websocket flag-change connections, and sets ReadHeaderTimeout instead so the slowloris protection behind that default is preserved. - middleware.CORS() takes explicit origins in v5 and panics without them; v4's DefaultCORSConfig implied "*", so the call sites now pass "*" to keep the existing behaviour. - KeyAuth validator and error handler signatures changed, and echo.ErrUnauthorized is now an immutable sentinel rather than an *echo.HTTPError, so status is read with echo.StatusCode. - ZapLogger unwraps the response with echo.UnwrapResponse since Context.Response() returns a plain http.ResponseWriter in v5. User-visible behaviour changes worth noting in the release notes: - Bind failures now surface echo's generic "Bad Request" instead of the underlying parser message, and HTTPError.Error() renders the cause as "err=" rather than "internal=". This affects the errorDetails field of OFREP responses. - A missing API key now returns 401 instead of 400 (echo's ErrKeyMissing changed). Invalid keys were already 401. - Access logs now report 304 responses on the ETag'd endpoints as 304 rather than the handler's 200, because the v5 ETag writer wraps the echo response rather than sitting beneath it. make lint is clean on all three modules and the full test suite passes.
thomaspoignant
added a commit
to thomaspoignant/go-feature-flag
that referenced
this pull request
Sep 8, 2026
Closes #5294. Echo v5.0.0 shipped 2026-01-18 and is now at v5.3.1, and the ecosystem blockers recorded on the issue have cleared: echo/v4 v4.15.4 -> echo/v5 v5.3.1 echo-contrib v0.50.1 -> echo-contrib/v5 v5.0.1 echo-swagger v1.5.2 -> echo-swagger/v2 v2.0.1 otelecho v0.70.0 -> labstack/echo-opentelemetry v0.0.3 awslabs/aws-lambda-go-api-proxy (archived, no v5) -> mshindle/aws-lambda-go-api-proxy v0.20.0 The etag middleware is the one dependency with no tagged v5 release; the in-repo copy added earlier in this stack is ported here and still tracks pablor21/echo-etag#14 for removal. Main changes beyond the mechanical echo.Context -> *echo.Context rename: - Server lifecycle moves to echo.StartConfig. Echo.Start/StartServer/ Shutdown/Listener/HideBanner/HidePort/Debug are all gone in v5, so shutdown is now driven by cancelling the context passed to Start. The Server keeps a cancel func and a done channel per listener so Stop keeps its previous "shut down and wait" semantics. api.New now returns *Server because Server holds a mutex. - BeforeServeFunc clears the new 30s ReadTimeout default that v5 applies, which would otherwise cut off the long-lived SSE and websocket flag-change connections, and sets ReadHeaderTimeout instead so the slowloris protection behind that default is preserved. - middleware.CORS() takes explicit origins in v5 and panics without them; v4's DefaultCORSConfig implied "*", so the call sites now pass "*" to keep the existing behaviour. - KeyAuth validator and error handler signatures changed, and echo.ErrUnauthorized is now an immutable sentinel rather than an *echo.HTTPError, so status is read with echo.StatusCode. - ZapLogger unwraps the response with echo.UnwrapResponse since Context.Response() returns a plain http.ResponseWriter in v5. User-visible behaviour changes worth noting in the release notes: - Bind failures now surface echo's generic "Bad Request" instead of the underlying parser message, and HTTPError.Error() renders the cause as "err=" rather than "internal=". This affects the errorDetails field of OFREP responses. - A missing API key now returns 401 instead of 400 (echo's ErrKeyMissing changed). Invalid keys were already 401. - Access logs now report 304 responses on the ETag'd endpoints as 304 rather than the handler's 200, because the v5 ETag writer wraps the echo response rather than sitting beneath it. make lint is clean on all three modules and the full test suite passes.
thomaspoignant
added a commit
to thomaspoignant/go-feature-flag
that referenced
this pull request
Sep 8, 2026
Closes #5294. Echo v5.0.0 shipped 2026-01-18 and is now at v5.3.1, and the ecosystem blockers recorded on the issue have cleared: echo/v4 v4.15.4 -> echo/v5 v5.3.1 echo-contrib v0.50.1 -> echo-contrib/v5 v5.0.1 echo-swagger v1.5.2 -> echo-swagger/v2 v2.0.1 otelecho v0.70.0 -> labstack/echo-opentelemetry v0.0.3 awslabs/aws-lambda-go-api-proxy (archived, no v5) -> mshindle/aws-lambda-go-api-proxy v0.20.0 The etag middleware is the one dependency with no tagged v5 release; the in-repo copy added earlier in this stack is ported here and still tracks pablor21/echo-etag#14 for removal. Main changes beyond the mechanical echo.Context -> *echo.Context rename: - Server lifecycle moves to echo.StartConfig. Echo.Start/StartServer/ Shutdown/Listener/HideBanner/HidePort/Debug are all gone in v5, so shutdown is now driven by cancelling the context passed to Start. The Server keeps a cancel func and a done channel per listener so Stop keeps its previous "shut down and wait" semantics. api.New now returns *Server because Server holds a mutex. - BeforeServeFunc clears the new 30s ReadTimeout default that v5 applies, which would otherwise cut off the long-lived SSE and websocket flag-change connections, and sets ReadHeaderTimeout instead so the slowloris protection behind that default is preserved. - middleware.CORS() takes explicit origins in v5 and panics without them; v4's DefaultCORSConfig implied "*", so the call sites now pass "*" to keep the existing behaviour. - KeyAuth validator and error handler signatures changed, and echo.ErrUnauthorized is now an immutable sentinel rather than an *echo.HTTPError, so status is read with echo.StatusCode. - ZapLogger unwraps the response with echo.UnwrapResponse since Context.Response() returns a plain http.ResponseWriter in v5. User-visible behaviour changes worth noting in the release notes: - Bind failures now surface echo's generic "Bad Request" instead of the underlying parser message, and HTTPError.Error() renders the cause as "err=" rather than "internal=". This affects the errorDetails field of OFREP responses. - A missing API key now returns 401 instead of 400 (echo's ErrKeyMissing changed). Invalid keys were already 401. - Access logs now report 304 responses on the ETag'd endpoints as 304 rather than the handler's 200, because the v5 ETag writer wraps the echo response rather than sitting beneath it. make lint is clean on all three modules and the full test suite passes.
thomaspoignant
added a commit
to thomaspoignant/go-feature-flag
that referenced
this pull request
Sep 8, 2026
Closes #5294. Echo v5.0.0 shipped 2026-01-18 and is now at v5.3.1, and the ecosystem blockers recorded on the issue have cleared: echo/v4 v4.15.4 -> echo/v5 v5.3.1 echo-contrib v0.50.1 -> echo-contrib/v5 v5.0.1 echo-swagger v1.5.2 -> echo-swagger/v2 v2.0.1 otelecho v0.70.0 -> labstack/echo-opentelemetry v0.0.3 awslabs/aws-lambda-go-api-proxy (archived, no v5) -> mshindle/aws-lambda-go-api-proxy v0.20.0 The etag middleware is the one dependency with no tagged v5 release; the in-repo copy added earlier in this stack is ported here and still tracks pablor21/echo-etag#14 for removal. Main changes beyond the mechanical echo.Context -> *echo.Context rename: - Server lifecycle moves to echo.StartConfig. Echo.Start/StartServer/ Shutdown/Listener/HideBanner/HidePort/Debug are all gone in v5, so shutdown is now driven by cancelling the context passed to Start. The Server keeps a cancel func and a done channel per listener so Stop keeps its previous "shut down and wait" semantics. api.New now returns *Server because Server holds a mutex. - BeforeServeFunc clears the new 30s ReadTimeout default that v5 applies, which would otherwise cut off the long-lived SSE and websocket flag-change connections, and sets ReadHeaderTimeout instead so the slowloris protection behind that default is preserved. - middleware.CORS() takes explicit origins in v5 and panics without them; v4's DefaultCORSConfig implied "*", so the call sites now pass "*" to keep the existing behaviour. - KeyAuth validator and error handler signatures changed, and echo.ErrUnauthorized is now an immutable sentinel rather than an *echo.HTTPError, so status is read with echo.StatusCode. - ZapLogger unwraps the response with echo.UnwrapResponse since Context.Response() returns a plain http.ResponseWriter in v5. User-visible behaviour changes worth noting in the release notes: - Bind failures now surface echo's generic "Bad Request" instead of the underlying parser message, and HTTPError.Error() renders the cause as "err=" rather than "internal=". This affects the errorDetails field of OFREP responses. - A missing API key now returns 401 instead of 400 (echo's ErrKeyMissing changed). Invalid keys were already 401. - Access logs now report 304 responses on the ETag'd endpoints as 304 rather than the handler's 200, because the v5 ETag writer wraps the echo response rather than sitting beneath it. make lint is clean on all three modules and the full test suite passes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #13.
Ports the middleware to Echo v5 (
v5.3.1). Echo v5 shipped on 2026-01-18 and v4 is security/bugfix-only until 2026-12-31, so todayecho-etagcan't be used from any project that has migrated.Layout
The module becomes
github.com/pablor21/echo-etag/v5onmain, which mirrors whatlabstack/echoandlabstack/echo-contribdo: the default branch tracks the latest major, and av4branch keeps the previous one alive. This needs one thing from you: cutting av4branch fromdcd5d3d(the currentmain) for Echo 4 maintenance. Nothing breaks if that happens after the merge — existing v4 users are already served by thev4.0.5tag.Happy to redo this as a
v5/subdirectory module instead if you'd rather keep both majors onmain— that avoids the branch entirely at the cost of duplicatingetag.go. Just say the word.The port
echo.Contextis a struct in v5, so handlers andmiddleware.Skippertake*echo.Context.c.Response()returns anhttp.ResponseWriterand no longer exposes aWriterfield. The buffering writer is now installed withc.SetResponse()and restored the same way, matchingmiddleware/body_dump.go.bufferedWritersat below*echo.Response, in v5 it sits above it. Nice side effect —Response.Status/Sizenow reflect what is actually sent, so a 304 gets logged as a 304 rather than as the handler's 200.SetResponserequires the writer to implementUnwrap() http.ResponseWritersoecho.UnwrapResponseandhttp.NewResponseControllercan reach the underlying*echo.Response.bufferedWriternow implements it, plusHijack.Flushis a deliberate no-op: the body is buffered on purpose to compute the Etag, so forwarding a flush would commit the response before theEtagheader is set. (In v4c.Response().Flush()panicked outright, so this is a small improvement. Streaming endpoints should still useSkipper, as the README warns.)The Etag logic itself is untouched — same hashes, same
If-None-Matchhandling, and the existing tests pass unchanged. Two small tests are added for the two things the port actually changed: that a204still passes through without an Etag, and thatecho.UnwrapResponseresolves while the middleware is active.Release workflow
actions.ymlpicked the highestvX.Y.Ztag repo-wide and bumped the patch, which after this change would tag v5 code asv4.0.6— and once a v5 tag existed, thev4branch could never be released again. It now derives the major from the module path ingo.mod, somaincutsv5.0.0and thev4branch keeps cuttingv4.0.x.v4is added to the push triggers.checks.ymlmoves to Go 1.25 because Echo v5's go directive is1.25.0.Verification
go build,go vet,staticcheck,golintandgo testall pass. Also smoke-tested against a real v5 server:GET /returnsEtag: W/"11-8dcfee46", and the same request with a matchingIf-None-Matchreturns304with an empty body.