From 67d9eeab5b2a2b4a9968f65fc78ffedfce69bf7d Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 13 Aug 2021 00:47:00 -0300 Subject: [PATCH] Clean up ahead of PR. --- Signal/src/Calls/CallService.swift | 4 +- Signal/src/util/Stripe.swift | 14 +- .../MessageSendingPerformanceTest.swift | 8 +- SignalMessaging/groups/GroupsV2Impl.swift | 19 +-- .../Interactions/OWSLinkPreview.swift | 24 ++- .../Messages/MessageSender+SenderKey.swift | 4 +- .../src/Network/API/OWSUpload.swift | 17 ++- .../src/Network/API/RESTNetworkManager.swift | 34 ++--- .../src/Network/OWSURLSession.swift | 39 ++--- .../src/Network/WebSockets/HTTP+OWS.swift | 144 ++++++++++++++++-- .../src/Network/WebSockets/OWSWebSocket.swift | 90 +---------- .../src/Util/StorageService.swift | 9 +- 12 files changed, 217 insertions(+), 189 deletions(-) diff --git a/Signal/src/Calls/CallService.swift b/Signal/src/Calls/CallService.swift index bb80729c68..7267318215 100644 --- a/Signal/src/Calls/CallService.swift +++ b/Signal/src/Calls/CallService.swift @@ -976,8 +976,8 @@ extension CallService: CallManagerDelegate { }.done(on: .main) { response in self.callManager.receivedHttpResponse( requestId: requestId, - statusCode: UInt16(response.statusCode), - body: response.responseData + statusCode: UInt16(response.responseStatusCode), + body: response.responseBodyData ) }.catch(on: .main) { error in if error.isNetworkFailureOrTimeout { diff --git a/Signal/src/util/Stripe.swift b/Signal/src/util/Stripe.swift index b7056debdb..75027facf4 100644 --- a/Signal/src/util/Stripe.swift +++ b/Signal/src/util/Stripe.swift @@ -119,11 +119,10 @@ fileprivate extension Stripe { firstly(on: .sharedUserInitiated) { () -> Promise in try postForm(endpoint: "tokens", parameters: parameters(for: payment)) }.map(on: .sharedUserInitiated) { response in - guard let responseData = response.responseData, !responseData.isEmpty else { - throw OWSAssertionError("Missing response data") + guard let json = response.responseBodyJson else { + throw OWSAssertionError("Missing responseBodyJson") } - let responseObject = try JSONSerialization.jsonObject(with: responseData, options: .init(rawValue: 0)) - guard let parser = ParamParser(responseObject: responseObject) else { + guard let parser = ParamParser(responseObject: json) else { throw OWSAssertionError("Failed to decode JSON response") } return try parser.required(key: "id") @@ -137,11 +136,10 @@ fileprivate extension Stripe { let parameters: [String: Any] = ["card": ["token": tokenId], "type": "card"] return try postForm(endpoint: "payment_methods", parameters: parameters) }.map(on: .sharedUserInitiated) { response in - guard let responseData = response.responseData, !responseData.isEmpty else { - throw OWSAssertionError("Missing response data") + guard let json = response.responseBodyJson else { + throw OWSAssertionError("Missing responseBodyJson") } - let responseObject = try JSONSerialization.jsonObject(with: responseData, options: .init(rawValue: 0)) - guard let parser = ParamParser(responseObject: responseObject) else { + guard let parser = ParamParser(responseObject: json) else { throw OWSAssertionError("Failed to decode JSON response") } return try parser.required(key: "id") diff --git a/Signal/test/PerformanceTests/MessageSendingPerformanceTest.swift b/Signal/test/PerformanceTests/MessageSendingPerformanceTest.swift index e3ba350bdf..d1328012f3 100644 --- a/Signal/test/PerformanceTests/MessageSendingPerformanceTest.swift +++ b/Signal/test/PerformanceTests/MessageSendingPerformanceTest.swift @@ -209,10 +209,10 @@ class StubbableNetworkManager: NetworkManager { var block: (TSRequest, NetworkManagerSuccess, NetworkManagerFailure) -> Void = { request, success, _ in Logger.info("faking success for request: \(request)") let response = OWSHTTPResponseImpl(requestUrl: request.url!, - status: 200, - headers: OWSHttpHeaders(), - bodyData: nil, - message: nil) + status: 200, + headers: OWSHttpHeaders(), + bodyData: nil, + message: nil) success(response) } diff --git a/SignalMessaging/groups/GroupsV2Impl.swift b/SignalMessaging/groups/GroupsV2Impl.swift index 1cc635b006..5d5b6c15c4 100644 --- a/SignalMessaging/groups/GroupsV2Impl.swift +++ b/SignalMessaging/groups/GroupsV2Impl.swift @@ -284,7 +284,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior404: .fail) }.then(on: .global()) { (response: OWSHTTPResponse) -> Promise in - guard let changeActionsProtoData = response.responseData else { + guard let changeActionsProtoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } let changeActionsProto = try GroupsV2Protos.parseAndVerifyChangeActionsProto(changeActionsProtoData, @@ -490,7 +490,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior404: .fail) }.map(on: .global()) { (response: OWSHTTPResponse) -> GroupsProtoAvatarUploadAttributes in - guard let protoData = response.responseData else { + guard let protoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } return try GroupsProtoAvatarUploadAttributes(serializedData: protoData) @@ -549,7 +549,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior403: .removeFromGroup, behavior404: .groupDoesNotExistOnService) }.map(on: .global()) { (response: OWSHTTPResponse) -> GroupsProtoGroup in - guard let groupProtoData = response.responseData else { + guard let groupProtoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } return try GroupsProtoGroup(serializedData: groupProtoData) @@ -634,7 +634,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior403: .ignore, behavior404: .fail) }.map(on: .global()) { (response: OWSHTTPResponse) -> GroupsProtoGroupChanges in - guard let groupChangesProtoData = response.responseData else { + guard let groupChangesProtoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } return try GroupsProtoGroupChanges(serializedData: groupChangesProtoData) @@ -996,9 +996,10 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { headers: request.headers.headers, body: request.bodyData) }.map(on: .global()) { (response: OWSHTTPResponse) -> OWSHTTPResponse in - let hasValidStatusCode = [200, 206].contains(response.statusCode) + let statusCode = response.responseStatusCode + let hasValidStatusCode = [200, 206].contains(statusCode) guard hasValidStatusCode else { - throw OWSAssertionError("Invalid status code: \(response.statusCode)") + throw OWSAssertionError("Invalid status code: \(statusCode)") } // NOTE: responseObject may be nil; not all group v2 responses have bodies. @@ -1530,7 +1531,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior403: behavior403, behavior404: .fail) }.map(on: .global()) { (response: OWSHTTPResponse) -> GroupInviteLinkPreview in - guard let protoData = response.responseData else { + guard let protoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } let groupInviteLinkPreview = try GroupsV2Protos.parseGroupInviteLinkPreview(protoData, groupV2Params: groupV2Params) @@ -1691,7 +1692,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior403: .expiredGroupInviteLink, behavior404: .fail) }.then(on: .global()) { (response: OWSHTTPResponse) -> Promise in - guard let changeActionsProtoData = response.responseData else { + guard let changeActionsProtoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } // The PATCH request that adds us to the group (as a full or requesting member) @@ -2165,7 +2166,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift { behavior403: .fetchGroupUpdates, behavior404: .fail) }.map(on: .global()) { (response: OWSHTTPResponse) -> GroupsProtoGroupExternalCredential in - guard let groupProtoData = response.responseData else { + guard let groupProtoData = response.responseBodyData else { throw OWSAssertionError("Invalid responseObject.") } return try GroupsProtoGroupExternalCredential(serializedData: groupProtoData) diff --git a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift index a6f4879397..016bebcf25 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift +++ b/SignalServiceKit/src/Messages/Interactions/OWSLinkPreview.swift @@ -413,22 +413,19 @@ public class OWSLinkPreviewManager: NSObject, Dependencies { self.buildOWSURLSession().dataTaskPromise(url.absoluteString, method: .get) .catchCancellation(andThrow: LinkPreviewError.invalidPreview) - }.map(on: Self.workQueue) { (httpResponse: OWSHTTPResponse) -> (URL, String) in - let task = httpResponse.task - guard let response = task.response as? HTTPURLResponse, - let respondingUrl = response.url, - response.statusCode >= 200 && response.statusCode < 300 else { - Logger.warn("Invalid response: \(type(of: task.response)).") + }.map(on: Self.workQueue) { (response: OWSHTTPResponse) -> (URL, String) in + let statusCode = response.responseStatusCode + guard statusCode >= 200 && statusCode < 300 else { + Logger.warn("Invalid response: \(statusCode).") throw LinkPreviewError.fetchFailure } - guard let data = httpResponse.responseData, - let string = String(data: data, urlResponse: response), + guard let string = response.responseBodyString, string.count > 0 else { Logger.warn("Response object could not be parsed") throw LinkPreviewError.invalidPreview } - return (respondingUrl, string) + return (response.requestUrl, string) } } @@ -439,13 +436,12 @@ public class OWSLinkPreviewManager: NSObject, Dependencies { }.map(on: Self.workQueue) { (httpResponse: OWSHTTPResponse) -> Data in try autoreleasepool { - let task = httpResponse.task - guard let response = task.response as? HTTPURLResponse, - response.statusCode >= 200 && response.statusCode < 300 else { - Logger.warn("Invalid response: \(type(of: task.response)).") + let statusCode = httpResponse.responseStatusCode + guard statusCode >= 200 && statusCode < 300 else { + Logger.warn("Invalid response: \(statusCode).") throw LinkPreviewError.fetchFailure } - guard let rawData = httpResponse.responseData, + guard let rawData = httpResponse.responseBodyData, rawData.count < Self.maxFetchedContentSize else { Logger.warn("Response object could not be parsed") throw LinkPreviewError.invalidPreview diff --git a/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift b/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift index 12f7ba7461..1e625a590e 100644 --- a/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift +++ b/SignalServiceKit/src/Messages/MessageSender+SenderKey.swift @@ -400,10 +400,10 @@ extension MessageSender { recipients: recipients, udAccessMap: udAccessMap) }.map(on: senderKeyQueue) { response -> SenderKeySendResult in - guard response.statusCode == 200 else { throw + guard response.responseStatusCode == 200 else { throw OWSAssertionError("Unhandled error") } - let response = try Self.decodeSuccessResponse(data: response.responseData) + let response = try Self.decodeSuccessResponse(data: response.responseBodyData) let uuids404 = Set(response.uuids404) let successful = try recipients.filter { diff --git a/SignalServiceKit/src/Network/API/OWSUpload.swift b/SignalServiceKit/src/Network/API/OWSUpload.swift index bd4548e9f6..4223084834 100644 --- a/SignalServiceKit/src/Network/API/OWSUpload.swift +++ b/SignalServiceKit/src/Network/API/OWSUpload.swift @@ -387,11 +387,12 @@ public class OWSAttachmentUploadV2: NSObject { let urlSession = OWSUpload.cdnUrlSession(forCdnNumber: form.cdnNumber) let body = "".data(using: .utf8) return urlSession.dataTaskPromise(urlString, method: .post, headers: headers, body: body) - }.map(on: Self.serialQueue) { (response: OWSHTTPResponse) in - guard response.statusCode == 201 else { - throw OWSAssertionError("Invalid statusCode: \(response.statusCode).") + }.map(on: Self.serialQueue) { (response: HTTPResponse) in + let statusCode = response.responseStatusCode + guard statusCode == 201 else { + throw OWSAssertionError("Invalid statusCode: \(statusCode).") } - guard let locationHeader = response.allHeaderFields["Location"] as? String else { + guard let locationHeader = response.responseHeaders["Location"] else { throw OWSAssertionError("Missing location header.") } guard locationHeader.lowercased().hasPrefix("http") else { @@ -614,9 +615,9 @@ public class OWSAttachmentUploadV2: NSObject { return urlSession.dataTaskPromise(urlString, method: .put, headers: headers, body: body) }.map(on: Self.serialQueue) { (response: OWSHTTPResponse) in - - if response.statusCode != 308 { - owsFailDebug("Invalid status code: \(response.statusCode).") + let statusCode = response.responseStatusCode + if statusCode != 308 { + owsFailDebug("Invalid status code: \(statusCode).") // Return zero to restart the upload. return 0 } @@ -631,7 +632,7 @@ public class OWSAttachmentUploadV2: NSObject { // // * https://cloud.google.com/storage/docs/performing-resumable-uploads#resume-upload // * https://cloud.google.com/storage/docs/resumable-uploads - guard let rangeHeader = response.allHeaderFields["Range"] as? String else { + guard let rangeHeader = response.responseHeaders["Range"] else { Logger.warn("Missing Range header.") throw OWSUploadError.missingRangeHeader diff --git a/SignalServiceKit/src/Network/API/RESTNetworkManager.swift b/SignalServiceKit/src/Network/API/RESTNetworkManager.swift index 8fb8226eaf..b5d5c5bda4 100644 --- a/SignalServiceKit/src/Network/API/RESTNetworkManager.swift +++ b/SignalServiceKit/src/Network/API/RESTNetworkManager.swift @@ -158,27 +158,27 @@ public class RESTSessionManager: NSObject { } let afSuccess = { (task: URLSessionDataTask?, responseObject: Any?) in - var responseStatus: UInt32 = 0 - let responseHeaders = parseResponseHeaders(task: task) + guard let httpUrlResponse = parseResponse(task: task) else { + if DebugFlags.internalLogging, + let response = task?.response { + Logger.warn("Invalid response: \(response)") + } + owsFailDebug("Invalid response") + failure(OWSHTTPErrorWrapper(error: .invalidResponse(requestUrl: requestUrl))) + return + } // TODO: Can we extract a response body? var responseData: Data? - if let response = parseResponse(task: task) { - responseStatus = UInt32(response.statusCode) - if let responseObject = responseObject { - if let data = responseObject as? Data { - responseData = data - } else { - owsFailDebug("Invalid response: \(type(of: responseObject))") - } + if let responseObject = responseObject { + if let data = responseObject as? Data { + responseData = data + } else { + owsFailDebug("Invalid response: \(type(of: responseObject))") } - } else { - owsFailDebug("Invalid response.") } - let response = OWSHTTPResponseImpl(requestUrl: requestUrl, - status: responseStatus, - headers: responseHeaders, - bodyData: responseData, - message: nil) + let response = OWSHTTPResponseImpl.build(requestUrl: requestUrl, + httpUrlResponse: httpUrlResponse, + bodyData: responseData) success(response) } diff --git a/SignalServiceKit/src/Network/OWSURLSession.swift b/SignalServiceKit/src/Network/OWSURLSession.swift index fcb6cb2d85..c5b589ee59 100644 --- a/SignalServiceKit/src/Network/OWSURLSession.swift +++ b/SignalServiceKit/src/Network/OWSURLSession.swift @@ -53,19 +53,7 @@ public enum HTTPMethod { // MARK: - // TODO: Remove in favor of HTTPResponse? -public struct OWSHTTPResponse { - public let task: URLSessionTask - public let httpUrlResponse: HTTPURLResponse - public let responseData: Data? - - public var statusCode: Int { - httpUrlResponse.statusCode - } - - public var allHeaderFields: [AnyHashable: Any] { - httpUrlResponse.allHeaderFields - } -} +public typealias OWSHTTPResponse = HTTPResponse // MARK: - @@ -271,13 +259,13 @@ public class OWSURLSession: NSObject { } private class func uploadOrDataTaskCompletionPromise(requestConfig: RequestConfig, - responseData: Data?) -> Promise { + responseData: Data?) -> Promise { firstly { baseCompletionPromise(requestConfig: requestConfig, responseData: responseData) - }.map(on: .global()) { (httpUrlResponse: HTTPURLResponse) -> OWSHTTPResponse in - OWSHTTPResponse(task: requestConfig.task, - httpUrlResponse: httpUrlResponse, - responseData: responseData) + }.map(on: .global()) { (httpUrlResponse: HTTPURLResponse) -> HTTPResponse in + OWSHTTPResponseImpl.build(requestUrl: requestConfig.requestUrl, + httpUrlResponse: httpUrlResponse, + bodyData: responseData) } } @@ -786,11 +774,12 @@ public extension OWSURLSession { return firstly { () -> Promise<(URLSessionTask, Data?)> in taskState.promise - }.then(on: .global()) { (_, responseData: Data?) -> Promise in + }.then(on: .global()) { (_, responseData: Data?) -> Promise in guard let requestConfig = requestConfig else { throw OWSAssertionError("Missing requestConfig.") } - return Self.uploadOrDataTaskCompletionPromise(requestConfig: requestConfig, responseData: responseData) + return Self.uploadOrDataTaskCompletionPromise(requestConfig: requestConfig, + responseData: responseData) } } @@ -831,11 +820,12 @@ public extension OWSURLSession { return firstly { () -> Promise<(URLSessionTask, Data?)> in taskState.promise - }.then(on: .global()) { (_, responseData: Data?) -> Promise in + }.then(on: .global()) { (_, responseData: Data?) -> Promise in guard let requestConfig = requestConfig else { throw OWSAssertionError("Missing requestConfig.") } - return Self.uploadOrDataTaskCompletionPromise(requestConfig: requestConfig, responseData: responseData) + return Self.uploadOrDataTaskCompletionPromise(requestConfig: requestConfig, + responseData: responseData) } } @@ -886,11 +876,12 @@ public extension OWSURLSession { return firstly { () -> Promise<(URLSessionTask, Data?)> in taskState.promise - }.then(on: .global()) { (_, responseData: Data?) -> Promise in + }.then(on: .global()) { (_, responseData: Data?) -> Promise in guard let requestConfig = requestConfig else { throw OWSAssertionError("Missing requestConfig.") } - return Self.uploadOrDataTaskCompletionPromise(requestConfig: requestConfig, responseData: responseData) + return Self.uploadOrDataTaskCompletionPromise(requestConfig: requestConfig, + responseData: responseData) } } diff --git a/SignalServiceKit/src/Network/WebSockets/HTTP+OWS.swift b/SignalServiceKit/src/Network/WebSockets/HTTP+OWS.swift index 6e5eb0d6ca..0114eda400 100644 --- a/SignalServiceKit/src/Network/WebSockets/HTTP+OWS.swift +++ b/SignalServiceKit/src/Network/WebSockets/HTTP+OWS.swift @@ -18,6 +18,7 @@ public protocol HTTPResponse { var responseHeaders: [String: String] { get } var responseBodyData: Data? { get } var responseBodyJson: Any? { get } + var responseBodyString: String? { get } } // MARK: - @@ -56,6 +57,7 @@ public struct HTTPErrorServiceResponse { public enum OWSHTTPError: Error, IsRetryableProvider { case invalidAppState(requestUrl: URL) case invalidRequest(requestUrl: URL) + case invalidResponse(requestUrl: URL) // Request failed without a response from the service. case networkFailure(requestUrl: URL) // Request failed with a response from the service. @@ -111,6 +113,8 @@ public enum OWSHTTPError: Error, IsRetryableProvider { switch self { case .invalidAppState, .invalidRequest: return false + case .invalidResponse: + return true case .networkFailure: return true case .serviceResponse: @@ -123,7 +127,6 @@ public enum OWSHTTPError: Error, IsRetryableProvider { return true } } - return true } } @@ -137,6 +140,8 @@ extension OWSHTTPError: HTTPError { return requestUrl case .invalidRequest(let requestUrl): return requestUrl + case .invalidResponse(let requestUrl): + return requestUrl case .networkFailure(let requestUrl): return requestUrl case .serviceResponse(let serviceResponse): @@ -147,7 +152,7 @@ extension OWSHTTPError: HTTPError { // NOTE: This function should only be called from NetworkManager.swiftHTTPStatusCodeForError. public var responseStatusCode: Int { switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return 0 case .serviceResponse(let serviceResponse): return Int(serviceResponse.responseStatus) @@ -156,7 +161,7 @@ extension OWSHTTPError: HTTPError { public var responseHeaders: OWSHttpHeaders? { switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return nil case .serviceResponse(let serviceResponse): return serviceResponse.responseHeaders @@ -165,7 +170,7 @@ extension OWSHTTPError: HTTPError { public var responseError: Error? { switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return nil case .serviceResponse(let serviceResponse): return serviceResponse.responseError @@ -174,7 +179,7 @@ extension OWSHTTPError: HTTPError { public var responseBodyData: Data? { switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return nil case .serviceResponse(let serviceResponse): return serviceResponse.responseData @@ -188,7 +193,7 @@ extension OWSHTTPError: HTTPError { } switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return nil case .serviceResponse(let serviceResponse): return serviceResponse.customRetryAfterDate @@ -197,7 +202,7 @@ extension OWSHTTPError: HTTPError { public var customLocalizedDescription: String? { switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return nil case .serviceResponse(let serviceResponse): return serviceResponse.customLocalizedDescription @@ -206,7 +211,7 @@ extension OWSHTTPError: HTTPError { public var customLocalizedRecoverySuggestion: String? { switch self { - case .invalidAppState, .invalidRequest, .networkFailure: + case .invalidAppState, .invalidRequest, .invalidResponse, .networkFailure: return nil case .serviceResponse(let serviceResponse): return serviceResponse.customLocalizedRecoverySuggestion @@ -216,9 +221,7 @@ extension OWSHTTPError: HTTPError { // NOTE: This function should only be called from NetworkManager.isSwiftNetworkConnectivityError. public var isNetworkConnectivityError: Bool { switch self { - case .invalidAppState: - return false - case .invalidRequest: + case .invalidAppState, .invalidRequest, .invalidResponse: return false case .networkFailure: return true @@ -319,3 +322,122 @@ public extension Error { } } } + +// MARK: - + +// TODO: Rename. +@objc +public class OWSHTTPResponseImpl: NSObject { + + @objc + public let requestUrl: URL + + @objc + public let status: UInt32 + + @objc + public let headers: OWSHttpHeaders + + @objc + public let bodyData: Data? + + public let stringEncoding: String.Encoding + + private struct JSONValue { + let json: Any? + } + + // This property should only be accessed with unfairLock acquired. + // + // TODO: Type? + private var jsonValue: JSONValue? + + private static let unfairLock = UnfairLock() + + public required init(requestUrl: URL, + status: UInt32, + headers: OWSHttpHeaders, + bodyData: Data?, + stringEncoding: String.Encoding = .utf8) { + self.requestUrl = requestUrl + self.status = status + self.headers = headers + self.bodyData = bodyData + self.stringEncoding = stringEncoding + } + + public static func build(requestUrl: URL, + httpUrlResponse: HTTPURLResponse, + bodyData: Data?) -> HTTPResponse { + let headers = OWSHttpHeaders(response: httpUrlResponse) + let stringEncoding: String.Encoding = httpUrlResponse.parseStringEncoding() ?? .utf8 + return OWSHTTPResponseImpl(requestUrl: requestUrl, + status: UInt32(httpUrlResponse.statusCode), + headers: headers, + bodyData: bodyData, + stringEncoding: stringEncoding) + } + + @objc + public var bodyJson: Any? { + Self.unfairLock.withLock { + if let jsonValue = self.jsonValue { + return jsonValue.json + } + let jsonValue = Self.parseJSON(data: bodyData) + self.jsonValue = jsonValue + return jsonValue.json + } + } + + private static func parseJSON(data: Data?) -> JSONValue { + guard let data = data else { + return JSONValue(json: nil) + } + do { + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSONValue(json: json) + } catch { + owsFailDebug("Could not parse JSON: \(error).") + return JSONValue(json: nil) + } + } +} + +// MARK: - + +// TODO: Modify OWSHTTPResponse to confirm to HTTPResponse as well? +extension OWSHTTPResponseImpl: HTTPResponse { + @objc + public var responseStatusCode: Int { Int(status) } + @objc + public var responseHeaders: [String: String] { headers.headers } + @objc + public var responseBodyData: Data? { bodyData } + @objc + public var responseBodyJson: Any? { bodyJson } + @objc + public var responseBodyString: String? { + guard let data = bodyData, + let string = String(data: data, encoding: stringEncoding) else { + Logger.warn("Invalid body string.") + return nil + } + return string + } +} + +// MARK: - + +extension HTTPURLResponse { + fileprivate func parseStringEncoding() -> String.Encoding? { + guard let encodingName = textEncodingName else { + return nil + } + let encoding = CFStringConvertIANACharSetNameToEncoding(encodingName as CFString) + guard encoding != kCFStringEncodingInvalidId else { + return nil + } + return String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(encoding)) + } +} diff --git a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift b/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift index 647c00d38f..7d4c0b0cca 100644 --- a/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift +++ b/SignalServiceKit/src/Network/WebSockets/OWSWebSocket.swift @@ -90,90 +90,6 @@ extension OWSWebSocket { // MARK: - -@objc -public class OWSHTTPResponseImpl: NSObject { - - @objc - public let requestUrl: URL - - @objc - public let status: UInt32 - - @objc - public let headers: OWSHttpHeaders - - @objc - public let bodyData: Data? - - // TODO: Remove? - @objc - public let message: String? - - private struct JSONValue { - let json: Any? - } - - // This property should only be accessed with unfairLock acquired. - // - // TODO: Type? - private var jsonValue: JSONValue? - - private static let unfairLock = UnfairLock() - - public required init(requestUrl: URL, - status: UInt32, - headers: OWSHttpHeaders, - bodyData: Data?, - message: String?) { - self.requestUrl = requestUrl - self.status = status - self.headers = headers - self.bodyData = bodyData - self.message = message - } - - @objc - public var bodyJson: Any? { - Self.unfairLock.withLock { - if let jsonValue = self.jsonValue { - return jsonValue.json - } - let jsonValue = Self.parseJSON(data: bodyData) - self.jsonValue = jsonValue - return jsonValue.json - } - } - - private static func parseJSON(data: Data?) -> JSONValue { - guard let data = data else { - return JSONValue(json: nil) - } - do { - let json = try JSONSerialization.jsonObject(with: data, options: []) - return JSONValue(json: json) - } catch { - owsFailDebug("Could not parse JSON: \(error).") - return JSONValue(json: nil) - } - } -} - -// MARK: - - -// TODO: Modify OWSHTTPResponse to confirm to HTTPResponse as well? -extension OWSHTTPResponseImpl: HTTPResponse { - @objc - public var responseStatusCode: Int { Int(status) } - @objc - public var responseHeaders: [String: String] { headers.headers } - @objc - public var responseBodyData: Data? { bodyData } - @objc - public var responseBodyJson: Any? { bodyJson } -} - -// MARK: - - // TODO: Make this private, rename class. @objc public class SocketMessageInfo: NSObject { @@ -217,11 +133,13 @@ public class SocketMessageInfo: NSObject { headers: OWSHttpHeaders, bodyData: Data?, message: String?) { + if let message = message?.nilIfEmpty { + Logger.verbose("message: \(message)") + } let response = OWSHTTPResponseImpl(requestUrl: requestUrl, status: status, headers: headers, - bodyData: bodyData, - message: message) + bodyData: bodyData) didSucceed(response: response) } diff --git a/SignalServiceKit/src/Util/StorageService.swift b/SignalServiceKit/src/Util/StorageService.swift index df1e5fcd64..bc3b79d312 100644 --- a/SignalServiceKit/src/Util/StorageService.swift +++ b/SignalServiceKit/src/Util/StorageService.swift @@ -412,7 +412,8 @@ public struct StorageService: Dependencies { }.map(on: .global()) { (response: OWSHTTPResponse) -> StorageResponse in let status: StorageResponse.Status - switch response.statusCode { + let statusCode = response.responseStatusCode + switch statusCode { case 200: status = .success case 204: @@ -422,12 +423,12 @@ public struct StorageService: Dependencies { case 404: status = .notFound default: - let error = OWSAssertionError("Unexpected statusCode: \(response.statusCode)") - throw StorageError.networkError(statusCode: response.statusCode, underlyingError: error) + let error = OWSAssertionError("Unexpected statusCode: \(statusCode)") + throw StorageError.networkError(statusCode: statusCode, underlyingError: error) } // We should always receive response data, for some responses it will be empty. - guard let responseData = response.responseData else { + guard let responseData = response.responseBodyData else { owsFailDebug("missing response data") throw StorageError.retryableAssertion }