Add request maker.

pull/1/head
Matthew Chen 7 years ago
parent b5ed4e8ead
commit 2894db0d6e

@ -135,46 +135,18 @@ public class ProfileFetcherJob: NSObject {
Logger.error("getProfile: \(recipientId)")
let unidentifiedAccess: SSKUnidentifiedAccess? = self.getUnidentifiedAccess(forRecipientId: recipientId)
let socketType: OWSWebSocketType = unidentifiedAccess == nil ? .default : .UD
if socketManager.canMakeRequests(of: socketType) {
let (promise, resolver) = Promise<SignalServiceProfile>.pending()
let socketSuccess = { (responseObject: Any?) -> Void in
do {
let profile = try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject)
resolver.fulfill(profile)
} catch {
resolver.reject(error)
}
}
let request = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess)
self.socketManager.make(request,
webSocketType: socketType,
success: socketSuccess,
failure: { (statusCode: NSInteger, _:Data?, error: Error) in
// If UD auth fails, try again with non-UD auth.
if unidentifiedAccess != nil && (statusCode == 401 || statusCode == 403) {
Logger.info("Profile request failing over to non-UD auth.")
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: recipientId)
let nonUDRequest = OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: nil)
self.socketManager.make(nonUDRequest,
webSocketType: .default,
success: socketSuccess,
failure: { (_: NSInteger, _:Data?, error: Error) in
resolver.reject(error)
})
return
}
resolver.reject(error)
})
return promise
} else {
return self.signalServiceClient.retrieveProfile(recipientId: recipientId, unidentifiedAccess: unidentifiedAccess)
let requestMaker = RequestMaker(requestFactoryBlock: { (unidentifiedAccessForRequest) -> TSRequest in
return OWSRequestFactory.getProfileRequest(recipientId: recipientId, unidentifiedAccess: unidentifiedAccessForRequest)
}, udAuthFailureBlock: {
// Do nothing
}, websocketFailureBlock: {
// Do nothing
}, recipientId: recipientId,
unidentifiedAccess: unidentifiedAccess)
return requestMaker.makeRequest()
.then { (result: RequestMakerResult) -> Promise<SignalServiceProfile> in
let responseObject: Any? = result.responseObject
return Promise.value(try SignalServiceProfile(recipientId: recipientId, responseObject: responseObject))
}
}

@ -1016,74 +1016,53 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSLogWarn(@"Sending a message with no device messages.");
}
const BOOL isUDSend = messageSend.isUDSend;
TSRequest *request = [OWSRequestFactory submitMessageRequestWithRecipient:recipient.uniqueId
messages:deviceMessages
timeStamp:message.timestamp
unidentifiedAccess:messageSend.unidentifiedAccess];
OWSWebSocketType webSocketType = (isUDSend ? OWSWebSocketTypeUD : OWSWebSocketTypeDefault);
BOOL canMakeWebsocketRequests = ([TSSocketManager.shared canMakeRequestsOfType:webSocketType] &&
!messageSend.hasWebsocketSendFailed);
if (canMakeWebsocketRequests) {
[TSSocketManager.shared makeRequest:request
webSocketType:webSocketType
success:^(id _Nullable responseObject) {
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:isUDSend];
}
failure:^(NSInteger statusCode, NSData *_Nullable responseData, NSError *error) {
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc]
initWithRequestFactoryBlock:^(SSKUnidentifiedAccess *_Nullable unidentifiedAccess) {
return [OWSRequestFactory submitMessageRequestWithRecipient:recipient.recipientId
messages:deviceMessages
timeStamp:message.timestamp
unidentifiedAccess:unidentifiedAccess];
}
udAuthFailureBlock:^{
[messageSend setHasUDAuthFailed];
}
websocketFailureBlock:^{
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipient.recipientId
unidentifiedAccess:messageSend.unidentifiedAccess];
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
dispatch_async([OWSDispatch sendingQueue], ^{
OWSLogDebug(@"Web socket send failed; failing over to REST.");
if (isUDSend && (statusCode == 401 || statusCode == 403)) {
// If a UD send fails due to service response (as opposed to network
// failure), mark recipient as _not_ in UD mode, then retry.
OWSLogDebug(@"UD send failed; failing over to non-UD send.");
[self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeDisabled
recipientId:recipient.uniqueId];
[messageSend setHasUDAuthFailed];
dispatch_async([OWSDispatch sendingQueue], ^{
[self sendMessageToRecipient:messageSend];
});
return;
const BOOL wasSentByUD = result.wasSentByUD;
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:wasSentByUD];
});
})
.catch(^(NSError *error) {
dispatch_async([OWSDispatch sendingQueue], ^{
NSUInteger statusCode = 0;
NSData *_Nullable responseData = nil;
if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (underlyingError) {
responseData
= underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
} else {
OWSFailDebug(@"Missing underlying error: %@", error);
}
} else {
OWSFailDebug(@"Unexpected error: %@", error);
}
// Websockets can fail in different ways, so we don't decrement remainingAttempts for websocket
// failure. Instead we fall back to REST, which will decrement retries. e.g. after linking a new
// device, sync messages will fail until the websocket re-opens.
messageSend.hasWebsocketSendFailed = YES;
[self sendMessageToRecipient:messageSend];
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
});
}];
} else {
[self.networkManager makeRequest:request
success:^(NSURLSessionDataTask *task, id responseObject) {
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:isUDSend];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
NSInteger statusCode = response.statusCode;
NSData *_Nullable responseData = error.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
if (isUDSend && (statusCode == 401 || statusCode == 403)) {
// If a UD send fails due to service response (as opposed to network
// failure), mark recipient as _not_ in UD mode, then retry.
OWSLogDebug(@"UD send failed; failing over to non-UD send.");
[self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeDisabled
recipientId:recipient.uniqueId];
[messageSend setHasUDAuthFailed];
dispatch_async([OWSDispatch sendingQueue], ^{
[self sendMessageToRecipient:messageSend];
});
return;
}
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
}];
}
}) retainUntilComplete];
}
- (void)messageSendDidSucceed:(OWSMessageSend *)messageSend
@ -1497,90 +1476,41 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
NSString *recipientId = recipient.recipientId;
OWSAssertDebug(recipientId.length > 0);
const BOOL isUDSend = messageSend.isUDSend;
TSRequest *request = [OWSRequestFactory recipientPrekeyRequestWithRecipient:recipientId
deviceId:[deviceId stringValue]
unidentifiedAccess:messageSend.unidentifiedAccess];
OWSWebSocketType webSocketType = (isUDSend ? OWSWebSocketTypeUD : OWSWebSocketTypeDefault);
BOOL canMakeWebsocketRequests
= ([TSSocketManager.shared canMakeRequestsOfType:webSocketType] && !messageSend.hasWebsocketSendFailed);
if (canMakeWebsocketRequests) {
[TSSocketManager.shared makeRequest:request
webSocketType:webSocketType
success:^(id _Nullable responseObject) {
dispatch_async([OWSDispatch sendingQueue], ^{
PreKeyBundle *_Nullable bundle = [PreKeyBundle preKeyBundleFromDictionary:responseObject
forDeviceNumber:deviceId];
success(bundle);
});
}
failure:^(NSInteger statusCode, NSData *_Nullable responseData, NSError *error) {
dispatch_async([OWSDispatch sendingQueue], ^{
OWSLogDebug(@"Web socket prekey request failed; failing over to REST: %@.", error);
if (isUDSend && (statusCode == 401 || statusCode == 403)) {
// If a UD send fails due to service response (as opposed to network
// failure), mark recipient as _not_ in UD mode, then retry.
OWSLogDebug(@"UD prekey request failed; failing over to non-UD prekey request.");
[self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeDisabled
recipientId:recipient.uniqueId];
[messageSend setHasUDAuthFailed];
// Try again without UD auth.
[self makePrekeyRequestForMessageSend:messageSend
deviceId:deviceId
success:success
failure:failure];
return;
}
// Websockets can fail in different ways, so we don't decrement remainingAttempts for websocket
// failure. Instead we fall back to REST, which will decrement retries. e.g. after linking a new
// device, sync messages will fail until the websocket re-opens.
messageSend.hasWebsocketSendFailed = YES;
// Try again without websocket.
[self makePrekeyRequestForMessageSend:messageSend
deviceId:deviceId
success:success
failure:failure];
});
}];
} else {
[self.networkManager makeRequest:request
completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
success:^(NSURLSessionDataTask *task, id responseObject) {
dispatch_async([OWSDispatch sendingQueue], ^{
PreKeyBundle *_Nullable bundle = [PreKeyBundle preKeyBundleFromDictionary:responseObject
forDeviceNumber:deviceId];
success(bundle);
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
dispatch_async([OWSDispatch sendingQueue], ^{
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents messageSenderErrorRecipientPrekeyRequestFailed]);
}
OWSLogDebug(@"REST prekey request failed: %@.", error);
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
NSUInteger statusCode = response.statusCode;
if (isUDSend && (statusCode == 401 || statusCode == 403)) {
// If a UD send fails due to service response (as opposed to network
// failure), mark recipient as _not_ in UD mode, then retry.
OWSLogDebug(@"UD prekey request failed; failing over to non-UD prekey request.");
[self.udManager setUnidentifiedAccessMode:UnidentifiedAccessModeDisabled
recipientId:recipient.uniqueId];
[messageSend setHasUDAuthFailed];
// Try again without UD auth.
[self makePrekeyRequestForMessageSend:messageSend
deviceId:deviceId
success:success
failure:failure];
return;
}
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc]
initWithRequestFactoryBlock:^(SSKUnidentifiedAccess *_Nullable unidentifiedAccess) {
return [OWSRequestFactory recipientPrekeyRequestWithRecipient:recipientId
deviceId:[deviceId stringValue]
unidentifiedAccess:unidentifiedAccess];
}
udAuthFailureBlock:^{
[messageSend setHasUDAuthFailed];
}
websocketFailureBlock:^{
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipientId
unidentifiedAccess:messageSend.unidentifiedAccess];
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
// We _do not_ want to dispatch to the sendingQueue here; we're
// using a semaphore on the sendingQueue to block on this request.
const id responseObject = result.responseObject;
PreKeyBundle *_Nullable bundle =
[PreKeyBundle preKeyBundleFromDictionary:responseObject forDeviceNumber:deviceId];
success(bundle);
})
.catch(^(NSError *error) {
// We _do not_ want to dispatch to the sendingQueue here; we're
// using a semaphore on the sendingQueue to block on this request.
NSUInteger statusCode = 0;
if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
} else {
OWSFailDebug(@"Unexpected error: %@", error);
}
failure(statusCode);
});
}];
}
failure(statusCode);
}) retainUntilComplete];
}
// NOTE: This method uses exceptions for control flow.

@ -0,0 +1,157 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
public enum RequestMakerError: Error {
case websocketRequestError(statusCode : Int, responseData : Data?, underlyingError : Error)
}
@objc(OWSRequestMakerResult)
public class RequestMakerResult: NSObject {
@objc
public let responseObject: Any?
@objc
public let wasSentByUD: Bool
@objc
public init(responseObject: Any?,
wasSentByUD: Bool) {
self.responseObject = responseObject
self.wasSentByUD = wasSentByUD
}
}
// A utility class that handles:
//
// * UD auth-to-Non-UD auth failover.
// * Websocket-to-REST failover.
@objc(OWSRequestMaker)
public class RequestMaker: NSObject {
public typealias RequestFactoryBlock = (SSKUnidentifiedAccess?) -> TSRequest
public typealias UDAuthFailureBlock = () -> Void
public typealias WebsocketFailureBlock = () -> Void
private let requestFactoryBlock: RequestFactoryBlock
private let udAuthFailureBlock: UDAuthFailureBlock
private let websocketFailureBlock: WebsocketFailureBlock
private let recipientId: String
private let unidentifiedAccess: SSKUnidentifiedAccess?
@objc
public init(requestFactoryBlock : @escaping RequestFactoryBlock,
udAuthFailureBlock : @escaping UDAuthFailureBlock,
websocketFailureBlock : @escaping WebsocketFailureBlock,
recipientId: String,
unidentifiedAccess: SSKUnidentifiedAccess?) {
self.requestFactoryBlock = requestFactoryBlock
self.udAuthFailureBlock = udAuthFailureBlock
self.websocketFailureBlock = websocketFailureBlock
self.recipientId = recipientId
self.unidentifiedAccess = unidentifiedAccess
}
// MARK: - Dependencies
private var socketManager: TSSocketManager {
return SSKEnvironment.shared.socketManager
}
private var networkManager: TSNetworkManager {
return SSKEnvironment.shared.networkManager
}
private var udManager: OWSUDManager {
return SSKEnvironment.shared.udManager
}
// MARK: -
@objc
public func makeRequestObjc() -> AnyPromise {
let promise = makeRequest()
.recover { (error: Error) -> Promise<RequestMakerResult> in
switch error {
case NetworkManagerError.taskError(_, let underlyingError):
throw underlyingError
default:
throw error
}
}
let anyPromise = AnyPromise(promise)
anyPromise.retainUntilComplete()
return anyPromise
}
public func makeRequest() -> Promise<RequestMakerResult> {
return makeRequestInternal(skipUD: false, skipWebsocket: false)
}
private func makeRequestInternal(skipUD: Bool, skipWebsocket: Bool) -> Promise<RequestMakerResult> {
var unidentifiedAccessForRequest: SSKUnidentifiedAccess?
if !skipUD {
unidentifiedAccessForRequest = unidentifiedAccess
}
let isUDSend = unidentifiedAccessForRequest != nil
let request = requestFactoryBlock(unidentifiedAccessForRequest)
let webSocketType: OWSWebSocketType = (isUDSend ? .UD : .default)
let canMakeWebsocketRequests = (socketManager.canMakeRequests(of: webSocketType) && !skipWebsocket)
if canMakeWebsocketRequests {
return Promise { resolver in
socketManager.make(request, webSocketType: webSocketType, success: { (responseObject: Any?) in
_ = resolver.fulfill(RequestMakerResult(responseObject: responseObject, wasSentByUD: isUDSend))
}) { (statusCode: Int, responseData: Data?, error: Error) in
resolver.reject(RequestMakerError.websocketRequestError(statusCode: statusCode, responseData: responseData, underlyingError: error))
}
}.recover { (error: Error) -> Promise<RequestMakerResult> in
switch error {
case RequestMakerError.websocketRequestError(let statusCode, _, _):
if isUDSend && (statusCode == 401 || statusCode == 403) {
// If a UD send fails due to service response (as opposed to network
// failure), mark recipient as _not_ in UD mode, then retry.
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId)
self.udAuthFailureBlock()
Logger.info("UD websocket request failed; failing over to non-UD websocket request.")
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
}
break
default:
break
}
self.websocketFailureBlock()
Logger.info("Non-UD Web socket request failed; failing over to REST request: \(error).")
return self.makeRequestInternal(skipUD: skipUD, skipWebsocket: true)
}
} else {
return self.networkManager.makePromise(request: request)
.then { (networkManagerResult: TSNetworkManager.NetworkManagerResult) -> Promise<RequestMakerResult> in
// Unwrap the network manager promise into a request maker promise.
return Promise.value(RequestMakerResult(responseObject: networkManagerResult.responseObject, wasSentByUD: isUDSend))
}.recover { (error: Error) -> Promise<RequestMakerResult> in
switch error {
case NetworkManagerError.taskError(let task, _):
let statusCode = task.statusCode()
if isUDSend && (statusCode == 401 || statusCode == 403) {
// If a UD send fails due to service response (as opposed to network
// failure), mark recipient as _not_ in UD mode, then retry.
self.udManager.setUnidentifiedAccessMode(.disabled, recipientId: self.recipientId)
self.udAuthFailureBlock()
Logger.info("UD REST request failed; failing over to non-UD REST request.")
return self.makeRequestInternal(skipUD: true, skipWebsocket: skipWebsocket)
}
break
default:
break
}
Logger.debug("Non-UD REST request failed: \(error).")
throw error
}
}
}
}

@ -27,9 +27,10 @@ extension NetworkManagerError {
}
extension TSNetworkManager {
public typealias NetworkManagerResult = (task: URLSessionDataTask, responseObject: Any?)
public func makePromise(request: TSRequest) -> Promise<(task: URLSessionDataTask, responseObject: Any?)> {
let (promise, resolver) = Promise<(task: URLSessionDataTask, responseObject: Any?)>.pending()
public func makePromise(request: TSRequest) -> Promise<NetworkManagerResult> {
let (promise, resolver) = Promise<NetworkManagerResult>.pending()
self.makeRequest(request,
success: { task, responseObject in

Loading…
Cancel
Save