mirror of
https://github.com/minio/minio.git
synced 2024-12-24 06:05:55 -05:00
fix: remove CompleteMultipartUpload() 200 OK response for blocking calls (#17699)
sending whitespace character with CompleteMultipartUpload() with 200 OK was an AWS S3 compatible implementation detail, and it was expected that the client SDK must look for both successful XML as well as error XML for 200 OK. But this is not useful anymore on MinIO, since we do not have any large delayed coalescing of parts anymore.
This commit is contained in:
parent
e12ab486a2
commit
331bdc2245
@ -19,8 +19,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -941,24 +939,6 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
if api.CacheAPI() != nil {
|
||||
completeMultiPartUpload = api.CacheAPI().CompleteMultipartUpload
|
||||
}
|
||||
// This code is specifically to handle the requirements for slow
|
||||
// complete multipart upload operations on FS mode.
|
||||
writeErrorResponseWithoutXMLHeader := func(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
||||
switch err.Code {
|
||||
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
|
||||
// Set retxry-after header to indicate user-agents to retry request after 120secs.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||
w.Header().Set(xhttp.RetryAfter, "120")
|
||||
}
|
||||
|
||||
// Generate error response.
|
||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
||||
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
||||
encodedErrorResponse, _ := xml.Marshal(errorResponse)
|
||||
setCommonHeaders(w)
|
||||
w.Header().Set(xhttp.ContentType, string(mimeXML))
|
||||
w.Write(encodedErrorResponse)
|
||||
}
|
||||
|
||||
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
||||
suspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||
@ -998,36 +978,12 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
multipartETag := etag.Multipart(completeETags...)
|
||||
opts.UserDefined["etag"] = multipartETag.String()
|
||||
|
||||
w = &whiteSpaceWriter{ResponseWriter: w, Flusher: w.(http.Flusher)}
|
||||
completeDoneCh := sendWhiteSpace(ctx, w)
|
||||
objInfo, err := completeMultiPartUpload(ctx, bucket, object, uploadID, complMultipartUpload.Parts, opts)
|
||||
// Stop writing white spaces to the client. Note that close(doneCh) style is not used as it
|
||||
// can cause white space to be written after we send XML response in a race condition.
|
||||
headerWritten := <-completeDoneCh
|
||||
if err != nil {
|
||||
if headerWritten {
|
||||
writeErrorResponseWithoutXMLHeader(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
} else {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
}
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Get object location.
|
||||
location := getObjectLocation(r, globalDomainNames, bucket, object)
|
||||
// Generate complete multipart response.
|
||||
response := generateCompleteMultpartUploadResponse(bucket, object, location, objInfo)
|
||||
var encodedSuccessResponse []byte
|
||||
if !headerWritten {
|
||||
encodedSuccessResponse = encodeResponse(response)
|
||||
} else {
|
||||
encodedSuccessResponse, err = xml.Marshal(response)
|
||||
if err != nil {
|
||||
writeErrorResponseWithoutXMLHeader(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
opts.EncryptFn, err = objInfo.metadataEncryptFn(r.Header)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
@ -1050,6 +1006,12 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
defer globalReplicationStats.UpdateReplicaStat(bucket, actualSize)
|
||||
}
|
||||
|
||||
// Get object location.
|
||||
location := getObjectLocation(r, globalDomainNames, bucket, object)
|
||||
// Generate complete multipart response.
|
||||
response := generateCompleteMultpartUploadResponse(bucket, object, location, objInfo)
|
||||
encodedSuccessResponse := encodeResponse(response)
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodedSuccessResponse)
|
||||
|
||||
@ -1194,63 +1156,3 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodedSuccessResponse)
|
||||
}
|
||||
|
||||
type whiteSpaceWriter struct {
|
||||
http.ResponseWriter
|
||||
http.Flusher
|
||||
written bool
|
||||
}
|
||||
|
||||
func (w *whiteSpaceWriter) Write(b []byte) (n int, err error) {
|
||||
n, err = w.ResponseWriter.Write(b)
|
||||
w.written = true
|
||||
return
|
||||
}
|
||||
|
||||
func (w *whiteSpaceWriter) WriteHeader(statusCode int) {
|
||||
if !w.written {
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Send empty whitespaces every 10 seconds to the client till completeMultiPartUpload() is
|
||||
// done so that the client does not time out. Downside is we might send 200 OK and
|
||||
// then send error XML. But accoording to S3 spec the client is supposed to check
|
||||
// for error XML even if it received 200 OK. But for erasure this is not a problem
|
||||
// as completeMultiPartUpload() is quick. Even For FS, it would not be an issue as
|
||||
// we do background append as and when the parts arrive and completeMultiPartUpload
|
||||
// is quick. Only in a rare case where parts would be out of order will
|
||||
// FS:completeMultiPartUpload() take a longer time.
|
||||
func sendWhiteSpace(ctx context.Context, w http.ResponseWriter) <-chan bool {
|
||||
doneCh := make(chan bool)
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
ticker := time.NewTicker(time.Second * 10)
|
||||
defer ticker.Stop()
|
||||
headerWritten := false
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Write header if not written yet.
|
||||
if !headerWritten {
|
||||
_, err := w.Write([]byte(xml.Header))
|
||||
headerWritten = err == nil
|
||||
}
|
||||
|
||||
// Once header is written keep writing empty spaces
|
||||
// which are ignored by client SDK XML parsers.
|
||||
// This occurs when server takes long time to completeMultiPartUpload()
|
||||
_, err := w.Write([]byte(" "))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
case doneCh <- headerWritten:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return doneCh
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user