feat: release v0.27 unified HTTP limits and safe request shutdown
This commit is contained in:
@@ -420,7 +420,9 @@ func (s *Server) serveConnection(baseCtx context.Context, state *connection) {
|
||||
status = protocolErr.status
|
||||
message = protocolErr.message + "\n"
|
||||
}
|
||||
_ = writeSimple(state.conn, status, message, true, s.WriteTimeout)
|
||||
if writeSimple(state.conn, status, message, true, s.WriteTimeout) == nil && status == http.StatusRequestEntityTooLarge {
|
||||
closeRejectedBody(baseCtx, state.conn)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -482,13 +484,15 @@ func (s *Server) serveConnection(baseCtx context.Context, state *connection) {
|
||||
}
|
||||
handler.ServeHTTP(writer, request)
|
||||
var bodyErr error
|
||||
if body, ok := request.Body.(*requestBody); ok && !body.done && body.err == nil && writer.status == http.StatusRequestEntityTooLarge {
|
||||
rejectedBody := false
|
||||
if body, ok := request.Body.(*requestBody); ok && !body.done && body.err == nil && (writer.bodyTooLarge || writer.status == http.StatusRequestEntityTooLarge) {
|
||||
// An application can reject size from the headers alone when its
|
||||
// body limit is below the transport limit. Waiting to
|
||||
// drain a slow or absent body would withhold the rejection response.
|
||||
// Abandon it and close; unread bytes can never become a next request.
|
||||
body.closed = true
|
||||
closeAfter = true
|
||||
rejectedBody = true
|
||||
} else {
|
||||
bodyErr = request.Body.Close()
|
||||
}
|
||||
@@ -538,11 +542,30 @@ func (s *Server) serveConnection(baseCtx context.Context, state *connection) {
|
||||
s.writerPool.Put(writer)
|
||||
shouldClose = s.finishRequest(state) || shouldClose
|
||||
if shouldClose {
|
||||
if rejectedBody {
|
||||
closeRejectedBody(baseCtx, state.conn)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Half-close after flushing a rejection, then allow the peer to consume it
|
||||
// before closing a socket with unread upload data. As in net/http, immediate
|
||||
// full close can cause a TCP reset to hide the response on BSD systems. This
|
||||
// never waits for the body and cancellation bounds the additional grace.
|
||||
func closeRejectedBody(ctx context.Context, conn net.Conn) {
|
||||
if writer, ok := conn.(interface{ CloseWrite() error }); ok {
|
||||
_ = writer.CloseWrite()
|
||||
}
|
||||
timer := time.NewTimer(500 * time.Millisecond)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// Publish the idle transition under the same lock that starts shutdown. Either
|
||||
// Shutdown observes an idle connection and closes it, or this request observes
|
||||
// shutdown and closes it instead of starting another Keep-Alive read.
|
||||
@@ -1055,6 +1078,7 @@ func writeSimple(conn net.Conn, status int, body string, closeAfter bool, timeou
|
||||
|
||||
type responseWriter struct {
|
||||
header http.Header
|
||||
bodyTooLarge bool
|
||||
status int
|
||||
wroteHeader bool
|
||||
body bytes.Buffer
|
||||
@@ -1081,6 +1105,7 @@ func (w *responseWriter) reset(method string) {
|
||||
}
|
||||
w.status = 0
|
||||
w.wroteHeader = false
|
||||
w.bodyTooLarge = false
|
||||
if w.body.Cap() > maxPooledResponseBytes {
|
||||
w.body = bytes.Buffer{}
|
||||
} else {
|
||||
@@ -1110,6 +1135,10 @@ func (w *responseWriter) configure(request *http.Request, conn net.Conn, closeAf
|
||||
|
||||
func (w *responseWriter) Header() http.Header { return w.header }
|
||||
|
||||
// RequestBodyTooLarge preserves semantic body rejection independently from a
|
||||
// project's chosen HTTP response status. Called through the optional writer hook.
|
||||
func (w *responseWriter) RequestBodyTooLarge() { w.bodyTooLarge = true }
|
||||
|
||||
func (w *responseWriter) WriteHeader(status int) {
|
||||
if w.wroteHeader {
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user