1package handlers 2 3import ( 4 "context" 5 "errors" 6 "io" 7 "net/http" 8 9 dcontext "github.com/docker/distribution/context" 10) 11 12// closeResources closes all the provided resources after running the target 13// handler. 14func closeResources(handler http.Handler, closers ...io.Closer) http.Handler { 15 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 for _, closer := range closers { 17 defer closer.Close() 18 } 19 handler.ServeHTTP(w, r) 20 }) 21} 22 23// copyFullPayload copies the payload of an HTTP request to destWriter. If it 24// receives less content than expected, and the client disconnected during the 25// upload, it avoids sending a 400 error to keep the logs cleaner. 26// 27// The copy will be limited to `limit` bytes, if limit is greater than zero. 28func copyFullPayload(ctx context.Context, responseWriter http.ResponseWriter, r *http.Request, destWriter io.Writer, limit int64, action string) error { 29 // Get a channel that tells us if the client disconnects 30 clientClosed := r.Context().Done() 31 var body = r.Body 32 if limit > 0 { 33 body = http.MaxBytesReader(responseWriter, body, limit) 34 } 35 36 // Read in the data, if any. 37 copied, err := io.Copy(destWriter, body) 38 if clientClosed != nil && (err != nil || (r.ContentLength > 0 && copied < r.ContentLength)) { 39 // Didn't receive as much content as expected. Did the client 40 // disconnect during the request? If so, avoid returning a 400 41 // error to keep the logs cleaner. 42 select { 43 case <-clientClosed: 44 // Set the response code to "499 Client Closed Request" 45 // Even though the connection has already been closed, 46 // this causes the logger to pick up a 499 error 47 // instead of showing 0 for the HTTP status. 48 responseWriter.WriteHeader(499) 49 50 dcontext.GetLoggerWithFields(ctx, map[interface{}]interface{}{ 51 "error": err, 52 "copied": copied, 53 "contentLength": r.ContentLength, 54 }, "error", "copied", "contentLength").Error("client disconnected during " + action) 55 return errors.New("client disconnected") 56 default: 57 } 58 } 59 60 if err != nil { 61 dcontext.GetLogger(ctx).Errorf("unknown error reading request payload: %v", err) 62 return err 63 } 64 65 return nil 66} 67