@@ -21,6 +21,7 @@ import (
2121 "strings"
2222 "sync"
2323 "testing"
24+ "time"
2425)
2526
2627func Test_parseRequestURL (t * testing.T ) {
@@ -1096,6 +1097,7 @@ func TestMiddleware_multipartWriteFormData(t *testing.T) {
10961097 })
10971098
10981099 req := & Request {
1100+ mu : new (sync.Mutex ),
10991101 Header : http.Header {},
11001102 isMultiPart : true ,
11011103 multipartFields : []* MultipartField {
@@ -1262,3 +1264,105 @@ func TestMiddlewareCoverage(t *testing.T) {
12621264 err1 := createRawRequest (c , req1 )
12631265 assertTrue (t , strings .Contains (err1 .Error (), "invalid character" ), "invalid URL error expected" )
12641266}
1267+
1268+ func TestMultipartEarlyResponseRace (t * testing.T ) {
1269+ client := NewWithClient (& http.Client {
1270+ Transport : earlyResponseTransport {},
1271+ })
1272+
1273+ for range 200 {
1274+ _ , _ = client .R ().
1275+ SetMultipartFields (& MultipartField {
1276+ Name : "audio" ,
1277+ FileName : "audio.wav" ,
1278+ ContentType : "audio/wav" ,
1279+ Reader : bytes .NewReader (make ([]byte , 1 << 20 )),
1280+ }).
1281+ Post ("http://resty.test/upload" )
1282+ }
1283+ }
1284+
1285+ type earlyResponseTransport struct {}
1286+
1287+ func (earlyResponseTransport ) RoundTrip (req * http.Request ) (* http.Response , error ) {
1288+ // net/http allows a RoundTripper to consume and close the request body
1289+ // asynchronously after RoundTrip returns.
1290+ go func () {
1291+ _ , _ = io .Copy (io .Discard , req .Body )
1292+ _ = req .Body .Close ()
1293+ }()
1294+
1295+ return & http.Response {
1296+ StatusCode : http .StatusUnprocessableEntity ,
1297+ Status : "422 Unprocessable Entity" ,
1298+ Header : make (http.Header ),
1299+ Body : io .NopCloser (strings .NewReader ("rejected" )),
1300+ Request : req ,
1301+ }, nil
1302+ }
1303+
1304+ type blockingReadCloser struct {
1305+ started chan struct {}
1306+ release chan struct {}
1307+ once sync.Once
1308+ }
1309+
1310+ func newBlockingReadCloser () * blockingReadCloser {
1311+ return & blockingReadCloser {
1312+ started : make (chan struct {}),
1313+ release : make (chan struct {}),
1314+ }
1315+ }
1316+
1317+ func (r * blockingReadCloser ) Read ([]byte ) (int , error ) {
1318+ select {
1319+ case <- r .started :
1320+ default :
1321+ close (r .started )
1322+ }
1323+
1324+ <- r .release
1325+
1326+ return 0 , io .EOF
1327+ }
1328+
1329+ func (r * blockingReadCloser ) Close () error {
1330+ r .once .Do (func () {
1331+ close (r .release )
1332+ })
1333+
1334+ return nil
1335+ }
1336+
1337+ func TestMultipartReturnsAfterEarlyResponse (t * testing.T ) {
1338+ // RoundTrip returns before the request body is fully written, leaving the
1339+ // multipart producer blocked inside Reader.Read. stopMultipart must cancel
1340+ // production and close closable field readers so execute does not hang on
1341+ // multipartErrChan (see #1186).
1342+ reader := newBlockingReadCloser ()
1343+ client := NewWithClient (& http.Client {
1344+ Transport : earlyResponseTransport {},
1345+ })
1346+
1347+ done := make (chan error , 1 )
1348+ go func () {
1349+ _ , err := client .R ().
1350+ SetMultipartFields (& MultipartField {
1351+ Name : "audio" ,
1352+ FileName : "audio.wav" ,
1353+ ContentType : "audio/wav" ,
1354+ Reader : reader ,
1355+ }).
1356+ Post ("http://resty.test/upload" )
1357+ done <- err
1358+ }()
1359+
1360+ select {
1361+ case <- done :
1362+ // returned after early response without caller unblocking the reader
1363+ case <- time .After (2 * time .Second ):
1364+ _ = reader .Close ()
1365+ <- done
1366+ t .Fatal ("multipart request did not return after the transport responded" )
1367+ }
1368+ }
0 commit comments