Skip to content

Commit

Permalink
feat(storage) Parity: AWS SDK S3 accelerate via useAccelerateEndpoint…
Browse files Browse the repository at this point in the history
… pluginOptions.
  • Loading branch information
harsh62 committed Jul 13, 2023
1 parent 4c7a84b commit d797c89
Show file tree
Hide file tree
Showing 23 changed files with 502 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ extension AWSS3StoragePlugin {
if let pluginOptions = options.pluginOptions as? AWSStorageGetURLOptions, pluginOptions.validateObjectExistence {
try await storageService.validateObjectExistence(serviceKey: serviceKey)
}
let result = try await storageService.getPreSignedURL(serviceKey: serviceKey,
signingOperation: .getObject,
expires: options.expires)
let accelerate = try AWSS3PluginOptions.accelerateValue(
pluginOptions: options.pluginOptions)
let result = try await storageService.getPreSignedURL(
serviceKey: serviceKey,
signingOperation: .getObject,
accelerate: accelerate,
expires: options.expires)

let channel = HubChannel(from: categoryType)
let payload = HubPayload(eventName: HubPayload.EventName.Storage.getURL, context: options, data: result)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Amplify
import Foundation

/// - Tag: AWSS3PluginOptions
struct AWSS3PluginOptions {

/// - Tag: AWSS3PluginOptionsCodingKeys
enum CodingKeys: String, CodingKey {

/// See: https://docs.amplify.aws/lib/storage/transfer-acceleration/q/platform/js/
/// - Tag: AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint
case useAccelerateEndpoint
}

/// Attempts to extract the boolean under the
/// [useAccelerateEndpoint](x-source-tag://AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint)
/// contained in the given dictionary.
///
/// In other words, a non-nil boolean is returned if:
///
/// * The `pluginOptions` parameter is a dictionary ([String: Any])
/// * The `pluginOptions` dictionary contains a boolean key under the [useAccelerateEndpoint](x-source-tag://AWSS3PluginOptionsCodingKeys.useAccelerateEndpoint) key.
///
/// - Tag: AWSS3PluginOptions.accelerateValue
static func accelerateValue(pluginOptions: Any?) throws -> Bool? {
guard let pluginOptions = pluginOptions as? [String:Any] else {
return nil
}
guard let value = pluginOptions[CodingKeys.useAccelerateEndpoint.rawValue] else {
return nil
}
guard let boolValue = value as? Bool else {
throw StorageError.validation(CodingKeys.useAccelerateEndpoint.rawValue,
"Expecting boolean value for key \(CodingKeys.useAccelerateEndpoint.rawValue)",
"Ensure the value associated with \(CodingKeys.useAccelerateEndpoint.rawValue) is a boolean",
nil)
}
return boolValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ class AWSS3PreSignedURLBuilderAdapter: AWSS3PreSignedURLBuilderBehavior {
/// - Returns: Pre-Signed URL
func getPreSignedURL(key: String,
signingOperation: AWSS3SigningOperation,
accelerate: Bool? = nil,
expires: Int64? = nil) async throws -> URL {
let expiresDate = Date(timeIntervalSinceNow: Double(expires ?? defaultExpiration))
let expiration = expiresDate.timeIntervalSinceNow
let config = (accelerate == nil) ? self.config : S3ClientConfigurationProxy(
target: self.config,
accelerateOverride: accelerate)
let preSignedUrl: URL?
switch signingOperation {
case .getObject:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,36 @@ import AWSS3
import ClientRuntime
import AWSClientRuntime

/// - Tag: AWSS3PreSignedURLBuilderError
enum AWSS3PreSignedURLBuilderError: Error {

/// Returned by an implementation of a
/// [AWSS3PreSignedURLBuilderBehavior](x-source-tag://AWSS3PreSignedURLBuilderBehavior)
///
/// - Tag: AWSS3PreSignedURLBuilderError.failed
case failed(reason: String, error: Error?)
}

// Behavior that the implemenation class for AWSS3PreSignedURLBuilder will use.
/// Behavior that the implemenation class for AWSS3PreSignedURLBuilder will use.
///
/// - Tag: AWSS3PreSignedURLBuilderBehavior
protocol AWSS3PreSignedURLBuilderBehavior {

/// Gets a pre-signed URL.
/// Attempts to generate a pre-signed URL.
///
/// - Parameters:
/// - key: String represnting the key of an S3 object.
/// - signingOperation: [AWSS3SigningOperation](x-source-tag://AWSS3SigningOperation)
/// (get, put, upload part) for which the URL will be generated.
/// - accelerate: Optional boolean indicating wether or not to enable S3 bucket
/// [transfer acceleration](https://docs.amplify.aws/lib/storage/transfer-acceleration/q/platform/js/)
/// - expires: Int64 indicating the expiration as the number of milliseconds since the 1970 epoc.
/// - Returns: Pre-Signed URL
func getPreSignedURL(key: String, signingOperation: AWSS3SigningOperation, expires: Int64?) async throws -> URL
///
/// - Tag: AWSS3PreSignedURLBuilderBehavior.getPreSignedURL
func getPreSignedURL(key: String,
signingOperation: AWSS3SigningOperation,
accelerate: Bool?,
expires: Int64?) async throws -> URL

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0

import AWSS3
import AWSClientRuntime
import ClientRuntime
import Foundation

/// Convenience proxy class around a
/// [S3ClientConfigurationProtocol](x-source-tag://S3ClientConfigurationProtocol)
/// implementaitons that allows Amplify to change configuration values JIT.
///
/// - Tag: S3ClientConfigurationProxy
struct S3ClientConfigurationProxy {

/// - Tag: S3ClientConfigurationProxy.target
var target: S3ClientConfigurationProtocol

/// - Tag: S3ClientConfigurationProxy.accelerateOverride
var accelerateOverride: Bool?
}

extension S3ClientConfigurationProxy: S3ClientConfigurationProtocol {

var accelerate: Bool? {
if let accelerateOverride = accelerateOverride {
return accelerateOverride
}
return target.accelerate
}

var disableMultiRegionAccessPoints: Bool? {
return target.disableMultiRegionAccessPoints
}

var endpointResolver: EndpointResolver {
return target.endpointResolver
}

var forcePathStyle: Bool? {
return target.forcePathStyle
}

var useArnRegion: Bool? {
return target.useArnRegion
}

var useGlobalEndpoint: Bool? {
return target.useGlobalEndpoint
}

var credentialsProvider: AWSClientRuntime.CredentialsProvider {
get {
return target.credentialsProvider
}
set(newValue) {
target.credentialsProvider = newValue
}
}

var region: String? {
get {
return target.region
}
set(newValue) {
target.region = newValue
}
}

var signingRegion: String? {
get {
return target.signingRegion
}
set(newValue) {
target.signingRegion = newValue
}
}

var regionResolver: RegionResolver? {
get {
return target.regionResolver
}
set(newValue) {
target.regionResolver = newValue
}
}

var frameworkMetadata: FrameworkMetadata? {
get {
return target.frameworkMetadata
}
set(newValue) {
target.frameworkMetadata = newValue
}
}

var useFIPS: Bool? {
get {
return target.useFIPS
}
set(newValue) {
target.useFIPS = newValue
}
}

var useDualStack: Bool? {
get {
return target.useDualStack
}
set(newValue) {
target.useDualStack = newValue
}
}

var logger: LogAgent {
return target.logger
}

var retryer: ClientRuntime.SDKRetryer {
return target.retryer
}

var endpoint: String? {
get {
return target.endpoint
}
set(newValue) {
target.endpoint = newValue
}
}

var encoder: ClientRuntime.RequestEncoder? {
return target.encoder
}

var decoder: ClientRuntime.ResponseDecoder? {
return target.decoder
}

var httpClientEngine: ClientRuntime.HttpClientEngine {
return target.httpClientEngine
}

var httpClientConfiguration: ClientRuntime.HttpClientConfiguration {
return target.httpClientConfiguration
}

var idempotencyTokenGenerator: ClientRuntime.IdempotencyTokenGenerator {
return target.idempotencyTokenGenerator
}

var clientLogMode: ClientRuntime.ClientLogMode {
return target.clientLogMode
}

var partitionID: String? {
return target.partitionID
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class AWSS3StorageDownloadDataOperation: AmplifyInProcessReportingOperation<
do {
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
storageService.download(serviceKey: serviceKey, fileURL: nil) { [weak self] event in
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
storageService.download(serviceKey: serviceKey, fileURL: nil, accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ class AWSS3StorageDownloadFileOperation: AmplifyInProcessReportingOperation<
do {
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
storageService.download(serviceKey: serviceKey, fileURL: self.request.local) { [weak self] event in
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
storageService.download(serviceKey: serviceKey, fileURL: self.request.local, accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} catch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,21 @@ class AWSS3StorageUploadDataOperation: AmplifyInProcessReportingOperation<
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata)
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
if request.data.count > StorageUploadDataRequest.Options.multiPartUploadSizeThreshold {
storageService.multiPartUpload(serviceKey: serviceKey,
uploadSource: .data(request.data),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} else {
storageService.upload(serviceKey: serviceKey,
uploadSource: .data(request.data),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,21 @@ class AWSS3StorageUploadFileOperation: AmplifyInProcessReportingOperation<
let prefix = try await prefixResolver.resolvePrefix(for: request.options.accessLevel, targetIdentityId: request.options.targetIdentityId)
let serviceKey = prefix + request.key
let serviceMetadata = StorageRequestUtils.getServiceMetadata(request.options.metadata)
let accelerate = try AWSS3PluginOptions.accelerateValue(pluginOptions: request.options.pluginOptions)
if uploadSize > StorageUploadFileRequest.Options.multiPartUploadSizeThreshold {
storageService.multiPartUpload(serviceKey: serviceKey,
uploadSource: .local(request.local),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
} else {
storageService.upload(serviceKey: serviceKey,
uploadSource: .local(request.local),
contentType: request.options.contentType,
metadata: serviceMetadata) { [weak self] event in
metadata: serviceMetadata,
accelerate: accelerate) { [weak self] event in
self?.onServiceEvent(event: event)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ extension AWSS3StorageService {

func download(serviceKey: String,
fileURL: URL?,
onEvent: @escaping StorageServiceDownloadEventHandler) {
accelerate: Bool?,
onEvent: @escaping StorageServiceDownloadEventHandler) {
let fail: (Error) -> Void = { error in
let storageError = StorageError(error: error)
onEvent(.failed(storageError))
Expand All @@ -27,7 +28,10 @@ extension AWSS3StorageService {

Task {
do {
let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: .getObject, expires: nil)
let preSignedURL = try await preSignedURLBuilder.getPreSignedURL(key: serviceKey,
signingOperation: .getObject,
accelerate: accelerate,
expires: nil)
startDownload(preSignedURL: preSignedURL, transferTask: transferTask)
} catch {
onEvent(.failed(StorageError.unknown("Failed to get pre-signed URL", nil)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ extension AWSS3StorageService {

func getPreSignedURL(serviceKey: String,
signingOperation: AWSS3SigningOperation,
accelerate: Bool?,
expires: Int) async throws -> URL {
return try await preSignedURLBuilder.getPreSignedURL(key: serviceKey, signingOperation: signingOperation, expires: Int64(expires))
return try await preSignedURLBuilder.getPreSignedURL(
key: serviceKey,
signingOperation: signingOperation,
accelerate: accelerate,
expires: Int64(expires))
}

func validateObjectExistence(serviceKey: String) async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ extension AWSS3StorageService {
uploadSource: UploadSource,
contentType: String?,
metadata: [String: String]?,
accelerate: Bool?,
onEvent: @escaping StorageServiceMultiPartUploadEventHandler) {
let fail: (Error) -> Void = { error in
let storageError = StorageError(error: error)
Expand Down
Loading

0 comments on commit d797c89

Please sign in to comment.