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: Implement URLSessionTaskDelegate needNewBodyStream method #715

Merged
merged 2 commits into from
May 8, 2024

Conversation

jbelkins
Copy link
Contributor

@jbelkins jbelkins commented May 7, 2024

Issue #

awslabs/aws-sdk-swift#1457

Description of changes

  • Implement the urlSession(_:task:needNewBodyStream:) delegate method. This method must be implemented whenever URLRequest input body streaming is used. When called, the FoundationStreamBridge replaces its bound stream pair and passes the new InputStream to the URLSessionTask.
  • In practice, this method gets called when there are multiple concurrent HTTP requests to the same host, so without it we were seeing intermittent connection failures on Amplify customers, when they attempted to authenticate to the server at app startup (this involves multiple concurrent requests to AWS Cognito.)

Also:

  • Configure the URLSession to never handle cookies, which are not set or used on AWS API endpoints.

Scope

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@jbelkins jbelkins requested review from sichanyoo and dayaffe May 7, 2024 14:08
/// A Foundation `OutputStream` that will read from the `ReadableStream`.
///
/// Will be replaced when `replaceStreams(_:)` is called to replace the input stream.
private var outputStream: OutputStream
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input & output Foundation streams are now vars because they may need to be replaced during a HTTP request.

(inputStream, outputStream) = Self.makeStreams(boundStreamBufferSize: self.boundStreamBufferSize, queue: queue)
}

func replaceStreams(completion: @escaping (InputStream?) -> Void) async {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method will be called by the HTTP client when a new body stream is needed.

The output stream is closed, both streams are replaced with new ones & configured, and the output stream is reopened.

Note that I never do anything to the input stream, only the URLSessionDataTask ever manipulates that stream.

await open()
}

private static func makeStreams(boundStreamBufferSize: Int, queue: DispatchQueue) -> (InputStream, OutputStream) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method just extracts the stream creation logic from the initializer so it can be used to replace the streams as well as at this class's initialization.

@@ -14,6 +14,9 @@ extension URLSessionConfiguration {
public static func from(httpClientConfiguration: HttpClientConfiguration) -> URLSessionConfiguration {
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = httpClientConfiguration.socketTimeout
config.httpShouldSetCookies = false
config.httpCookieAcceptPolicy = .never
config.httpCookieStorage = nil
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly related to the rest of this PR but this should be done... the URLSession is configured to ignore cookies completely.

AWS HTTP APIs should never use cookies, but this guards against any weirdness from a proxy or gateway a customer may use.

(lol been bit by this before on other apps, hence I'm adding it here)

/// - Parameters:
/// - session: The `URLSession` the task belongs to.
/// - task: The `URLSessionTask` that needs a new body stream.
/// - completionHandler: A block to be called with the new `InputStream` when it is ready.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delegate method replaces the InputStream used for body streaming on request.

The FoundationStreamBridge performs the actual replacement, then informs the completion block of the new stream.

The guard below should never fail, because this only gets called when an input stream is used, and the only way we make an input stream is through a FoundationStreamBridge.

@jbelkins jbelkins marked this pull request as ready for review May 7, 2024 14:40
@jbelkins jbelkins merged commit 34f090b into main May 8, 2024
14 checks passed
@jbelkins jbelkins deleted the jbe/replace_body_stream branch May 8, 2024 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants