Skip to content

S3 Breaking Change - 3.7.412.0+ doesn't respect seekable streams and incorrectly sets Content-Length Header #3629

Closed
@niemyjski

Description

@niemyjski

Describe the bug

I've tracked it down to this was working in 3.7.411.7 but broken in any future release. Due to how you tag releases and different client versions it's pretty hard to track down...

System.Net.Http.HttpRequestException
Sent 298 request content bytes, but Content-Length promised 305.
   at System.Net.Http.HttpConnection.SendRequestContentAsync(HttpRequestMessage request, HttpContentWriteStream stream, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnection.SendRequestContentWithExpect100ContinueAsync(HttpRequestMessage request, Task`1 allowExpect100ToContinueTask, HttpContentWriteStream stream, Timer expect100Timer, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Amazon.Runtime.HttpWebRequestMessage.GetResponseAsync(CancellationToken cancellationToken)
   at Amazon.Runtime.Internal.HttpHandler`1.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RedirectHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.Unmarshaller.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.S3.Internal.AmazonS3ResponseHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.Signer.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.S3.Internal.S3Express.S3ExpressPreSigner.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.S3.Internal.AmazonS3ExceptionHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Foundatio.Storage.S3FileStorage.SaveFileAsync(String path, Stream stream, CancellationToken cancellationToken) in /Users/blakeniemyjski/code/Foundatio.AWS/src/Foundatio.AWS/Storage/S3FileStorage.cs:line 220

Regression Issue

  • Select this option if this issue appears to be a regression.

Expected Behavior

I should be able to post a partial stream.

Current Behavior

Crashes with an error

System.Net.Http.HttpRequestException
Sent 298 request content bytes, but Content-Length promised 305.

Reproduction Steps

string path = "blake.txt";
            using (var memoryStream = new MemoryStream())
            {
                long offset;
                await using (var writer = new StreamWriter(memoryStream, Encoding.UTF8, 1024, true))
                {
                    writer.AutoFlush = true;
                    await writer.WriteAsync("Eric");
                    offset = memoryStream.Position;
                    await writer.WriteAsync("Blake");
                    await writer.FlushAsync();
                }

                memoryStream.Seek(offset, SeekOrigin.Begin);
                await SaveFileAsync(path, memoryStream);
            }

    public async Task<bool> SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default(CancellationToken))
    {
        if (String.IsNullOrEmpty(path))
            throw new ArgumentNullException(nameof(path));
        if (stream == null)
            throw new ArgumentNullException(nameof(stream));

        var req = new PutObjectRequest
        {
            CannedACL = _cannedAcl,
            BucketName = _bucket,
            Key = NormalizePath(path),
            AutoResetStreamPosition = false,
            AutoCloseStream = !stream.CanSeek,
            InputStream = stream.CanSeek ? stream : AmazonS3Util.MakeStreamSeekable(stream),
            UseChunkEncoding = _useChunkEncoding
        };

        _logger.LogTrace("Saving {Path}", req.Key);
        var response = await _client.PutObjectAsync(req, cancellationToken).AnyContext();
        return response.HttpStatusCode.IsSuccessful();
    }

Possible Solution

Set the content length header to stream length - stream position instead of stream length

Additional Information/Context

No response

AWS .NET SDK and/or Package version used

AWSSDK.S3 3.7.412.0 +

Targeted .NET Platform

.NET Core 8

Operating System and version

Mac Os

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis issue is a bug.p1This is a high priority issuepending-releaseThis issue will be fixed by an approved PR that hasn't been released yet.s3xsEffort estimation: tiny

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions