From e46ba056f3a25da848ab2b2a78b14ad981ff3de8 Mon Sep 17 00:00:00 2001 From: Sebastian Villena <97059974+ruisebas@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:08:08 -0400 Subject: [PATCH] fix: Fixing presigned URL for Multi part upload --- .../UploadPartInput+presignURL.swift | 47 ++++++++++--------- .../StorageMultipartUploadSession.swift | 17 +++++-- .../StorageServiceSessionDelegate.swift | 11 +++++ 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift index 11d7c02789..1b2809e007 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Dependency/UploadPartInput+presignURL.swift @@ -40,6 +40,7 @@ extension UploadPartInput { .withRegion(value: config.region) .withSigningName(value: "s3") .withSigningRegion(value: config.signingRegion) + .withUnsignedPayloadTrait(value: true) .build() let builder = ClientRuntime.OrchestratorBuilder() config.interceptorProviders.forEach { provider in @@ -62,6 +63,7 @@ extension UploadPartInput { builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware()) builder.interceptors.add(AWSClientRuntime.AWSS3ErrorWith200StatusXMLMiddleware()) builder.interceptors.add(AWSClientRuntime.FlexibleChecksumsRequestMiddleware(checksumAlgorithm: input.checksumAlgorithm?.rawValue)) + builder.serialize(UploadPartPresignedMiddleware()) var metricsAttributes = Smithy.Attributes() metricsAttributes.set(key: ClientRuntime.OrchestratorMetricsAttributesKeys.service, value: "S3") metricsAttributes.set(key: ClientRuntime.OrchestratorMetricsAttributesKeys.method, value: "UploadPart") @@ -76,33 +78,34 @@ extension UploadPartInput { .build() return try await op.presignRequest(input: input).endpoint.url } + } - static func urlPathProvider(_ value: UploadPartInput) -> Swift.String? { - guard let key = value.key else { - return nil - } - return "/\(key.urlPercentEncoding(encodeForwardSlash: false))" - } +struct UploadPartPresignedMiddleware: Smithy.RequestMessageSerializer { + typealias InputType = UploadPartInput + typealias RequestType = SmithyHTTPAPI.HTTPRequest - } + let id: Swift.String = "UploadPartPresignedMiddleware" -extension UploadPartInput { + func apply(input: InputType, builder: SmithyHTTPAPI.HTTPRequestBuilder, attributes: Smithy.Context) throws { + builder.withQueryItem(.init( + name: "x-id", + value: "UploadPart") + ) - static func queryItemProvider(_ value: UploadPartInput) throws -> [URIQueryItem] { - var items = [URIQueryItem]() - items.append(URIQueryItem(name: "x-id", value: "UploadPart")) - guard let partNumber = value.partNumber else { - let message = "Creating a URL Query Item failed. partNumber is required and must not be nil." - throw ClientError.unknownError(message) + guard let partNumber = input.partNumber else { + throw ClientError.invalidValue("partNumber is required and must not be nil.") } - let partNumberQueryItem = URIQueryItem(name: "partNumber".urlPercentEncoding(), value: Swift.String(partNumber).urlPercentEncoding()) - items.append(partNumberQueryItem) - guard let uploadId = value.uploadId else { - let message = "Creating a URL Query Item failed. uploadId is required and must not be nil." - throw ClientError.unknownError(message) + builder.withQueryItem(.init( + name: "partNumber".urlPercentEncoding(), + value: Swift.String(partNumber).urlPercentEncoding()) + ) + + guard let uploadId = input.uploadId else { + throw ClientError.invalidValue("uploadId is required and must not be nil.") } - let uploadIdQueryItem = URIQueryItem(name: "uploadId".urlPercentEncoding(), value: Swift.String(uploadId).urlPercentEncoding()) - items.append(uploadIdQueryItem) - return items + builder.withQueryItem(.init( + name: "uploadId".urlPercentEncoding(), + value: Swift.String(uploadId).urlPercentEncoding()) + ) } } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift index de59ecc9bb..f4149037e5 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageMultipartUploadSession.swift @@ -43,6 +43,7 @@ class StorageMultipartUploadSession { private let onEvent: AWSS3StorageServiceBehavior.StorageServiceMultiPartUploadEventHandler private let transferTask: StorageTransferTask + private var cancelationFailure: (any Error)? init(client: StorageMultipartUploadClient, bucket: String, @@ -244,10 +245,15 @@ class StorageMultipartUploadSession { } case .completed: onEvent(.completed(())) - case .aborting: + case .aborting(_, let error): + cancelationFailure = error try abort() - case .aborted: - onEvent(.completed(())) + case .aborted(_, let error): + if let cancelationFailure { + onEvent(.failed(StorageError(error: cancelationFailure))) + } else { + onEvent(.failed(StorageError.unknown("Unable to upload", error))) + } case .failed(_, _, let error): onEvent(.failed(StorageError(error: error))) default: @@ -331,6 +337,11 @@ class StorageMultipartUploadSession { let index = partNumber - 1 parts[index] = .pending(bytes: part.bytes) multipartUpload = .parts(uploadId: uploadId, uploadFile: uploadFile, partSize: partSize, parts: parts) + let remainingParts = parts.filter({ $0.inProgress }) + if remainingParts.isEmpty { + // If there are no remaining parts in progress, manually trigger the reupload + try client.uploadPart(partNumber: partNumber, multipartUpload: multipartUpload, subTask: createSubTask(partNumber: partNumber)) + } } else { fatalError("Invalid state") } diff --git a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift index 1b83d5e17f..0ca4447988 100644 --- a/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift +++ b/AmplifyPlugins/Storage/Sources/AWSS3StoragePlugin/Support/Internal/StorageServiceSessionDelegate.swift @@ -120,6 +120,17 @@ extension StorageServiceSessionDelegate: URLSessionTaskDelegate { if response.isErrorRetriable { logURLSessionActivity("Task can be retried.") } + // For multipart uploads, we need to handle the upload part failure + if case .multiPartUploadPart(let uploadId, let partNumber) = transferTask.transferType, + let multipartUploadSession = storageService.findMultipartUploadSession(uploadId: uploadId), + !multipartUploadSession.isAborted { + multipartUploadSession.handle( + uploadPartEvent: .failed( + partNumber: partNumber, + error: responseError + ) + ) + } return }