From a854e8cc5ccb61739d80c9b66dd4775ebf8a1544 Mon Sep 17 00:00:00 2001 From: Krishnan Parthasarathi Date: Tue, 28 Jun 2016 01:18:18 -0700 Subject: [PATCH] api: Sent ErrPreconditionFailed on If-Match failure (#2009) * api: Sent ErrPreconditionFailed on If-Match failure ref: http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList * tests: Added functional tests for GetObject w/ If-Match headers set * tests: Used verifyError to simplify errorCode and description matching on error --- api-errors.go | 6 ++++ object-handlers.go | 2 +- server_xl_test.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/api-errors.go b/api-errors.go index 867ca9436..cfc773f76 100644 --- a/api-errors.go +++ b/api-errors.go @@ -74,6 +74,7 @@ const ( ErrNoSuchKey ErrNoSuchUpload ErrNotImplemented + ErrPreconditionFailed ErrRequestTimeTooSkewed ErrSignatureDoesNotMatch ErrMethodNotAllowed @@ -255,6 +256,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{ Description: "A header you provided implies functionality that is not implemented.", HTTPStatusCode: http.StatusNotImplemented, }, + ErrPreconditionFailed: { + Code: "PreconditionFailed", + Description: "At least one of the preconditions you specified did not hold.", + HTTPStatusCode: http.StatusPreconditionFailed, + }, ErrRequestTimeTooSkewed: { Code: "RequestTimeTooSkewed", Description: "The difference between the request time and the server's time is too large.", diff --git a/object-handlers.go b/object-handlers.go index bd4037248..8a4128949 100644 --- a/object-handlers.go +++ b/object-handlers.go @@ -242,7 +242,7 @@ func checkETag(w http.ResponseWriter, r *http.Request) bool { delete(h, "Content-Type") delete(h, "Content-Length") delete(h, "Content-Range") - w.WriteHeader(http.StatusPreconditionFailed) + writeErrorResponse(w, r, ErrPreconditionFailed, r.URL.Path) return true } } diff --git a/server_xl_test.go b/server_xl_test.go index e13f7dd7c..71636387c 100644 --- a/server_xl_test.go +++ b/server_xl_test.go @@ -841,6 +841,77 @@ func (s *MyAPIXLSuite) TestNotBeAbleToCreateObjectInNonexistentBucket(c *C) { verifyError(c, response, "NoSuchBucket", "The specified bucket does not exist.", http.StatusNotFound) } +// TestGetOnObject - Asserts properties for GET on an object. +// GET requests on an object retrieves the object from server. +// Tests behaviour when If-Match/If-None-Match headers are set on the request +func (s *MyAPIXLSuite) TestGetOnObject(c *C) { + // generate a random bucket name. + bucketName := getRandomBucketName() + // make HTTP request to create the bucket. + request, err := newTestRequest("PUT", getMakeBucketURL(s.endPoint, bucketName), + 0, nil, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + client := http.Client{} + // execute the HTTP request to create bucket. + response, err := client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + buffer1 := bytes.NewReader([]byte("hello world")) + request, err = newTestRequest("PUT", s.endPoint+"/"+bucketName+"/object1", + int64(buffer1.Len()), buffer1, s.accessKey, s.secretKey) + c.Assert(err, IsNil) + + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + + // GetObject with If-Match sending correct etag in request headers + // is expected to return the object + md5Writer := md5.New() + md5Writer.Write([]byte("hello world")) + etag := hex.EncodeToString(md5Writer.Sum(nil)) + request, err = newTestRequest("GET", s.endPoint+"/"+bucketName+"/object1", + 0, nil, s.accessKey, s.secretKey) + request.Header.Set("If-Match", etag) + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusOK) + var body []byte + body, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(string(body), Equals, "hello world") + + // GetObject with If-Match sending mismatching etag in request headers + // is expected to return an error response with ErrPreconditionFailed. + request, err = newTestRequest("GET", s.endPoint+"/"+bucketName+"/object1", + 0, nil, s.accessKey, s.secretKey) + request.Header.Set("If-Match", etag[1:]) + response, err = client.Do(request) + verifyError(c, response, "PreconditionFailed", "At least one of the preconditions you specified did not hold.", http.StatusPreconditionFailed) + + // GetObject with If-None-Match sending mismatching etag in request headers + // is expected to return the object. + request, err = newTestRequest("GET", s.endPoint+"/"+bucketName+"/object1", + 0, nil, s.accessKey, s.secretKey) + request.Header.Set("If-None-Match", etag[1:]) + response, err = client.Do(request) + c.Assert(response.StatusCode, Equals, http.StatusOK) + body, err = ioutil.ReadAll(response.Body) + c.Assert(err, IsNil) + c.Assert(string(body), Equals, "hello world") + + // GetObject with If-None-Match sending matching etag in request headers + // is expected to return (304) Not-Modified. + request, err = newTestRequest("GET", s.endPoint+"/"+bucketName+"/object1", + 0, nil, s.accessKey, s.secretKey) + request.Header.Set("If-None-Match", etag) + response, err = client.Do(request) + c.Assert(err, IsNil) + c.Assert(response.StatusCode, Equals, http.StatusNotModified) +} + // TestHeadOnObjectLastModified - Asserts response for HEAD on an object. // HEAD requests on an object validates the existence of the object. // The responses for fetching the object when If-Modified-Since