9c88801a12
Back in the before time, the best practices surrounding usage of Context weren't quite worked out. We defined our own type to make usage easier. As this packaged was used elsewhere, it make it more and more challenging to integrate with the forked `Context` type. Now that it is available in the standard library, we can just use that one directly. To make usage more consistent, we now use `dcontext` when referring to the distribution context package. Signed-off-by: Stephen J Day <stephen.day@docker.com>
72 lines
2.3 KiB
Go
72 lines
2.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
|
|
dcontext "github.com/docker/distribution/context"
|
|
)
|
|
|
|
// closeResources closes all the provided resources after running the target
|
|
// handler.
|
|
func closeResources(handler http.Handler, closers ...io.Closer) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
for _, closer := range closers {
|
|
defer closer.Close()
|
|
}
|
|
handler.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// copyFullPayload copies the payload of an HTTP request to destWriter. If it
|
|
// receives less content than expected, and the client disconnected during the
|
|
// upload, it avoids sending a 400 error to keep the logs cleaner.
|
|
//
|
|
// The copy will be limited to `limit` bytes, if limit is greater than zero.
|
|
func copyFullPayload(ctx context.Context, responseWriter http.ResponseWriter, r *http.Request, destWriter io.Writer, limit int64, action string) error {
|
|
// Get a channel that tells us if the client disconnects
|
|
var clientClosed <-chan bool
|
|
if notifier, ok := responseWriter.(http.CloseNotifier); ok {
|
|
clientClosed = notifier.CloseNotify()
|
|
} else {
|
|
dcontext.GetLogger(ctx).Warnf("the ResponseWriter does not implement CloseNotifier (type: %T)", responseWriter)
|
|
}
|
|
|
|
var body = r.Body
|
|
if limit > 0 {
|
|
body = http.MaxBytesReader(responseWriter, body, limit)
|
|
}
|
|
|
|
// Read in the data, if any.
|
|
copied, err := io.Copy(destWriter, body)
|
|
if clientClosed != nil && (err != nil || (r.ContentLength > 0 && copied < r.ContentLength)) {
|
|
// Didn't receive as much content as expected. Did the client
|
|
// disconnect during the request? If so, avoid returning a 400
|
|
// error to keep the logs cleaner.
|
|
select {
|
|
case <-clientClosed:
|
|
// Set the response code to "499 Client Closed Request"
|
|
// Even though the connection has already been closed,
|
|
// this causes the logger to pick up a 499 error
|
|
// instead of showing 0 for the HTTP status.
|
|
responseWriter.WriteHeader(499)
|
|
|
|
dcontext.GetLoggerWithFields(ctx, map[interface{}]interface{}{
|
|
"error": err,
|
|
"copied": copied,
|
|
"contentLength": r.ContentLength,
|
|
}, "error", "copied", "contentLength").Error("client disconnected during " + action)
|
|
return errors.New("client disconnected")
|
|
default:
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
dcontext.GetLogger(ctx).Errorf("unknown error reading request payload: %v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|