Skip to content

Add compressed request body echo endpoint#264

Closed
RomeoApps wants to merge 4 commits into
mccutchen:mainfrom
RomeoApps:codex/httpbin-echo-endpoint
Closed

Add compressed request body echo endpoint#264
RomeoApps wants to merge 4 commits into
mccutchen:mainfrom
RomeoApps:codex/httpbin-echo-endpoint

Conversation

@RomeoApps

Copy link
Copy Markdown

Summary

  • Add /echo to return the request body as plain text.
  • Decompress gzip/deflate request bodies before echoing them.
  • Keep decoded echo output bounded by MaxBodySize, including compressed payloads that expand past the cap.
  • List the endpoint on the index page.

Why

Some client test suites need a small HTTP echo endpoint that can verify request-body compression without depending on com.sun.net.httpserver or a custom local test server. This keeps that use case inside go-httpbin's reusable binary/server surface.

Validation

  • gofmt -w httpbin/httpbin.go httpbin/handlers.go httpbin/handlers_test.go
  • go test ./httpbin -run '^TestEcho$'
  • go test ./...
  • go test -race ./...
  • git diff --check

@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.85714% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.59%. Comparing base (536f214) to head (b1ec63a).

Files with missing lines Patch % Lines
httpbin/handlers.go 66.66% 6 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #264      +/-   ##
==========================================
- Coverage   94.81%   94.59%   -0.23%     
==========================================
  Files           9        9              
  Lines        2027     2055      +28     
==========================================
+ Hits         1922     1944      +22     
- Misses         62       65       +3     
- Partials       43       46       +3     
Files with missing lines Coverage Δ
httpbin/httpbin.go 100.00% <100.00%> (ø)
httpbin/handlers.go 97.67% <66.66%> (-0.62%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mccutchen mccutchen left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a useful addition to me, but my expectation of an /echo endpoint would be to echo the request body back verbatim (so a gzip'd body would be gzip'd on the way back out).

My first instinct would be make the auto-decoding optional, based on a ?decode query param, but maybe this implementation is what would be most useful most of the time, and we should instead support a ?raw query param that disables decoding? That could be deferred to a follow-up if/when there's an actual need to disable auto-decoding.

So, LGTM overall, but I have a couple of small questions/suggestions below.

Comment thread httpbin/handlers.go
Comment on lines +153 to +161
body, err := io.ReadAll(io.LimitReader(reader, h.MaxBodySize+1))
if err != nil {
writeError(w, http.StatusBadRequest, fmt.Errorf("error reading request body: %w", err))
return
}
if int64(len(body)) > h.MaxBodySize {
writeError(w, http.StatusBadRequest, fmt.Errorf("request body exceeds maximum size of %d bytes", h.MaxBodySize))
return
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the second check here necessary? My epxectation is that io.LimitReader will take effect after incoming bytes are decoded from gzip/deflate and should effectively enforce the limit itself.

Comment thread httpbin/handlers.go
reader = zr
}

body, err := io.ReadAll(io.LimitReader(reader, h.MaxBodySize+1))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why h.MaxBodySize+1 here?

Comment thread httpbin/handlers.go Outdated
return
}

writeResponse(w, http.StatusOK, textContentType, body)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should take the content-type from the incoming request (if given) and falling back to plain text by default. That way a POST /echo with Content-Type: application/json (or image/jpeg or whatever) would have an appropriate content type in the response.

@RomeoApps

Copy link
Copy Markdown
Author

Pushed follow-up commit 88c38b6 addressing the review notes:

  • echo responses now preserve the incoming Content-Type, falling back to text/plain; charset=utf-8 when it is absent
  • kept the decoded-size MaxBodySize+1 read, but added an inline comment plus exact-limit/over-limit gzip tests so the truncation guard is explicit
  • added invalid gzip/deflate tests to cover the bad decode branches and improve patch coverage

Validation with local Go 1.25.0:

  • go test ./httpbin -run '^TestEcho$'
  • go test ./httpbin -coverprofile=/tmp/go-httpbin-echo-cover.out (Echo 92.6%)
  • go test ./...
  • go test -race ./...
  • git diff --check

I left the optional raw/no-decode switch out of this commit since you mentioned it could be deferred unless there is a concrete need.

@RomeoApps

RomeoApps commented Jun 22, 2026

Copy link
Copy Markdown
Author

Pushed follow-up commit 0ae3268 adding the concrete ?raw option discussed above.

  • default /echo behavior is unchanged: gzip/deflate request bodies are decoded before echoing, which is still the path I need for the Requests-Scala test-server use case
  • /echo?raw skips request-body decoding and echoes the incoming bytes, so a gzip payload can round-trip byte-for-byte
  • updated the index copy and added focused tests for valid and invalid gzip bodies in raw mode

Validation with local Go 1.26.4:

  • go test ./httpbin -run ^TestEcho$
  • go test ./httpbin -coverprofile=/tmp/go-httpbin-echo-raw-cover.out (httpbin package 98.2%, Echo 92.9%)
  • go test ./...
  • go test -race ./...
  • live binary smoke: decoded gzip returns plain text, ?raw returns byte-for-byte gzip payload, invalid gzip remains 400 by default and returns the raw body with ?raw
  • git diff --check

@RomeoApps

RomeoApps commented Jun 22, 2026

Copy link
Copy Markdown
Author

Pushed test-only follow-up commit cc82227 to tighten the /echo coverage surface without changing endpoint behavior.

What changed:

  • added coverage for the Echo body-read error path
  • asserted the index page documents /echo and ?raw

Local validation with Go 1.26.4:

  • gofmt -w httpbin/handlers_test.go
  • go test ./httpbin -run '^TestEcho$' -coverprofile=/tmp/go-httpbin-echo-coverage-fix.out (Echo now reports 100% in that profile)
  • go test ./httpbin -run '^TestIndex$'
  • go test ./httpbin -coverprofile=/tmp/go-httpbin-package-coverage-fix.out (httpbin package 98.4%, Echo 100%)
  • go test ./...
  • go test -race ./...
  • git diff --check

I am not assuming the hosted Codecov status until it runs on the pushed commit.

Romeo / romeoapps.com

@mccutchen

Copy link
Copy Markdown
Owner

Seems like a potentially useful endpoint, but I'm not interested in contributions from automated agents. Come back with a human.

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