Files
NekoPunch 7b02f51320 deps: bump testcontainers-go to v0.44.0 (#1237)
Part of #1230

When reusing an already-running Ryuk reaper, testcontainers-go v0.43.0
waits only for its Docker port mapping. Docker exposes that port before
Ryuk is listening, so a second package can connect too early and lose
the handshake with `read ack: EOF`. That is
[testcontainers-go#3743](https://github.com/testcontainers/testcontainers-go/issues/3743);
v0.44.0 also waits for the reaper's `Started` log line
([#3761](https://github.com/testcontainers/testcontainers-go/pull/3761)).

A package whose handshake fails is not counted as a Ryuk client but its
containers still carry the shared session label, so another package
exiting can delete a database that is still in use — the failure
reported in #1230.

## Scope

This helps local runs, where the reaper stays on. **It does not fix CI
on its own**, so it is deliberately separate from #1235, which disables
the reaper for `run-tests`.

Measured on a 4-core Linux VM with cold build caches, which staggers
package start times the way CI does:

```
v0.43.0, 3 rounds   handshake failures in 3/3 rounds; one round cascaded
v0.44.0, 3 rounds   no handshake failures; database tests still unavailable in 3/3 rounds
```

Under v0.44.0 the failures move to `wait for reaper <id>: context
deadline exceeded` — a late package finds a reaper that is already
shutting down, and one readiness probe (`defaultStartupTimeout`, 60s)
outlives the whole reaper retry budget (`MaxElapsedTime`, 20s), so the
retry loop never gets a second attempt. The remaining fail-open behind
all of this is
[#3827](https://github.com/testcontainers/testcontainers-go/issues/3827),
still open upstream.

## Diff size

Four lines of `go.mod`. The rest is `go mod vendor` output: v0.44.0
pulls newer `moby/client`, `gopsutil`, and `otelhttp`, and `otelhttp`
moves `otel/semconv` from v1.39.0 to v1.41.0. Insertions and deletions
nearly cancel because most of it is a directory swap and one generated
`httpsnoop` file being merged into another.

```
go.mod, go.sum      62 lines
vendor/             61 files, 17356 +/17490 -
```

`hack/verify/go-modules.sh`, `licenses.sh`, `boilerplate.sh`, and
`gofmt.sh` all pass; `go test -race ./cmd/ateapi/...` is green with no
silently skipped database tests.

---

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-08-26 21:51:25 -07:00
..
2026-06-02 20:02:51 -07:00
2026-06-02 20:02:51 -07:00
2026-06-02 20:02:51 -07:00
2026-06-02 20:02:51 -07:00
2026-06-02 20:02:51 -07:00

httpsnoop

Package httpsnoop provides an easy way to capture http related metrics (i.e. response time, bytes written, and http status code) from your application's http.Handlers.

Doing this requires non-trivial wrapping of the http.ResponseWriter interface, which is also exposed for users interested in a more low-level API.

Go Reference Build Status

Usage Example

// myH is your app's http handler, perhaps a http.ServeMux or similar.
var myH http.Handler
// wrappedH wraps myH in order to log every request.
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	m := httpsnoop.CaptureMetrics(myH, w, r)
	log.Printf(
		"%s %s (code=%d dt=%s written=%d)",
		r.Method,
		r.URL,
		m.Code,
		m.Duration,
		m.Written,
	)
})
http.ListenAndServe(":8080", wrappedH)

Why this package exists

Instrumenting an application's http.Handler is surprisingly difficult.

However if you google for e.g. "capture ResponseWriter status code" you'll find lots of advise and code examples that suggest it to be a fairly trivial undertaking. Unfortunately everything I've seen so far has a high chance of breaking your application.

The main problem is that a http.ResponseWriter often implements additional interfaces such as http.Flusher, http.CloseNotifier, http.Hijacker, http.Pusher, and io.ReaderFrom. So the naive approach of just wrapping http.ResponseWriter in your own struct that also implements the http.ResponseWriter interface will hide the additional interfaces mentioned above. This has a high change of introducing subtle bugs into any non-trivial application.

Another approach I've seen people take is to return a struct that implements all of the interfaces above. However, that's also problematic, because it's difficult to fake some of these interfaces behaviors when the underlying http.ResponseWriter doesn't have an implementation. It's also dangerous, because an application may choose to operate differently, merely because it detects the presence of these additional interfaces.

This package solves this problem by checking which additional interfaces a http.ResponseWriter implements, returning a wrapped version implementing the exact same set of interfaces.

Additionally this package properly handles edge cases such as WriteHeader not being called, or called more than once, as well as concurrent calls to http.ResponseWriter methods, and even calls happening after the wrapped ServeHTTP has already returned.

Unfortunately this package is not perfect either. It's possible that it is still missing some interfaces provided by the go core (let me know if you find one), and it won't work for applications adding their own interfaces into the mix. You can however use httpsnoop.Unwrap(w) to access the underlying http.ResponseWriter and type-assert the result to its other interfaces.

However, hopefully the explanation above has sufficiently scared you of rolling your own solution to this problem. httpsnoop may still break your application, but at least it tries to avoid it as much as possible.

Anyway, the real problem here is that smuggling additional interfaces inside http.ResponseWriter is a problematic design choice, but it probably goes as deep as the Go language specification itself. But that's okay, I still prefer Go over the alternatives ;).

Performance

BenchmarkBaseline-8      	   20000	     94912 ns/op
BenchmarkCaptureMetrics-8	   20000	     95461 ns/op

As you can see, using CaptureMetrics on a vanilla http.Handler introduces an overhead of ~500 ns per http request on my machine. However, the margin of error appears to be larger than that, therefor it should be reasonable to assume that the overhead introduced by CaptureMetrics is absolutely negligible.

License

MIT