Skip to content

fix: preserve final responses after informational headers - #3106

Open
LeulTew wants to merge 1 commit into
labstack:masterfrom
LeulTew:leultew-echo-contribution-gauntlet
Open

LeulTew wants to merge 1 commit into
labstack:masterfrom
LeulTew:leultew-echo-contribution-gauntlet

Conversation

@LeulTew

@LeulTew LeulTew commented Sep 18, 2026

Copy link
Copy Markdown

Sending 103 Early Hints currently commits Echo's response, so a later explicit status or error response can be discarded. Keep informational responses provisional across the response, HEAD, gzip, and delayed JSON writers, while preserving 101 Switching Protocols as final.

Validation limit: Local checks used Windows/amd64 with Go 1.27.1. Race testing could not run because CGO is disabled; other OS/Go configurations and upstream CI are unverified.

Implementation and validation
  • Forward informational headers immediately without changing final status, commitment, byte accounting, or invoking final-response hooks.
  • Respect gzip's existing buffered-header/body state so a late informational header cannot follow a logically committed final response.
  • Preserve delayed JSON status overrides and serialization-error recovery. No public API, dependency, configuration, Flush, or upgrade-handling changes.
  • Update the directly affected GoDoc.
  • Reproduce the original failure with real Echo HTTP handlers on the unchanged base: 103 followed by 201 sends a final 200. Final regression tests also fail against exact base source via a Go overlay.
  • Cover repeated informational responses over HTTP/1.1 and HTTP/2, immediate delivery, final headers/body, hooks and byte accounting, HEAD, gzip thresholds, error handling, custom serializers, and terminal-status controls.
  • Check late informational responses against unwrapped net/http, including buffered gzip and delayed JSON paths.

Passing local commands, with isolated caches and bounded concurrency:

go test -mod=readonly -p=1 -parallel=1 -count=1 .\...
go vet -mod=readonly -p=1 .\...
go build -mod=readonly -p=1 .\...
staticcheck .\...
golint -set_exit_status . .\echotest .\middleware

Staticcheck: 2026.2.1. Golint: v0.0.0-20241112194109-818c5a804067. Lint commands used GOFLAGS=-mod=readonly -p=1; GOMAXPROCS=2 throughout. The focused new regression/control suite also passed three consecutive runs.

@aldas

aldas commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

could you describe real-world use-case for this feature.

@LeulTew

LeulTew commented Sep 18, 2026

Copy link
Copy Markdown
Author

One use case is early asset preloading on a server-rendered page: send 103 with a CSS preload Link while loading data, then send the page or a final 503 if loading fails. Echo currently marks the hint as committed and drops that later error response; the real-HTTP regression covers this. This fixes the existing ResponseWriter path, rather than adding a new API, and was found through a compatibility check rather than a production incident.

@aldas

aldas commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

this is important - these early status hints is HTTP/2 feature. In HTTP/1.1 writing 103 has to be separate response.

@LeulTew

LeulTew commented Sep 18, 2026

Copy link
Copy Markdown
Author

Yes, the 103 must be a separate interim response in HTTP/1.1, as in RFC 8297's example. Go's WriteHeader(103) already sends that header block; this change stops Echo treating it as the final response, so the later status and body can still be sent. The tests cover that exchange over both HTTP/1.1 and HTTP/2; the patch does not enable hints automatically or assume every client supports them.

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.

2 participants