Skip to content

Commit

Permalink
Merge pull request #650 from danielgtaylor/handle-client-disconnect
Browse files Browse the repository at this point in the history
fix: do not panic on client disconnect
  • Loading branch information
danielgtaylor authored Nov 13, 2024
2 parents bea7c1a + f516eb6 commit f71eb50
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 0 deletions.
7 changes: 7 additions & 0 deletions huma.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,13 @@ func transformAndWrite(api API, ctx Context, status int, ct string, body any) er
ctx.SetStatus(status)
if status != http.StatusNoContent && status != http.StatusNotModified {
if merr := api.Marshal(ctx.BodyWriter(), ct, tval); merr != nil {
if errors.Is(ctx.Context().Err(), context.Canceled) {
// The client disconnected, so don't bother writing anything. Attempt
// to set the status in case it'll get logged. Technically this was
// not a normal successful request.
ctx.SetStatus(499)
return nil
}
ctx.BodyWriter().Write([]byte("error marshaling response"))
// When including tval in the panic message, the server may become unresponsive for some time if the value is very large
// therefore, it has been removed from the panic message
Expand Down
33 changes: 33 additions & 0 deletions huma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2117,6 +2117,39 @@ func TestCustomError(t *testing.T) {
assert.Equal(t, `{"$schema":"http://localhost/schemas/MyError.json","message":"not found","details":["some-other-error"]}`+"\n", resp.Body.String())
}

type BrokenWriter struct {
http.ResponseWriter
}

func (br *BrokenWriter) Write(p []byte) (n int, err error) {
return 0, errors.New("failed writing")
}

func TestClientDisconnect(t *testing.T) {
_, api := humatest.New(t, huma.DefaultConfig("Test API", "1.0.0"))

huma.Get(api, "/error", func(ctx context.Context, i *struct{}) (*struct {
Body string
}, error) {
return &struct{ Body string }{Body: "test"}, nil
})

// Create and immediately cancel the context. This simulates a client
// that has disconnected.
ctx, cancel := context.WithCancel(context.Background())
cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "/error", nil)

// Also make the response writer fail when writing.
recorder := httptest.NewRecorder()
resp := &BrokenWriter{recorder}

// We do not want any panics as this is not a real error.
assert.NotPanics(t, func() {
api.Adapter().ServeHTTP(resp, req)
})
}

type NestedResolversStruct struct {
Field2 string `json:"field2"`
}
Expand Down

0 comments on commit f71eb50

Please sign in to comment.