@@ -325,6 +325,44 @@ func TestMultipartFormStreamDrainCollectsTrailingFieldsAndClosesBody(t *testing.
325325 require .ErrorIs (t , err , io .EOF )
326326}
327327
328+ func TestMultipartFormStreamCloseIgnoresBodyCloseEOF (t * testing.T ) {
329+ // Since go1.27.0, a net/http server request body reports io.EOF from Close
330+ // when it discards the unread remainder of the request. The stream must not
331+ // pass that on as a failure.
332+ body , contentType := orderedMultipartBody (t ,
333+ orderedFile {field : testFieldFile , filename : streamedFilename , content : "payload" },
334+ orderedField {name : "after" , value : "value" },
335+ )
336+ trackedBody := & observableReadCloser {Reader : body , closeErr : io .EOF }
337+ request := httptest .NewRequestWithContext (t .Context (), http .MethodPost , testUploadPath , nil )
338+ request .Body = trackedBody
339+ request .Header .Set (HeaderContentType , contentType )
340+ stream , err := NewMultipartFormStream (request )
341+ require .NoError (t , err )
342+
343+ _ , err = stream .NextFile ()
344+ require .NoError (t , err )
345+ require .NoError (t , stream .Drain ())
346+ assert .EqualT (t , "value" , request .Form .Get ("after" ))
347+ assert .TrueT (t , trackedBody .Closed ())
348+ }
349+
350+ func TestMultipartFormStreamCloseReportsBodyCloseError (t * testing.T ) {
351+ body , contentType := orderedMultipartBody (t ,
352+ orderedFile {field : testFieldFile , filename : streamedFilename , content : "payload" },
353+ )
354+ closeErr := stderrors .New ("close failed" )
355+ trackedBody := & observableReadCloser {Reader : body , closeErr : closeErr }
356+ request := httptest .NewRequestWithContext (t .Context (), http .MethodPost , testUploadPath , nil )
357+ request .Body = trackedBody
358+ request .Header .Set (HeaderContentType , contentType )
359+ stream , err := NewMultipartFormStream (request )
360+ require .NoError (t , err )
361+
362+ require .ErrorIs (t , stream .Close (), closeErr )
363+ assert .TrueT (t , trackedBody .Closed ())
364+ }
365+
328366func TestMultipartFormStreamCloseAbortsWithoutDraining (t * testing.T ) {
329367 body , contentType := orderedMultipartBody (t ,
330368 orderedFile {field : testFieldFile , filename : streamedFilename , content : "payload" },
@@ -626,6 +664,8 @@ func orderedMultipartBody(t *testing.T, parts ...orderedMultipartPart) (*bytes.B
626664type observableReadCloser struct {
627665 io.Reader
628666
667+ closeErr error
668+
629669 mu sync.Mutex
630670 closed bool
631671}
@@ -635,7 +675,7 @@ func (r *observableReadCloser) Close() error {
635675 defer r .mu .Unlock ()
636676 r .closed = true
637677
638- return nil
678+ return r . closeErr
639679}
640680
641681func (r * observableReadCloser ) Closed () bool {
0 commit comments