-
Notifications
You must be signed in to change notification settings - Fork 411
Use fallback API hosts when receiving server down response #4970
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
Changes from 14 commits
9577ba5
85267dc
720b3cd
b7ae271
f51040c
23420f1
e149e07
be78693
5146304
a18a382
1c365cd
88b9c03
0c9e623
c2facde
a9275b6
89e8d79
3c5a9d3
854a9f6
ba66359
cafbf9c
cd0dc1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -245,6 +245,7 @@ internal extension HTTPClient { | |
| var headers: HTTPClient.RequestHeaders | ||
| var verificationMode: Signing.ResponseVerificationMode | ||
| var completionHandler: HTTPClient.Completion<Data>? | ||
| private(set) var fallbackPathIndex: Int? | ||
|
|
||
| /// Whether the request has been retried. | ||
| var retried: Bool { | ||
|
|
@@ -279,25 +280,46 @@ internal extension HTTPClient { | |
| } | ||
|
|
||
| var method: HTTPRequest.Method { self.httpRequest.method } | ||
| var path: String { self.httpRequest.path.relativePath } | ||
| var path: String { (self.getCurrentPath() ?? self.httpRequest.path).relativePath } | ||
|
|
||
| func getCurrentRequestURL(proxyURL: URL?) -> URL? { | ||
| return self.getCurrentPath()?.url(proxyURL: proxyURL) | ||
| } | ||
|
|
||
| func retriedRequest() -> Self { | ||
| var copy = self | ||
| copy.retryCount += 1 | ||
| copy.headers[RequestHeader.retryCount.rawValue] = "\(copy.retryCount)" | ||
| return copy | ||
| } | ||
|
|
||
| func requestWithNextFallbackPath() -> Self? { | ||
| var copy = self | ||
| copy.fallbackPathIndex = self.fallbackPathIndex?.advanced(by: 1) ?? 0 | ||
| guard copy.getCurrentPath() != nil else { | ||
| // No more fallback paths available | ||
| return nil | ||
| } | ||
| return copy | ||
| } | ||
|
|
||
| var description: String { | ||
| """ | ||
| <\(type(of: self)): httpMethod=\(self.method.httpMethod) | ||
| path=\(self.path) | ||
| headers=\(self.headers.description ) | ||
| headers=\(self.headers.description) | ||
| retried=\(self.retried) | ||
| > | ||
| """ | ||
| } | ||
|
|
||
| private func getCurrentPath() -> HTTPRequestPath? { | ||
| if let fallbackPathIndex = self.fallbackPathIndex { | ||
| return self.httpRequest.path.fallbackPaths[safe: fallbackPathIndex] | ||
| } else { | ||
| return self.httpRequest.path | ||
| } | ||
| } | ||
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -433,18 +455,16 @@ private extension HTTPClient { | |
| requestStartTime: Date) { | ||
| RCTestAssertNotMainThread() | ||
|
|
||
| let response = self.parse( | ||
| urlResponse: urlResponse, | ||
| request: request, | ||
| urlRequest: urlRequest, | ||
| data: data, | ||
| error: networkError, | ||
| requestStartTime: requestStartTime | ||
| ) | ||
| let response = self.parse(urlResponse: urlResponse, | ||
| request: request, | ||
| urlRequest: urlRequest, | ||
| data: data, | ||
| error: networkError, | ||
| requestStartTime: requestStartTime) | ||
|
|
||
| if let response = response { | ||
| let httpURLResponse = urlResponse as? HTTPURLResponse | ||
| var requestRetryScheduled = false | ||
| var retryScheduled = false | ||
|
|
||
| switch response { | ||
| case let .success(response): | ||
|
|
@@ -463,26 +483,28 @@ private extension HTTPClient { | |
| case let .failure(error): | ||
| let httpURLResponse = urlResponse as? HTTPURLResponse | ||
|
|
||
| Logger.debug(Strings.network.api_request_failed( | ||
| request.httpRequest, | ||
| httpCode: httpURLResponse?.httpStatusCode, | ||
| error: error, | ||
| metadata: httpURLResponse?.metadata) | ||
| ) | ||
| Logger.debug(Strings.network.api_request_failed(request.httpRequest, | ||
| httpCode: httpURLResponse?.httpStatusCode, | ||
| error: error, | ||
| metadata: httpURLResponse?.metadata)) | ||
|
|
||
| if httpURLResponse?.isLoadShedder == true { | ||
| Logger.debug(Strings.network.request_handled_by_load_shedder(request.httpRequest.path)) | ||
| } | ||
|
|
||
| requestRetryScheduled = self.retryRequestIfNeeded(request: request, httpURLResponse: httpURLResponse) | ||
| retryScheduled = self.retryRequestWithNextFallbackPathIfNeeded(request: request, | ||
| httpURLResponse: httpURLResponse) | ||
| if !retryScheduled { | ||
| retryScheduled = self.retryRequestIfNeeded(request: request, | ||
| httpURLResponse: httpURLResponse) | ||
| } | ||
| } | ||
|
|
||
| if !requestRetryScheduled { | ||
| if !retryScheduled { | ||
| request.completionHandler?(response) | ||
| } | ||
| } else { | ||
| Logger.debug(Strings.network.retrying_request(httpMethod: request.method.httpMethod, | ||
| path: request.path)) | ||
| Logger.debug(Strings.network.retrying_request(httpMethod: request.method.httpMethod, path: request.path)) | ||
|
|
||
| self.state.modify { | ||
| $0.queuedRequests.insert(request.retriedRequest(), at: 0) | ||
|
|
@@ -540,7 +562,7 @@ private extension HTTPClient { | |
| } | ||
|
|
||
| func convert(request: Request) -> URLRequest? { | ||
| guard let requestURL = request.httpRequest.path.url(proxyURL: SystemInfo.proxyURL) else { | ||
| guard let requestURL = request.getCurrentRequestURL(proxyURL: SystemInfo.proxyURL) else { | ||
| return nil | ||
| } | ||
| var urlRequest = URLRequest(url: requestURL) | ||
|
|
@@ -623,6 +645,36 @@ private extension HTTPClient { | |
| // MARK: - Request Retry Logic | ||
| extension HTTPClient { | ||
|
|
||
| /// Evaluates whether a request should be retried with the next path in the list of fallback paths. | ||
| /// | ||
| /// This function checks the HTTP response status code to determine if the request should be retried | ||
| /// with the next fallback path. If the retry conditions are met, it schedules the request immediately and | ||
| /// returns `true` to indicate that the request was retried. | ||
| /// | ||
| /// - Parameters: | ||
| /// - request: The original `HTTPClient.Request` that may need to be retried. | ||
| /// - httpURLResponse: An optional `HTTPURLResponse` that contains the status code of the response. | ||
| /// - Returns: A Boolean value indicating whether the request was retried. | ||
| internal func retryRequestWithNextFallbackPathIfNeeded( | ||
|
||
| request: HTTPClient.Request, | ||
| httpURLResponse: HTTPURLResponse? | ||
| ) -> Bool { | ||
|
|
||
| guard let statusCode = httpURLResponse?.statusCode, HTTPStatusCode(rawValue: statusCode).isServerError, | ||
| let nextRequest = request.requestWithNextFallbackPath() else { | ||
| return false | ||
| } | ||
|
|
||
| Logger.debug(Strings.network.retrying_request_with_fallback_path( | ||
| httpMethod: nextRequest.method.httpMethod, | ||
| path: nextRequest.path | ||
| )) | ||
| self.state.modify { | ||
| $0.queuedRequests.insert(nextRequest, at: 0) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm nothing to do for this PR since I believe this could actually happen already, but I wonder if we should dedupe any queued requests here... In android we do something like that, and just "hook" the callbacks to the existing queued/in progress request if it exists. |
||
| } | ||
| return true | ||
| } | ||
|
|
||
| /// Evaluates whether a request should be retried and schedules a retry if necessary. | ||
| /// | ||
| /// This function checks the HTTP response status code to determine if the request should be retried. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.