diff --git a/Source/ARTFallback.h b/Source/ARTFallback.h index 03b5f7989..9dc99a00e 100644 --- a/Source/ARTFallback.h +++ b/Source/ARTFallback.h @@ -20,6 +20,5 @@ returns a random fallback host, returns null when all hosts have been popped. */ -(NSString *) popFallbackHost; -+(bool) shouldTryFallback:(ARTHttpResponse *) response options:(ARTClientOptions *) options; @end diff --git a/Source/ARTRest.m b/Source/ARTRest.m index 5578ddaa1..c1d808e9c 100644 --- a/Source/ARTRest.m +++ b/Source/ARTRest.m @@ -136,27 +136,30 @@ - (void)executeRequest:(NSMutableURLRequest *)request completion:(void (^)(NSHTT // Send it again, requesting a new token (forward callback) [self.logger debug:__FILE__ line:__LINE__ message:@"requesting new token"]; [self executeRequest:request withAuthOption:ARTAuthenticationNewToken completion:callback]; - } else if (callback) { + return; + } else { // Return error with HTTP StatusCode if ARTErrorStatusCode does not exist if (!dataError) { dataError = [NSError errorWithDomain:ARTAblyErrorDomain code:response.statusCode userInfo:@{NSLocalizedDescriptionKey:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]}]; } - callback(nil, nil, dataError); + error = dataError; } - return; - } - if (!blockFallbacks && [self shouldRetryWithFallback:request response:response error:error]) { - blockFallbacks = [[ARTFallback alloc] init]; } - if (error && blockFallbacks) { - NSString *host = [blockFallbacks popFallbackHost]; - if (host != nil) { - NSMutableURLRequest *newRequest = [request copy]; - NSURL *url = request.URL; - NSString *urlStr = [NSString stringWithFormat:@"%@://%@:%@%@?%@", url.scheme, host, url.port, url.path, (url.query ? url.query : @"")]; - newRequest.URL = [NSURL URLWithString:urlStr]; - [self executeRequest:newRequest completion:callback fallbacks:fallbacks]; - return; + if ([self shouldRetryWithFallback:request response:response error:error]) { + if (!blockFallbacks && [request.URL.host isEqualToString:[ARTDefault restHost]]) { + blockFallbacks = [[ARTFallback alloc] init]; + } + if (blockFallbacks) { + NSString *host = [blockFallbacks popFallbackHost]; + if (host != nil) { + [self.logger debug:__FILE__ line:__LINE__ message:@"host is down; retrying request at %@", host]; + NSMutableURLRequest *newRequest = [request copy]; + NSURL *url = request.URL; + NSString *urlStr = [NSString stringWithFormat:@"%@://%@:%@%@?%@", url.scheme, host, url.port, url.path, (url.query ? url.query : @"")]; + newRequest.URL = [NSURL URLWithString:urlStr]; + [self executeRequest:newRequest completion:callback fallbacks:fallbacks]; + return; + } } } if (callback) { @@ -167,9 +170,6 @@ - (void)executeRequest:(NSMutableURLRequest *)request completion:(void (^)(NSHTT } - (BOOL)shouldRetryWithFallback:(NSMutableURLRequest *)request response:(NSHTTPURLResponse *)response error:(NSError *)error { - if (![request.URL.host isEqualToString:[ARTDefault restHost]]) { - return NO; - } if (response.statusCode >= 500 && response.statusCode <= 504) { return YES; } diff --git a/Spec/RestClient.swift b/Spec/RestClient.swift index 38b972eab..fe2e7ff55 100644 --- a/Spec/RestClient.swift +++ b/Spec/RestClient.swift @@ -535,6 +535,73 @@ class RestClient: QuickSpec { } + // RSC15 + context("Host Fallback") { + + // RSC15d + context("should use an alternative host when") { + + for caseTest: NetworkAnswer in [.HostUnreachable, + .RequestTimeout(timeout: 0.1), + .HostInternalError(code: 501)] { + it("\(caseTest)") { + let options = ARTClientOptions(key: "xxxx:xxxx") + let client = ARTRest(options: options) + client.httpExecutor = testHTTPExecutor + testHTTPExecutor.http = MockHTTP(network: caseTest) + let channel = client.channels.get("test") + + testHTTPExecutor.afterRequest = { _ in + if testHTTPExecutor.requests.count == 2 { + // Stop + testHTTPExecutor.http = nil + } + } + + waitUntil(timeout: testTimeout) { done in + channel.publish(nil, data: "nil") { _ in + done() + } + } + + expect(testHTTPExecutor.requests).to(haveCount(2)) + if testHTTPExecutor.requests.count != 2 { + return + } + expect(NSRegularExpression.match(testHTTPExecutor.requests[0].URL!.absoluteString, pattern: "//rest.ably.io")).to(beTrue()) + expect(NSRegularExpression.match(testHTTPExecutor.requests[1].URL!.absoluteString, pattern: "//[a-e].ably-realtime.com")).to(beTrue()) + } + } + + } + + // RSC15d + it("should not use an alternative host when the client receives an bad request") { + let options = ARTClientOptions(key: "xxxx:xxxx") + let client = ARTRest(options: options) + client.httpExecutor = testHTTPExecutor + testHTTPExecutor.http = MockHTTP(network: .Host400BadRequest) + let channel = client.channels.get("test") + + testHTTPExecutor.afterRequest = { _ in + if testHTTPExecutor.requests.count == 2 { + // Stop + testHTTPExecutor.http = nil + } + } + + waitUntil(timeout: testTimeout) { done in + channel.publish(nil, data: "nil") { _ in + done() + } + } + + expect(testHTTPExecutor.requests).to(haveCount(1)) + expect(NSRegularExpression.match(testHTTPExecutor.requests[0].URL!.absoluteString, pattern: "//rest.ably.io")).to(beTrue()) + } + + } + } //RestClient } } \ No newline at end of file diff --git a/Spec/TestUtilities.swift b/Spec/TestUtilities.swift index a84d06d17..06114ad05 100644 --- a/Spec/TestUtilities.swift +++ b/Spec/TestUtilities.swift @@ -500,6 +500,7 @@ enum NetworkAnswer { case HostUnreachable case RequestTimeout(timeout: NSTimeInterval) case HostInternalError(code: Int) + case Host400BadRequest case Custom(response: NSHTTPURLResponse?, data: NSData?, error: NSError?) } @@ -524,6 +525,8 @@ class MockHTTP: ARTHttp { } case .HostInternalError(let code): callback?(NSHTTPURLResponse(URL: NSURL(string: "http://ios.test.suite")!, statusCode: code, HTTPVersion: nil, headerFields: nil), nil, nil) + case .Host400BadRequest: + callback?(NSHTTPURLResponse(URL: NSURL(string: "http://ios.test.suite")!, statusCode: 400, HTTPVersion: nil, headerFields: nil), nil, nil) case .Custom(let response, let data, let error): callback?(response, data, error) }