Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): add Foundation HTTP client for watchOS / tvOS #3230

Merged
merged 16 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AWSCognitoIdentity
import AWSCognitoIdentityProvider
import AWSPluginsCore
import ClientRuntime
@_spi(FoundationClientEngine) import AWSPluginsCore

extension AWSCognitoAuthPlugin {

Expand Down Expand Up @@ -92,9 +93,23 @@ extension AWSCognitoAuthPlugin {
)

if var httpClientEngineProxy = httpClientEngineProxy {
let sdkEngine = configuration.httpClientEngine
httpClientEngineProxy.target = sdkEngine
let httpClientEngine: HttpClientEngine
#if os(iOS) || os(macOS)
// networking goes through CRT
httpClientEngine = configuration.httpClientEngine
#else
// networking goes through Foundation
httpClientEngine = FoundationClientEngine()
#endif
httpClientEngineProxy.target = httpClientEngine
configuration.httpClientEngine = httpClientEngineProxy
} else {
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
configuration.httpClientEngine = FoundationClientEngine()
#endif
}

return CognitoIdentityProviderClient(config: configuration)
Expand All @@ -110,6 +125,14 @@ extension AWSCognitoAuthPlugin {
frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(),
region: identityPoolConfig.region
)

#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
configuration.httpClientEngine = FoundationClientEngine()
atierian marked this conversation as resolved.
Show resolved Hide resolved
#endif

return CognitoIdentityClient(config: configuration)
default:
fatalError()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import ClientRuntime

extension Foundation.URLRequest {
init(sdkRequest: ClientRuntime.SdkHttpRequest) throws {
guard let url = sdkRequest.endpoint.url else {
throw FoundationClientEngineError.invalidRequestURL(sdkRequest: sdkRequest)
}
self.init(url: url)
httpMethod = sdkRequest.method.rawValue

for header in sdkRequest.headers.headers {
for value in header.value {
addValue(value, forHTTPHeaderField: header.name)
}
}

switch sdkRequest.body {
case .data(let data): httpBody = data
case .stream(let stream): httpBody = stream.toBytes().getData()
case .none: break
}
}
}

extension ClientRuntime.HttpResponse {
private static func headers(
from allHeaderFields: [AnyHashable: Any]
) -> ClientRuntime.Headers {
var headers = Headers()
for header in allHeaderFields {
switch (header.key, header.value) {
case let (key, value) as (String, String):
headers.add(name: key, value: value)
case let (key, values) as (String, [String]):
headers.add(name: key, values: values)
default: continue
}
}
return headers
}

convenience init(httpURLResponse: HTTPURLResponse, data: Data) throws {
let headers = Self.headers(from: httpURLResponse.allHeaderFields)
let body = HttpBody.stream(ByteStream.from(data: data))

guard let statusCode = HttpStatusCode(rawValue: httpURLResponse.statusCode) else {
// This shouldn't happen, but `HttpStatusCode` only exposes a failable
// `init`. The alternative here is force unwrapping, but we can't
// make the decision to crash here on behalf on consuming applications.
throw FoundationClientEngineError.unexpectedStatusCode(
statusCode: httpURLResponse.statusCode
)
}
self.init(headers: headers, body: body, statusCode: statusCode)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import ClientRuntime
import Amplify

@_spi(FoundationClientEngine)
public struct FoundationClientEngine: HttpClientEngine {
public func execute(request: ClientRuntime.SdkHttpRequest) async throws -> ClientRuntime.HttpResponse {
let urlRequest = try URLRequest(sdkRequest: request)

let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard let httpURLResponse = response as? HTTPURLResponse else {
// This shouldn't be necessary because we're only making HTTP requests.
// `URLResponse` should always be a `HTTPURLResponse`.
// But to refrain from crashing consuming applications, we're throwing here.
throw FoundationClientEngineError.invalidURLResponse(urlRequest: response)
}

let httpResponse = try HttpResponse(
httpURLResponse: httpURLResponse,
data: data
)

return httpResponse
}

public init() {}

/// no-op
func close() async {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify
import ClientRuntime

struct FoundationClientEngineError: AmplifyError {
let errorDescription: ErrorDescription
let recoverySuggestion: RecoverySuggestion
let underlyingError: Error?

// protocol requirement
init(
errorDescription: ErrorDescription,
recoverySuggestion: RecoverySuggestion,
error: Error
) {
self.errorDescription = errorDescription
self.recoverySuggestion = recoverySuggestion
self.underlyingError = error
}
}

extension FoundationClientEngineError {
init(
errorDescription: ErrorDescription,
recoverySuggestion: RecoverySuggestion,
error: Error?
) {
self.errorDescription = errorDescription
self.recoverySuggestion = recoverySuggestion
self.underlyingError = error
}

static func invalidRequestURL(sdkRequest: ClientRuntime.SdkHttpRequest) -> Self {
.init(
errorDescription: """
The SdkHttpRequest generated by ClientRuntime doesn't include a valid URL
- \(sdkRequest)
""",
recoverySuggestion: """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
""",
error: nil
)
}

static func invalidURLResponse(urlRequest: URLResponse) -> Self {
.init(
errorDescription: """
The URLResponse received is not an HTTPURLResponse
- \(urlRequest)
""",
recoverySuggestion: """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
""",
error: nil
)
}

static func unexpectedStatusCode(statusCode: Int) -> Self {
.init(
errorDescription: """
The status code received isn't a valid `HttpStatusCode` value.
- status code: \(statusCode)
""",
recoverySuggestion: """
Please open an issue at https://github.com/aws-amplify/amplify-swift
with the contents of this error message.
""",
error: nil
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
// SPDX-License-Identifier: Apache-2.0
//

import Foundation
import Amplify
import AWSPluginsCore
import Foundation

@_spi(FoundationClientEngine) import AWSPluginsCore
import AWSLocation
import AWSClientRuntime

Expand All @@ -35,6 +35,13 @@ extension AWSLocationGeoPlugin {
frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(),
region: region)

#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
serviceConfiguration.httpClientEngine = FoundationClientEngine()
#endif

let location = LocationClient(config: serviceConfiguration)
let locationService = AWSLocationAdapter(location: location)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import AWSClientRuntime
import AWSPluginsCore
import AWSPinpoint
@_spi(FoundationClientEngine) import AWSPluginsCore

extension PinpointClient {
convenience init(region: String, credentialsProvider: CredentialsProvider) throws {
Expand All @@ -16,6 +17,12 @@ extension PinpointClient {
frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(),
region: region
)
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
configuration.httpClientEngine = FoundationClientEngine()
#endif
PinpointRequestsRegistry.shared.setCustomHttpEngine(on: configuration)
self.init(config: configuration)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import ClientRuntime
}

nonisolated func setCustomHttpEngine(on configuration: PinpointClient.PinpointClientConfiguration) {
let oldHttpClientEngine = configuration.httpClientEngine
let baseHTTPClientEngine = configuration.httpClientEngine

configuration.httpClientEngine = CustomPinpointHttpClientEngine(
httpClientEngine: oldHttpClientEngine
httpClientEngine: baseHTTPClientEngine
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AWSPluginsCore
@_spi(FoundationClientEngine) import AWSPluginsCore
import Amplify
import Combine
import Foundation
Expand Down Expand Up @@ -107,6 +108,14 @@ final class AWSCloudWatchLoggingSessionController {
frameworkMetadata: AmplifyAWSServiceConfiguration.frameworkMetaData(),
region: region
)

#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
configuration.httpClientEngine = FoundationClientEngine()
#endif

self.client = CloudWatchLogsClient(config: configuration)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import AWSTextract
import AWSComprehend
import AWSPolly
import AWSPluginsCore
@_spi(FoundationClientEngine) import AWSPluginsCore
import Foundation
import ClientRuntime
import AWSClientRuntime
Expand All @@ -37,30 +38,60 @@ class AWSPredictionsService {
credentialsProvider: credentialsProvider,
region: configuration.convert.region
)
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
translateClientConfiguration.httpClientEngine = FoundationClientEngine()
#endif
let awsTranslateClient = TranslateClient(config: translateClientConfiguration)

let pollyClientConfiguration = try PollyClient.PollyClientConfiguration(
credentialsProvider: credentialsProvider,
region: configuration.convert.region
)
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
pollyClientConfiguration.httpClientEngine = FoundationClientEngine()
#endif
let awsPollyClient = PollyClient(config: pollyClientConfiguration)

let comprehendClientConfiguration = try ComprehendClient.ComprehendClientConfiguration(
credentialsProvider: credentialsProvider,
region: configuration.convert.region
)
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
comprehendClientConfiguration.httpClientEngine = FoundationClientEngine()
#endif
let awsComprehendClient = ComprehendClient(config: comprehendClientConfiguration)

let rekognitionClientConfiguration = try RekognitionClient.RekognitionClientConfiguration(
credentialsProvider: credentialsProvider,
region: configuration.identify.region
)
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
rekognitionClientConfiguration.httpClientEngine = FoundationClientEngine()
#endif
let awsRekognitionClient = RekognitionClient(config: rekognitionClientConfiguration)

let textractClientConfiguration = try TextractClient.TextractClientConfiguration(
credentialsProvider: credentialsProvider,
region: configuration.identify.region
)
#if os(iOS) || os(macOS) // no-op
#else
// For any platform except iOS or macOS
// Use Foundation instead of CRT for networking.
textractClientConfiguration.httpClientEngine = FoundationClientEngine()
#endif
let awsTextractClient = TextractClient(config: textractClientConfiguration)

let awsTranscribeStreamingAdapter = AWSTranscribeStreamingAdapter(
Expand Down
Loading
Loading