source/UberCore/Networking/Request.swift (108 lines of code) (raw):
//
// Request.swift
// UberRides
//
// Copyright © 2016 Uber Technologies, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
/**
* Struct that packages the response from an executed NSURLRequest.
*/
@objc(UBSDKResponse) public class Response: NSObject {
/// String representing JSON response data.
@objc public var data: Data?
/// HTTP status code of response.
@objc public var statusCode: Int
/// Response metadata.
@objc public var response: HTTPURLResponse?
/// NSError representing an optional error.
@objc public var error: UberError?
/**
Initialize a Response object.
- parameter data: Data returned from server.
- parameter response: Provides response metadata, such as HTTP headers and status code.
- parameter error: Indicates why the request failed, or nil if the request was successful.
*/
@objc public init(data: Data?, statusCode: Int, response: HTTPURLResponse?, error: UberError?) {
self.data = data
self.response = response
self.statusCode = statusCode
self.error = error
}
/**
- returns: string representation of JSON data.
*/
func toJSONString() -> String {
guard let data = data else {
return ""
}
return String(data: data, encoding: String.Encoding.utf8)!
}
}
/// Class to create and execute NSURLRequests.
public class Request {
let session: URLSession?
let endpoint: APIEndpoint
private(set) public var urlRequest: URLRequest
let serverToken: String?
let bearerToken: String?
/**
Initialize a request object.
- parameter hostURL: Host URL string for API.
- parameter session: NSURLSession to execute request with.
- parameter endpoint: UberAPI conforming endpoint.
- parameter serverToken: Developer's server token.
*/
public init?(session: URLSession?, endpoint: APIEndpoint, serverToken: String? = nil, bearerToken: String? = nil) {
guard var components = URLComponents(string: endpoint.host) else {
return nil
}
components.path = endpoint.path
components.queryItems = endpoint.query
guard let url = components.url else {
return nil
}
urlRequest = URLRequest(url: url)
urlRequest.httpMethod = endpoint.method.rawValue
urlRequest.httpBody = endpoint.body
self.session = session
self.endpoint = endpoint
self.serverToken = serverToken
self.bearerToken = bearerToken
}
/**
Adds HTTP Headers to the request.
*/
private func addHeaders() {
urlRequest.setValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding")
if let versionNumber = Bundle(for: type(of: self)).object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
urlRequest.setValue("iOS Rides SDK v\(versionNumber)", forHTTPHeaderField: "X-Uber-User-Agent")
}
if let token = bearerToken {
urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
} else if let token = serverToken {
urlRequest.setValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
var headers = endpoint.headers ?? [:]
if let contentType = endpoint.contentType {
headers["Content-Type"] = contentType
}
for (header,value) in headers {
urlRequest.setValue(value, forHTTPHeaderField: header)
}
}
/**
Prepares the NSURLRequest by adding necessary fields.
*/
public func prepare() {
addHeaders()
}
/**
Performs all steps to execute request (construct URL, add headers, etc).
- parameter completion: completion handler for returned Response.
*/
public func execute(_ completion: @escaping (_ response: Response) -> Void) {
guard let session = session else {
return
}
addHeaders()
let task = session.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
let httpResponse: HTTPURLResponse? = response as? HTTPURLResponse
var statusCode: Int = 0
var ridesError: UberError?
// Handle HTTP errors.
errorCheck: if httpResponse != nil {
statusCode = httpResponse!.statusCode
if statusCode <= 299 {
break errorCheck
}
if statusCode >= 400 && statusCode <= 499 {
ridesError = try? JSONDecoder.uberDecoder.decode(UberClientError.self, from: data!)
} else if (statusCode >= 500 && statusCode <= 599) {
ridesError = try? JSONDecoder.uberDecoder.decode(UberServerError.self, from: data!)
} else {
ridesError = try? JSONDecoder.uberDecoder.decode(UberUnknownError.self, from: data!)
}
ridesError?.status = statusCode
}
// Any other errors.
if response == nil || error != nil {
if let error = error as NSError? {
ridesError = UberUnknownError(status: error.code, code: nil, title: error.domain)
} else {
ridesError = UberUnknownError(status: -1, code: "request_error", title: "Request could not complete")
}
}
let ridesResponse = Response(data: data, statusCode: statusCode, response: httpResponse, error: ridesError)
completion(ridesResponse)
})
task.resume()
}
/**
* Cancel data tasks if needed.
*/
public func cancelTasks() {
guard let session = session else {
return
}
session.getTasksWithCompletionHandler({ data, upload, download in
for task in data {
task.cancel()
}
})
}
}