Skip to content

Commit dc8fb20

Browse files
authored
refactor: minor tweaks to /drip implementation (#185)
A handful of drive-by tweaks to the `/drip` implementation: - Update docs to clarify intended uses (a helpful reminder for myself, if nothing else) - Ensure all bad requests use standard JSON error responses - Switch from binary to text content type This also includes a small fix to the internal testing library, dropping the `assert.RoughDuration()` helper in favor of an updated generic `assert.RoughlyEqual()` implementation that works with the `time.Duration` type.
1 parent 24529f4 commit dc8fb20

File tree

5 files changed

+28
-24
lines changed

5 files changed

+28
-24
lines changed

httpbin/handlers.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -610,8 +610,15 @@ func (h *HTTPBin) Delay(w http.ResponseWriter, r *http.Request) {
610610
h.RequestWithBody(w, r)
611611
}
612612

613-
// Drip returns data over a duration after an optional initial delay, then
614-
// (optionally) returns with the given status code.
613+
// Drip simulates a slow HTTP server by writing data over a given duration
614+
// after an optional initial delay.
615+
//
616+
// Because this endpoint is intended to simulate a slow HTTP connection, it
617+
// intentionally does NOT use chunked transfer encoding even though its
618+
// implementation writes the response incrementally.
619+
//
620+
// See Stream (/stream) or StreamBytes (/stream-bytes) for endpoints that
621+
// respond using chunked transfer encoding.
615622
func (h *HTTPBin) Drip(w http.ResponseWriter, r *http.Request) {
616623
q := r.URL.Query()
617624

@@ -660,7 +667,7 @@ func (h *HTTPBin) Drip(w http.ResponseWriter, r *http.Request) {
660667
}
661668

662669
if duration+delay > h.MaxDuration {
663-
http.Error(w, "Too much time", http.StatusBadRequest)
670+
writeError(w, http.StatusBadRequest, fmt.Errorf("too much time: %v+%v > %v", duration, delay, h.MaxDuration))
664671
return
665672
}
666673

@@ -682,23 +689,23 @@ func (h *HTTPBin) Drip(w http.ResponseWriter, r *http.Request) {
682689
}
683690
}
684691

685-
w.Header().Set("Content-Type", binaryContentType)
692+
w.Header().Set("Content-Type", textContentType)
686693
w.Header().Set("Content-Length", fmt.Sprintf("%d", numBytes))
687694
w.WriteHeader(code)
688695

696+
// what we write with each increment of the ticker
697+
b := []byte{'*'}
698+
689699
// special case when we do not need to pause between each write
690700
if pause == 0 {
691-
for i := int64(0); i < numBytes; i++ {
692-
w.Write([]byte{'*'})
693-
}
701+
w.Write(bytes.Repeat(b, int(numBytes)))
694702
return
695703
}
696704

697705
// otherwise, write response body byte-by-byte
698706
ticker := time.NewTicker(pause)
699707
defer ticker.Stop()
700708

701-
b := []byte{'*'}
702709
flusher := w.(http.Flusher)
703710
for i := int64(0); i < numBytes; i++ {
704711
w.Write(b)

httpbin/handlers_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ func testRequestWithBodyExpect100Continue(t *testing.T, verb, path string) {
738738
})
739739

740740
t.Run("zero content-length ignored", func(t *testing.T) {
741-
// The Go stdlib's Expect:100-continue handling requires either a a)
741+
// The Go stdlib's Expect:100-continue handling requires either a)
742742
// non-zero Content-Length header or b) Transfer-Encoding:chunked
743743
// header to be present. Otherwise, the Expect header is ignored and
744744
// the request is processed normally.
@@ -2037,7 +2037,7 @@ func TestDrip(t *testing.T) {
20372037
elapsed := time.Since(start)
20382038

20392039
assert.StatusCode(t, resp, test.code)
2040-
assert.ContentType(t, resp, binaryContentType)
2040+
assert.ContentType(t, resp, textContentType)
20412041
assert.Header(t, resp, "Content-Length", strconv.Itoa(test.numbytes))
20422042
if elapsed < test.duration {
20432043
t.Fatalf("expected minimum duration of %s, request took %s", test.duration, elapsed)
@@ -2125,7 +2125,7 @@ func TestDrip(t *testing.T) {
21252125
// (allowing for minor mismatch in local timers and server timers)
21262126
// after the first byte.
21272127
if i > 0 {
2128-
assert.RoughDuration(t, gotPause, wantPauseBetweenWrites, 3*time.Millisecond)
2128+
assert.RoughlyEqual(t, gotPause, wantPauseBetweenWrites, 3*time.Millisecond)
21292129
}
21302130
}
21312131

@@ -3240,7 +3240,7 @@ func TestSSE(t *testing.T) {
32403240
// (allowing for minor mismatch in local timers and server timers)
32413241
// after the first byte.
32423242
if i > 0 {
3243-
assert.RoughDuration(t, gotPause, wantPauseBetweenWrites, 3*time.Millisecond)
3243+
assert.RoughlyEqual(t, gotPause, wantPauseBetweenWrites, 3*time.Millisecond)
32443244
}
32453245

32463246
eventCount++

httpbin/static/index.html.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
<li><a href="{{.Prefix}}/deny"><code>{{.Prefix}}/deny</code></a> Denied by robots.txt file.</li>
8080
<li><a href="{{.Prefix}}/digest-auth/auth/user/password"><code>{{.Prefix}}/digest-auth/:qop/:user/:password</code></a> Challenges HTTP Digest Auth using default MD5 algorithm</li>
8181
<li><a href="{{.Prefix}}/digest-auth/auth/user/password/SHA-256"><code>{{.Prefix}}/digest-auth/:qop/:user/:password/:algorithm</code></a> Challenges HTTP Digest Auth using specified algorithm (MD5 or SHA-256)</li>
82-
<li><a href="{{.Prefix}}/drip?code=200&amp;numbytes=5&amp;duration=5"><code>{{.Prefix}}/drip?numbytes=n&amp;duration=s&amp;delay=s&amp;code=code</code></a> Drips data over a duration after an optional initial delay, then (optionally) returns with the given status code.</li>
82+
<li><a href="{{.Prefix}}/drip?code=200&amp;numbytes=5&amp;duration=5"><code>{{.Prefix}}/drip?numbytes=n&amp;duration=s&amp;delay=s&amp;code=code</code></a> Drips data over the given duration after an optional initial delay, simulating a slow HTTP server.</li>
8383
<li><a href="{{.Prefix}}/dump/request"><code>{{.Prefix}}/dump/request</code></a> Returns the given request in its HTTP/1.x wire approximate representation.</li>
8484
<li><a href="{{.Prefix}}/encoding/utf8"><code>{{.Prefix}}/encoding/utf8</code></a> Returns page containing UTF-8 data.</li>
8585
<li><a href="{{.Prefix}}/etag/etag"><code>{{.Prefix}}/etag/:etag</code></a> Assumes the resource has the given etag and responds to If-None-Match header with a 200 or 304 and If-Match with a 200 or 412 as appropriate.</li>

httpbin/websocket/websocket_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ func TestConnectionLimits(t *testing.T) {
281281
elapsed := time.Since(start)
282282

283283
assert.Error(t, err, io.EOF)
284-
assert.RoughDuration(t, elapsed, maxDuration, 25*time.Millisecond)
284+
assert.RoughlyEqual(t, elapsed, maxDuration, 25*time.Millisecond)
285285
}
286286
})
287287

@@ -363,11 +363,11 @@ func TestConnectionLimits(t *testing.T) {
363363
conn.Close()
364364

365365
assert.Equal(t, os.IsTimeout(err), true, "expected timeout error")
366-
assert.RoughDuration(t, elapsedClientTime, clientTimeout, 10*time.Millisecond)
366+
assert.RoughlyEqual(t, elapsedClientTime, clientTimeout, 10*time.Millisecond)
367367

368368
// wait for the server to finish
369369
wg.Wait()
370-
assert.RoughDuration(t, elapsedServerTime, clientTimeout, 10*time.Millisecond)
370+
assert.RoughlyEqual(t, elapsedServerTime, clientTimeout, 10*time.Millisecond)
371371
}
372372
})
373373
}

internal/testing/assert/assert.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,14 @@ func DurationRange(t *testing.T, got, min, max time.Duration) {
129129
}
130130
}
131131

132-
// RoughDuration asserts that a duration is within a certain tolerance of a
133-
// given value.
134-
func RoughDuration(t *testing.T, got, want time.Duration, tolerance time.Duration) {
135-
t.Helper()
136-
DurationRange(t, got, want-tolerance, want+tolerance)
132+
type Number interface {
133+
~int64 | ~float64
137134
}
138135

139-
// RoughlyEqual asserts that a float64 is within a certain tolerance.
140-
func RoughlyEqual(t *testing.T, got, want float64, epsilon float64) {
136+
// RoughlyEqual asserts that a numeric value is within a certain tolerance.
137+
func RoughlyEqual[T Number](t *testing.T, got, want T, epsilon T) {
141138
t.Helper()
142139
if got < want-epsilon || got > want+epsilon {
143-
t.Fatalf("expected value between %f and %f, got %f", want-epsilon, want+epsilon, got)
140+
t.Fatalf("expected value between %v and %v, got %v", want-epsilon, want+epsilon, got)
144141
}
145142
}

0 commit comments

Comments
 (0)