Skip to content

feat: support Echo v5 - #14

Merged
pablor21 merged 1 commit into
pablor21:mainfrom
thomaspoignant:support-echo-v5
Sep 7, 2026
Merged

pablor21 merged 1 commit into
pablor21:mainfrom
thomaspoignant:support-echo-v5

Conversation

@thomaspoignant

@thomaspoignant thomaspoignant commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

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 today echo-etag can't be used from any project that has migrated.

Layout

The module becomes github.com/pablor21/echo-etag/v5 on main, which mirrors what labstack/echo and labstack/echo-contrib do: the default branch tracks the latest major, and a v4 branch keeps the previous one alive. This needs one thing from you: cutting a v4 branch from dcd5d3d (the current main) for Echo 4 maintenance. Nothing breaks if that happens after the merge — existing v4 users are already served by the v4.0.5 tag.

Happy to redo this as a v5/ subdirectory module instead if you'd rather keep both majors on main — that avoids the branch entirely at the cost of duplicating etag.go. Just say the word.

The port

  • echo.Context is a struct in v5, so handlers and middleware.Skipper take *echo.Context.
  • c.Response() returns an http.ResponseWriter and no longer exposes a Writer field. The buffering writer is now installed with c.SetResponse() and restored the same way, matching middleware/body_dump.go.
  • That flips the nesting: in v4 bufferedWriter sat below *echo.Response, in v5 it sits above it. Nice side effect — Response.Status/Size now reflect what is actually sent, so a 304 gets logged as a 304 rather than as the handler's 200.
  • SetResponse requires the writer to implement Unwrap() http.ResponseWriter so echo.UnwrapResponse and http.NewResponseController can reach the underlying *echo.Response. bufferedWriter now implements it, plus Hijack. Flush is a deliberate no-op: the body is buffered on purpose to compute the Etag, so forwarding a flush would commit the response before the Etag header is set. (In v4 c.Response().Flush() panicked outright, so this is a small improvement. Streaming endpoints should still use Skipper, as the README warns.)

The Etag logic itself is untouched — same hashes, same If-None-Match handling, and the existing tests pass unchanged. Two small tests are added for the two things the port actually changed: that a 204 still passes through without an Etag, and that echo.UnwrapResponse resolves while the middleware is active.

Release workflow

actions.yml picked the highest vX.Y.Z tag repo-wide and bumped the patch, which after this change would tag v5 code as v4.0.6 — and once a v5 tag existed, the v4 branch could never be released again. It now derives the major from the module path in go.mod, so main cuts v5.0.0 and the v4 branch keeps cutting v4.0.x. v4 is added to the push triggers. checks.yml moves to Go 1.25 because Echo v5's go directive is 1.25.0.

Verification

go build, go vet, staticcheck, golint and go test all pass. Also smoke-tested against a real v5 server: GET / returns Etag: W/"11-8dcfee46", and the same request with a matching If-None-Match returns 304 with an empty body.

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.
@pablor21
pablor21 merged commit bd25fc6 into pablor21:main Sep 7, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Echo v5

2 participants