Skip to content

Commit 2ab92c5

Browse files
committed
simplify and fix the json middleware example
1 parent b46df85 commit 2ab92c5

File tree

3 files changed

+84
-122
lines changed

3 files changed

+84
-122
lines changed

examples/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,5 @@ HTTP server cluster with automatic port allocation.
1717
## Running
1818

1919
```bash
20-
cd examples/<name>
21-
go run main.go
20+
go run ./examples/<name>
2221
```

examples/custom_middleware/example/jsonenforcer.go

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,66 +8,64 @@ import (
88
"github.com/robbyt/go-supervisor/runnables/httpserver"
99
)
1010

11-
// ResponseWrapper captures response data for transformation
11+
// ResponseBuffer captures response data for transformation
1212
//
1313
// This is a simple example for demonstration purposes and is not intended for
1414
// production use. Limitations:
1515
// - Does not preserve optional HTTP interfaces (http.Hijacker, http.Flusher, http.Pusher)
1616
// - Not safe for concurrent writes from multiple goroutines within the same request
1717
// - No memory limits on buffered content
1818
//
19-
// Each request gets its own ResponseWrapper instance, so different requests won't
19+
// Each request gets its own ResponseBuffer instance, so different requests won't
2020
// interfere with each other.
21-
type ResponseWrapper struct {
22-
original httpserver.ResponseWriter
23-
buffer *bytes.Buffer
24-
headers http.Header
25-
status int
21+
type ResponseBuffer struct {
22+
buffer *bytes.Buffer
23+
headers http.Header
24+
status int
2625
}
2726

28-
// NewResponseWrapper creates a new response wrapper
29-
func NewResponseWrapper(original httpserver.ResponseWriter) *ResponseWrapper {
30-
return &ResponseWrapper{
31-
original: original,
32-
buffer: new(bytes.Buffer),
33-
headers: make(http.Header),
34-
status: 0, // 0 means not set yet
27+
// NewResponseBuffer creates a new response buffer
28+
func NewResponseBuffer() *ResponseBuffer {
29+
return &ResponseBuffer{
30+
buffer: new(bytes.Buffer),
31+
headers: make(http.Header),
32+
status: 0, // 0 means not set yet
3533
}
3634
}
3735

3836
// Header implements http.ResponseWriter
39-
func (rw *ResponseWrapper) Header() http.Header {
40-
return rw.headers
37+
func (rb *ResponseBuffer) Header() http.Header {
38+
return rb.headers
4139
}
4240

4341
// Write implements http.ResponseWriter
44-
func (rw *ResponseWrapper) Write(data []byte) (int, error) {
45-
return rw.buffer.Write(data)
42+
func (rb *ResponseBuffer) Write(data []byte) (int, error) {
43+
return rb.buffer.Write(data)
4644
}
4745

4846
// WriteHeader implements http.ResponseWriter
49-
func (rw *ResponseWrapper) WriteHeader(statusCode int) {
50-
if rw.status == 0 {
51-
rw.status = statusCode
47+
func (rb *ResponseBuffer) WriteHeader(statusCode int) {
48+
if rb.status == 0 {
49+
rb.status = statusCode
5250
}
5351
}
5452

5553
// Status implements httpserver.ResponseWriter
56-
func (rw *ResponseWrapper) Status() int {
57-
if rw.status == 0 && rw.buffer.Len() > 0 {
54+
func (rb *ResponseBuffer) Status() int {
55+
if rb.status == 0 && rb.buffer.Len() > 0 {
5856
return http.StatusOK
5957
}
60-
return rw.status
58+
return rb.status
6159
}
6260

6361
// Written implements httpserver.ResponseWriter
64-
func (rw *ResponseWrapper) Written() bool {
65-
return rw.buffer.Len() > 0 || rw.status != 0
62+
func (rb *ResponseBuffer) Written() bool {
63+
return rb.buffer.Len() > 0 || rb.status != 0
6664
}
6765

6866
// Size implements httpserver.ResponseWriter
69-
func (rw *ResponseWrapper) Size() int {
70-
return rw.buffer.Len()
67+
func (rb *ResponseBuffer) Size() int {
68+
return rb.buffer.Len()
7169
}
7270

7371
// transformToJSON wraps non-JSON content in a JSON response
@@ -90,23 +88,25 @@ func transformToJSON(data []byte) ([]byte, error) {
9088
// Valid JSON responses are preserved as-is.
9189
func New() httpserver.HandlerFunc {
9290
return func(rp *httpserver.RequestProcessor) {
93-
// Wrap the response writer to capture output
94-
wrapper := NewResponseWrapper(rp.Writer())
95-
rp.SetWriter(wrapper)
91+
// Store original writer before buffering
92+
originalWriter := rp.Writer()
93+
94+
// Buffer the response to capture output
95+
buffer := NewResponseBuffer()
96+
rp.SetWriter(buffer)
9697

9798
// Continue to next middleware/handler
9899
rp.Next()
99100

100101
// RESPONSE PHASE: Transform response to JSON
101-
originalData := wrapper.buffer.Bytes()
102-
statusCode := wrapper.Status()
102+
originalData := buffer.buffer.Bytes()
103+
statusCode := buffer.Status()
103104
if statusCode == 0 {
104105
statusCode = http.StatusOK
105106
}
106107

107108
// Copy headers to original writer
108-
originalWriter := wrapper.original
109-
for key, values := range wrapper.Header() {
109+
for key, values := range buffer.Header() {
110110
for _, value := range values {
111111
originalWriter.Header().Add(key, value)
112112
}
@@ -120,7 +120,7 @@ func New() httpserver.HandlerFunc {
120120
}
121121

122122
// Transform captured data to JSON
123-
if len(originalData) == 0 && wrapper.status == 0 {
123+
if len(originalData) == 0 && buffer.status == 0 {
124124
return
125125
}
126126

examples/custom_middleware/example/jsonenforcer_test.go

Lines changed: 47 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ import (
99
"github.com/stretchr/testify/assert"
1010
)
1111

12+
func createTestRoute(t *testing.T, handler http.HandlerFunc) *httpserver.Route {
13+
t.Helper()
14+
route, err := httpserver.NewRouteFromHandlerFunc(
15+
"test",
16+
"/test",
17+
handler,
18+
New(),
19+
)
20+
assert.NoError(t, err)
21+
return route
22+
}
23+
1224
func TestJSONEnforcerMiddleware(t *testing.T) {
1325
t.Parallel()
1426

@@ -22,14 +34,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
2234
assert.NoError(t, err)
2335
}
2436

25-
// Create route with JSON enforcer middleware
26-
route, err := httpserver.NewRouteFromHandlerFunc(
27-
"test",
28-
"/test",
29-
textHandler,
30-
New(),
31-
)
32-
assert.NoError(t, err)
37+
route := createTestRoute(t, textHandler)
3338

3439
route.ServeHTTP(rec, req)
3540

@@ -53,13 +58,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
5358
assert.NoError(t, err)
5459
}
5560

56-
route, err := httpserver.NewRouteFromHandlerFunc(
57-
"test",
58-
"/test",
59-
jsonHandler,
60-
New(),
61-
)
62-
assert.NoError(t, err)
61+
route := createTestRoute(t, jsonHandler)
6362

6463
route.ServeHTTP(rec, req)
6564

@@ -83,13 +82,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
8382
assert.NoError(t, err)
8483
}
8584

86-
route, err := httpserver.NewRouteFromHandlerFunc(
87-
"test",
88-
"/test",
89-
errorHandler,
90-
New(),
91-
)
92-
assert.NoError(t, err)
85+
route := createTestRoute(t, errorHandler)
9386

9487
route.ServeHTTP(rec, req)
9588

@@ -112,13 +105,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
112105
w.WriteHeader(http.StatusNoContent)
113106
}
114107

115-
route, err := httpserver.NewRouteFromHandlerFunc(
116-
"test",
117-
"/test",
118-
emptyHandler,
119-
New(),
120-
)
121-
assert.NoError(t, err)
108+
route := createTestRoute(t, emptyHandler)
122109

123110
route.ServeHTTP(rec, req)
124111

@@ -139,13 +126,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
139126
assert.NoError(t, err)
140127
}
141128

142-
route, err := httpserver.NewRouteFromHandlerFunc(
143-
"test",
144-
"/test",
145-
notModifiedHandler,
146-
New(),
147-
)
148-
assert.NoError(t, err)
129+
route := createTestRoute(t, notModifiedHandler)
149130

150131
route.ServeHTTP(rec, req)
151132

@@ -164,13 +145,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
164145
assert.NoError(t, err)
165146
}
166147

167-
route, err := httpserver.NewRouteFromHandlerFunc(
168-
"test",
169-
"/test",
170-
htmlHandler,
171-
New(),
172-
)
173-
assert.NoError(t, err)
148+
route := createTestRoute(t, htmlHandler)
174149

175150
route.ServeHTTP(rec, req)
176151

@@ -192,13 +167,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
192167
assert.NoError(t, err)
193168
}
194169

195-
route, err := httpserver.NewRouteFromHandlerFunc(
196-
"test",
197-
"/test",
198-
arrayHandler,
199-
New(),
200-
)
201-
assert.NoError(t, err)
170+
route := createTestRoute(t, arrayHandler)
202171

203172
route.ServeHTTP(rec, req)
204173

@@ -222,13 +191,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
222191
assert.NoError(t, err)
223192
}
224193

225-
route, err := httpserver.NewRouteFromHandlerFunc(
226-
"test",
227-
"/test",
228-
malformedHandler,
229-
New(),
230-
)
231-
assert.NoError(t, err)
194+
route := createTestRoute(t, malformedHandler)
232195

233196
route.ServeHTTP(rec, req)
234197

@@ -255,13 +218,7 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
255218
assert.NoError(t, err)
256219
}
257220

258-
route, err := httpserver.NewRouteFromHandlerFunc(
259-
"test",
260-
"/test",
261-
multiWriteHandler,
262-
New(),
263-
)
264-
assert.NoError(t, err)
221+
route := createTestRoute(t, multiWriteHandler)
265222

266223
route.ServeHTTP(rec, req)
267224

@@ -274,33 +231,39 @@ func TestJSONEnforcerMiddleware(t *testing.T) {
274231
"all writes should be buffered and wrapped",
275232
)
276233
})
277-
}
278-
279-
// Test compliance with the middleware framework
280-
func TestJSONEnforcerCompliance(t *testing.T) {
281-
// This is a placeholder test that would import the compliance framework
282-
// In practice, you would add this to runnables/httpserver/middleware/compliance_test.go
283-
jsonMiddleware := New()
284234

285-
// Test basic compliance: calls Next()
286-
t.Run("calls Next()", func(t *testing.T) {
235+
t.Run("preserves headers from handler", func(t *testing.T) {
287236
req := httptest.NewRequest("GET", "/test", nil)
288237
rec := httptest.NewRecorder()
289238

290-
called := false
291-
mockHandler := func(w http.ResponseWriter, r *http.Request) {
292-
called = true
239+
headerHandler := func(w http.ResponseWriter, r *http.Request) {
240+
w.Header().Set("X-Custom-Header", "test-value")
241+
w.Header().Set("X-Another-Header", "another-value")
242+
_, err := w.Write([]byte("Hello World"))
243+
assert.NoError(t, err)
293244
}
294245

295-
route, err := httpserver.NewRouteFromHandlerFunc(
296-
"test",
297-
"/test",
298-
mockHandler,
299-
jsonMiddleware,
300-
)
301-
assert.NoError(t, err)
246+
route := createTestRoute(t, headerHandler)
247+
route.ServeHTTP(rec, req)
302248

249+
assert.Equal(t, "application/json", rec.Header().Get("Content-Type"))
250+
assert.Equal(t, "test-value", rec.Header().Get("X-Custom-Header"))
251+
assert.Equal(t, "another-value", rec.Header().Get("X-Another-Header"))
252+
assert.Contains(t, rec.Body.String(), `{"response":"Hello World"}`)
253+
})
254+
255+
t.Run("handles empty response", func(t *testing.T) {
256+
req := httptest.NewRequest("GET", "/test", nil)
257+
rec := httptest.NewRecorder()
258+
259+
emptyHandler := func(w http.ResponseWriter, r *http.Request) {
260+
// Handler does nothing - no writes, no status
261+
}
262+
263+
route := createTestRoute(t, emptyHandler)
303264
route.ServeHTTP(rec, req)
304-
assert.True(t, called, "should call the next handler")
265+
266+
assert.Equal(t, http.StatusOK, rec.Code)
267+
assert.Empty(t, rec.Body.String(), "empty response should remain empty")
305268
})
306269
}

0 commit comments

Comments
 (0)