Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify that HTTP Status Code 304 (NotModified) is not an error condition inside the Amazon S3 GetObject API?

Background

I am trying to make use of S3 as an 'infinity' large caching layer for some 'fairly' static XML documents. I want to ensure that the client application (which will be running on thousands of machines concurrently and requesting the XML documents many times per hour) only downloads these XML documents if their content has changed since the last time the client application downloaded them.

Approach

On Amazon S3, we can use HTTP ETAG for this. By default Amazon S3 objects have their ETAG set to the MD5 hash of the object.

We can then specify the MD5 hash of the XML document inside the GetObjectRequest.ETagToNotMatch property. This ensures that when we make the AmazonS3.GetObject call (or in my case the async version AmazonS3.BeginGetObject and AmazonS3.EndGetObject), that if the document being requested has the same MD5 hash as is contained in the GetObjectRequest.ETagToNotMatch then S3 automatically returns the HTTP Status code 304 (NotModified) and the actual contents of the XML document is not downloaded.

Problem

The problem however is that when calling AmazonS3.GetObject (or it's async equivalent) the Amazon .Net API actually sees the HTTP Status code 304 (NotModified) as an error and it retries the get request three times and then finally throws an Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3.

Obviously I could change this implementation to use AmazonS3.GetObjectMetaData and then compare the ETAG and use AmazonS3.GetObject if they do not match, but then there are two requests to S3 instead of one when the file is stale. I'd prefer to have one request regardless of whether the XML document needs downloaded or not.

Any ideas? Is this a bug or am I missing something? Is there even some way I can reduce the number of retries to one and 'process' the exception (although I feel 'yuck' about this route).

Implementation

I'm using the AWS SDK for .NET (version 1.3.14).

Here is my implementation (reduced slightly to keep it shorter):

public Task<GetObjectResponse> DownloadString(string key, string etag = null) {

    var request = new GetObjectRequest { Key = key, BucketName = Bucket };

    if (etag != null) {
        request.ETagToNotMatch = etag;
    }

    var task = Task<GetObjectResponse>.Factory.FromAsync(_s3Client.BeginGetObject, _s3Client.EndGetObject, request, null);

    return task;
}

I then call this like:

var dlTask          = s3Manager.DownloadString("new one", "d7db7bc318d6eb9222d728747879b52e");
var responseTasks   = new[]
    {
        dlTask.ContinueWith(x => _log.Error("Error downloading string.", x.Exception), TaskContinuationOptions.OnlyOnFaulted),
        dlTask.ContinueWith(x => _log.Warn("Downloading string was cancelled."), TaskContinuationOptions.OnlyOnCanceled),
        dlTask.ContinueWith(x => _log.Info(string.Format("Done with download: {0}", x.Result.ETag)), TaskContinuationOptions.OnlyOnRanToCompletion)
    };

try {
    Task.WaitAny(responseTasks);
} catch (AggregateException aex) {
    _log.Error("Error while processing download string.", aex);
}

_log.Info("Exiting...");

This then produces this log file output:

2011-10-11 13:21:20,376 [11] INFO  Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:01.6140812.
2011-10-11 13:21:20,385 [11] INFO  Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:20,789 [11] INFO  Amazon.S3.AmazonS3Client - Retry number 1 for request GetObject.
2011-10-11 13:21:22,329 [11] INFO  Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:01.1400356.
2011-10-11 13:21:22,329 [11] INFO  Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:23,929 [11] INFO  Amazon.S3.AmazonS3Client - Retry number 2 for request GetObject.
2011-10-11 13:21:26,508 [11] INFO  Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:00.9790314.
2011-10-11 13:21:26,508 [11] INFO  Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:32,908 [11] INFO  Amazon.S3.AmazonS3Client - Retry number 3 for request GetObject.
2011-10-11 13:21:40,604 [11] INFO  Amazon.S3.AmazonS3Client - Received response for GetObject (id 2ee99002-d148-4572-b19b-29259534f48f) with status code NotModified in 00:00:01.2950718.
2011-10-11 13:21:40,605 [11] INFO  Amazon.S3.AmazonS3Client - Request for GetObject is being redirect to https://s3.amazonaws.com/x/new%20one.
2011-10-11 13:21:40,621 [11] ERROR Amazon.S3.AmazonS3Client - Error for GetResponse
Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
   at Amazon.S3.AmazonS3Client.pauseOnRetry(Int32 retries, Int32 maxRetries, HttpStatusCode status, String requestAddr, WebHeaderCollection headers, Exception cause)
   at Amazon.S3.AmazonS3Client.handleHttpResponse[T](S3Request userRequest, HttpWebRequest request, HttpWebResponse httpResponse, Int32 retries, TimeSpan lengthOfRequest, T& response, Exception& cause, HttpStatusCode& statusCode)
   at Amazon.S3.AmazonS3Client.getResponseCallback[T](IAsyncResult result)
2011-10-11 13:21:40,635 [10] INFO  Example.Program - Exiting...
2011-10-11 13:21:40,638 [19] ERROR Example.Program - Error downloading string.
System.AggregateException: One or more errors occurred. ---> Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
   at Amazon.S3.AmazonS3Client.pauseOnRetry(Int32 retries, Int32 maxRetries, HttpStatusCode status, String requestAddr, WebHeaderCollection headers, Exception cause)
   at Amazon.S3.AmazonS3Client.handleHttpResponse[T](S3Request userRequest, HttpWebRequest request, HttpWebResponse httpResponse, Int32 retries, TimeSpan lengthOfRequest, T& response, Exception& cause, HttpStatusCode& statusCode)
   at Amazon.S3.AmazonS3Client.getResponseCallback[T](IAsyncResult result)
   at Amazon.S3.AmazonS3Client.endOperation[T](IAsyncResult result)
   at Amazon.S3.AmazonS3Client.EndGetObject(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endMethod, TaskCompletionSource`1 tcs)
   --- End of inner exception stack trace ---
---> (Inner Exception #0) Amazon.S3.AmazonS3Exception: Maximum number of retry attempts reached : 3
   at Amazon.S3.AmazonS3Client.pauseOnRetry(Int32 retries, Int32 maxRetries, HttpStatusCode status, String requestAddr, WebHeaderCollection headers, Exception cause)
   at Amazon.S3.AmazonS3Client.handleHttpResponse[T](S3Request userRequest, HttpWebRequest request, HttpWebResponse httpResponse, Int32 retries, TimeSpan lengthOfRequest, T& response, Exception& cause, HttpStatusCode& statusCode)
   at Amazon.S3.AmazonS3Client.getResponseCallback[T](IAsyncResult result)
   at Amazon.S3.AmazonS3Client.endOperation[T](IAsyncResult result)
   at Amazon.S3.AmazonS3Client.EndGetObject(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endMethod, TaskCompletionSource`1 tcs)<---
like image 347
InvertedAcceleration Avatar asked Oct 11 '11 13:10

InvertedAcceleration


People also ask

Which HTTP code indicates a successful upload of an object to Amazon S3?

HTTP 200 code indicates a successful write to S3.

Which is not supported by Amazon S3?

New Amazon S3 features are not supported for SOAP. Instead of using SOAP, we recommend that you use either the REST API or the AWS SDKs. Amazon S3 provides a set of error codes that are used by both the SOAP and REST API.

What HTTP response code will you receive after a successful upload to an S3 bucket?

Finally, S3 responds with the 204 OK response code if the upload was successful or with an appropriate error response code if something went wrong.


1 Answers

I also posted this question on the Amazon developers forum and got a reply from an official AWS Employee:

After investigating this we understand the problem but we are looking for feedback on how best to handle this.

First approach is to have this operation return with a property on the GetObjectResponse indicating that the object was not returned or set the output stream to null. This would be cleaner to code against but it does create a slight breaking behavior for anybody relying on an exception being thrown, albeit after the 3 retries. It would also be inconsistent with the CopyObject operation which does throw an exception without all the crazy retrying.

The other option is we throw an exception similar to CopyObject which keeps us consistent and no breaking changes but it is more difficult to code against.

If anybody has opinions on which way to handle this please respond to this thread.

Norm

I have already added my thoughts to the thread, if anybody else is interested in participating here is the link:

AmazonS3.GetObject sees HTTP 304 (NotModified) as an error. Way to allow it?


NOTE: When this has been resolved by Amazon I will update my answer to reflect the outcome.


UPDATE: (2012-01-24) Still waiting for further information from Amazon.

UPDATE: (2018-12-06) this was fixed in AWS SDK 1.5.20 in 2013 https://forums.aws.amazon.com/thread.jspa?threadID=77995&tstart=0

like image 185
InvertedAcceleration Avatar answered Nov 15 '22 07:11

InvertedAcceleration