Skip to content

Commit 245f9dd

Browse files
committed
cleanly handle http.MaxBytesError for enforcing req payload size
If a middleware applies `http.MaxBytesReader` and we encounter an `*http.MaxBytesError` when reading the request body, return an HTTP 413 with a clean error.
1 parent 76cddfc commit 245f9dd

3 files changed

Lines changed: 36 additions & 1 deletion

File tree

apiendpoint/api_endpoint.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ func executeAPIEndpoint[TReq any, TResp any](w http.ResponseWriter, r *http.Requ
139139
if r.Method != http.MethodGet {
140140
reqData, err := io.ReadAll(r.Body)
141141
if err != nil {
142+
var maxBytesErr *http.MaxBytesError
143+
if errors.As(err, &maxBytesErr) {
144+
return apierror.NewRequestEntityTooLarge("Request entity too large.")
145+
}
142146
return fmt.Errorf("error reading request body: %w", err)
143147
}
144148

apiendpoint/api_endpoint_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ func TestMountAndServe(t *testing.T) {
7272
requireStatusAndJSONResponse(t, http.StatusOK, &getResponse{Message: "Hello."}, bundle.recorder)
7373
})
7474

75+
t.Run("MaxBytesErrorHandling", func(t *testing.T) {
76+
t.Parallel()
77+
78+
mux, bundle := setup(t)
79+
80+
payload := mustMarshalJSON(t, &postRequest{Message: "Hello."})
81+
82+
req := httptest.NewRequest(http.MethodPost, "/api/post-endpoint/123", bytes.NewBuffer(payload))
83+
req.Body = http.MaxBytesReader(bundle.recorder, io.NopCloser(bytes.NewReader(payload)), int64(len(payload)-1))
84+
mux.ServeHTTP(bundle.recorder, req)
85+
requireStatusAndJSONResponse(t, http.StatusRequestEntityTooLarge, &apierror.APIError{Message: "Request entity too large."}, bundle.recorder)
86+
})
87+
7588
t.Run("MethodNotAllowed", func(t *testing.T) {
7689
t.Parallel()
7790

@@ -302,9 +315,10 @@ func (a *getEndpoint) Execute(_ context.Context, req *getRequest) (*getResponse,
302315

303316
type postEndpoint struct {
304317
Endpoint[postRequest, postResponse]
318+
MaxBodyBytes int64
305319
}
306320

307-
func (*postEndpoint) Meta() *EndpointMeta {
321+
func (a *postEndpoint) Meta() *EndpointMeta {
308322
return &EndpointMeta{
309323
Pattern: "POST /api/post-endpoint/{id}",
310324
StatusCode: http.StatusCreated,

apierror/api_error.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,23 @@ func NewNotFoundf(format string, a ...any) *NotFound {
131131
return NewNotFound(fmt.Sprintf(format, a...))
132132
}
133133

134+
//
135+
// RequestEntityTooLarge
136+
//
137+
138+
type RequestEntityTooLarge struct { //nolint:errname
139+
APIError
140+
}
141+
142+
func NewRequestEntityTooLarge(message string) *RequestEntityTooLarge {
143+
return &RequestEntityTooLarge{
144+
APIError: APIError{
145+
Message: message,
146+
StatusCode: http.StatusRequestEntityTooLarge,
147+
},
148+
}
149+
}
150+
134151
//
135152
// ServiceUnavailable
136153
//

0 commit comments

Comments
 (0)