@@ -425,7 +425,6 @@ type response struct {
425
425
reqBody io.ReadCloser
426
426
cancelCtx context.CancelFunc // when ServeHTTP exits
427
427
wroteHeader bool // a non-1xx header has been (logically) written
428
- wroteContinue bool // 100 Continue response was written
429
428
wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
430
429
wantsClose bool // HTTP request has Connection "close"
431
430
@@ -438,6 +437,7 @@ type response struct {
438
437
// against the main writer.
439
438
canWriteContinue atomic.Bool
440
439
writeContinueMu sync.Mutex
440
+ wroteContinue bool // 100 Continue response was written
441
441
442
442
w * bufio.Writer // buffers output in chunks to chunkWriter
443
443
cw chunkWriter
@@ -916,17 +916,7 @@ func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
916
916
if ecr .closed .Load () {
917
917
return 0 , ErrBodyReadAfterClose
918
918
}
919
- w := ecr .resp
920
- if ! w .wroteContinue && w .canWriteContinue .Load () && ! w .conn .hijacked () {
921
- w .wroteContinue = true
922
- w .writeContinueMu .Lock ()
923
- if w .canWriteContinue .Load () {
924
- w .conn .bufw .WriteString ("HTTP/1.1 100 Continue\r \n \r \n " )
925
- w .conn .bufw .Flush ()
926
- w .canWriteContinue .Store (false )
927
- }
928
- w .writeContinueMu .Unlock ()
929
- }
919
+ ecr .resp .writeContinueOnce ()
930
920
n , err = ecr .readCloser .Read (p )
931
921
if err == io .EOF {
932
922
ecr .sawEOF .Store (true )
@@ -1165,10 +1155,8 @@ func (w *response) WriteHeader(code int) {
1165
1155
// so it takes the non-informational path.
1166
1156
if code >= 100 && code <= 199 && code != StatusSwitchingProtocols {
1167
1157
// Prevent a potential race with an automatically-sent 100 Continue triggered by Request.Body.Read()
1168
- if code == 100 && w .canWriteContinue .Load () {
1169
- w .writeContinueMu .Lock ()
1170
- w .canWriteContinue .Store (false )
1171
- w .writeContinueMu .Unlock ()
1158
+ if code == 100 {
1159
+ w .disableContinue ()
1172
1160
}
1173
1161
1174
1162
writeStatusLine (w .conn .bufw , w .req .ProtoAtLeast (1 , 1 ), code , w .statusBuf [:])
@@ -1383,9 +1371,11 @@ func (cw *chunkWriter) writeHeader(p []byte) {
1383
1371
1384
1372
switch bdy := w .req .Body .(type ) {
1385
1373
case * expectContinueReader :
1374
+ bdy .resp .writeContinueMu .Lock ()
1386
1375
if bdy .resp .wroteContinue {
1387
1376
discard = true
1388
1377
}
1378
+ bdy .resp .writeContinueMu .Unlock ()
1389
1379
case * body :
1390
1380
bdy .mu .Lock ()
1391
1381
switch {
@@ -1625,15 +1615,7 @@ func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err er
1625
1615
return 0 , ErrHijacked
1626
1616
}
1627
1617
1628
- if w .canWriteContinue .Load () {
1629
- // Body reader wants to write 100 Continue but hasn't yet.
1630
- // Tell it not to. The store must be done while holding the lock
1631
- // because the lock makes sure that there is not an active write
1632
- // this very moment.
1633
- w .writeContinueMu .Lock ()
1634
- w .canWriteContinue .Store (false )
1635
- w .writeContinueMu .Unlock ()
1636
- }
1618
+ w .disableContinue ()
1637
1619
1638
1620
if ! w .wroteHeader {
1639
1621
w .WriteHeader (StatusOK )
@@ -1679,6 +1661,29 @@ func (w *response) finishRequest() {
1679
1661
}
1680
1662
}
1681
1663
1664
+ // disableContinue disables write of 100 Continue status.
1665
+ func (w * response ) disableContinue () {
1666
+ if w .canWriteContinue .Load () {
1667
+ w .writeContinueMu .Lock ()
1668
+ w .canWriteContinue .Store (false )
1669
+ w .writeContinueMu .Unlock ()
1670
+ }
1671
+ }
1672
+
1673
+ // writeContinueOnce writes 100 Continue status if allowed.
1674
+ func (w * response ) writeContinueOnce () {
1675
+ if w .canWriteContinue .Load () && ! w .conn .hijacked () {
1676
+ w .writeContinueMu .Lock ()
1677
+ if w .canWriteContinue .Load () {
1678
+ w .conn .bufw .WriteString ("HTTP/1.1 100 Continue\r \n \r \n " )
1679
+ w .conn .bufw .Flush ()
1680
+ w .canWriteContinue .Store (false )
1681
+ w .wroteContinue = true
1682
+ }
1683
+ w .writeContinueMu .Unlock ()
1684
+ }
1685
+ }
1686
+
1682
1687
// shouldReuseConnection reports whether the underlying TCP connection can be reused.
1683
1688
// It must only be called after the handler is done executing.
1684
1689
func (w * response ) shouldReuseConnection () bool {
@@ -1905,6 +1910,7 @@ func (c *conn) serve(ctx context.Context) {
1905
1910
if inFlightResponse != nil {
1906
1911
inFlightResponse .conn .r .abortPendingRead ()
1907
1912
inFlightResponse .reqBody .Close ()
1913
+ inFlightResponse .disableContinue ()
1908
1914
}
1909
1915
c .close ()
1910
1916
c .setState (c .rwc , StateClosed , runHooks )
@@ -2046,6 +2052,7 @@ func (c *conn) serve(ctx context.Context) {
2046
2052
return
2047
2053
}
2048
2054
w .finishRequest ()
2055
+ w .disableContinue ()
2049
2056
c .rwc .SetWriteDeadline (time.Time {})
2050
2057
if ! w .shouldReuseConnection () {
2051
2058
if w .requestBodyLimitHit || w .closedRequestBodyEarly () {
0 commit comments