239 lines
8.0 KiB
Swift
239 lines
8.0 KiB
Swift
//
|
|
// NSURLSession+Rx.swift
|
|
// RxCocoa
|
|
//
|
|
// Created by Krunoslav Zaher on 3/23/15.
|
|
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
#if !RX_NO_MODULE
|
|
import RxSwift
|
|
#endif
|
|
|
|
/**
|
|
RxCocoa URL errors.
|
|
*/
|
|
public enum RxCocoaURLError
|
|
: ErrorType
|
|
, CustomDebugStringConvertible {
|
|
/**
|
|
Unknown error occurred.
|
|
*/
|
|
case Unknown
|
|
/**
|
|
Response is not NSHTTPURLResponse
|
|
*/
|
|
case NonHTTPResponse(response: NSURLResponse)
|
|
/**
|
|
Response is not successful. (not in `200 ..< 300` range)
|
|
*/
|
|
case HTTPRequestFailed(response: NSHTTPURLResponse, data: NSData?)
|
|
/**
|
|
Deserialization error.
|
|
*/
|
|
case DeserializationError(error: ErrorType)
|
|
}
|
|
|
|
public extension RxCocoaURLError {
|
|
/**
|
|
A textual representation of `self`, suitable for debugging.
|
|
*/
|
|
public var debugDescription: String {
|
|
switch self {
|
|
case .Unknown:
|
|
return "Unknown error has occurred."
|
|
case let .NonHTTPResponse(response):
|
|
return "Response is not NSHTTPURLResponse `\(response)`."
|
|
case let .HTTPRequestFailed(response, _):
|
|
return "HTTP request failed with `\(response.statusCode)`."
|
|
case let .DeserializationError(error):
|
|
return "Error during deserialization of the response: \(error)"
|
|
}
|
|
}
|
|
}
|
|
|
|
func escapeTerminalString(value: String) -> String {
|
|
return value.stringByReplacingOccurrencesOfString("\"", withString: "\\\"", options:[], range: nil)
|
|
}
|
|
|
|
func convertURLRequestToCurlCommand(request: NSURLRequest) -> String {
|
|
let method = request.HTTPMethod ?? "GET"
|
|
var returnValue = "curl -i -v -X \(method) "
|
|
|
|
if request.HTTPMethod == "POST" && request.HTTPBody != nil {
|
|
let maybeBody = NSString(data: request.HTTPBody!, encoding: NSUTF8StringEncoding) as? String
|
|
if let body = maybeBody {
|
|
returnValue += "-d \"\(body)\""
|
|
}
|
|
}
|
|
|
|
for (key, value) in request.allHTTPHeaderFields ?? [:] {
|
|
let escapedKey = escapeTerminalString((key as String) ?? "")
|
|
let escapedValue = escapeTerminalString((value as String) ?? "")
|
|
returnValue += "-H \"\(escapedKey): \(escapedValue)\" "
|
|
}
|
|
|
|
let URLString = request.URL?.absoluteString ?? "<unknown url>"
|
|
|
|
returnValue += "\"\(escapeTerminalString(URLString))\""
|
|
|
|
return returnValue
|
|
}
|
|
|
|
func convertResponseToString(data: NSData!, _ response: NSURLResponse!, _ error: NSError!, _ interval: NSTimeInterval) -> String {
|
|
let ms = Int(interval * 1000)
|
|
|
|
if let response = response as? NSHTTPURLResponse {
|
|
if 200 ..< 300 ~= response.statusCode {
|
|
return "Success (\(ms)ms): Status \(response.statusCode)"
|
|
}
|
|
else {
|
|
return "Failure (\(ms)ms): Status \(response.statusCode)"
|
|
}
|
|
}
|
|
|
|
if let error = error {
|
|
if error.domain == NSURLErrorDomain && error.code == NSURLErrorCancelled {
|
|
return "Cancelled (\(ms)ms)"
|
|
}
|
|
return "Failure (\(ms)ms): NSError > \(error)"
|
|
}
|
|
|
|
return "<Unhandled response from server>"
|
|
}
|
|
|
|
extension NSURLSession {
|
|
/**
|
|
Observable sequence of responses for URL request.
|
|
|
|
Performing of request starts after observer is subscribed and not after invoking this method.
|
|
|
|
**URL requests will be performed per subscribed observer.**
|
|
|
|
Any error during fetching of the response will cause observed sequence to terminate with error.
|
|
|
|
- parameter request: URL request.
|
|
- returns: Observable sequence of URL responses.
|
|
*/
|
|
@warn_unused_result(message="http://git.io/rxs.uo")
|
|
public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSHTTPURLResponse)> {
|
|
return Observable.create { observer in
|
|
|
|
// smart compiler should be able to optimize this out
|
|
var d: NSDate?
|
|
|
|
if Logging.URLRequests(request) {
|
|
d = NSDate()
|
|
}
|
|
|
|
let task = self.dataTaskWithRequest(request) { (data, response, error) in
|
|
|
|
if Logging.URLRequests(request) {
|
|
let interval = NSDate().timeIntervalSinceDate(d ?? NSDate())
|
|
print(convertURLRequestToCurlCommand(request))
|
|
print(convertResponseToString(data, response, error, interval))
|
|
}
|
|
|
|
guard let response = response, data = data else {
|
|
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
|
|
return
|
|
}
|
|
|
|
guard let httpResponse = response as? NSHTTPURLResponse else {
|
|
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
|
|
return
|
|
}
|
|
|
|
observer.on(.Next(data, httpResponse))
|
|
observer.on(.Completed)
|
|
}
|
|
|
|
|
|
let t = task
|
|
t.resume()
|
|
|
|
return AnonymousDisposable {
|
|
task.cancel()
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Observable sequence of response data for URL request.
|
|
|
|
Performing of request starts after observer is subscribed and not after invoking this method.
|
|
|
|
**URL requests will be performed per subscribed observer.**
|
|
|
|
Any error during fetching of the response will cause observed sequence to terminate with error.
|
|
|
|
If response is not HTTP response with status code in the range of `200 ..< 300`, sequence
|
|
will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`.
|
|
|
|
- parameter request: URL request.
|
|
- returns: Observable sequence of response data.
|
|
*/
|
|
@warn_unused_result(message="http://git.io/rxs.uo")
|
|
public func rx_data(request: NSURLRequest) -> Observable<NSData> {
|
|
return rx_response(request).map { (data, response) -> NSData in
|
|
if 200 ..< 300 ~= response.statusCode {
|
|
return data ?? NSData()
|
|
}
|
|
else {
|
|
throw RxCocoaURLError.HTTPRequestFailed(response: response, data: data)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Observable sequence of response JSON for URL request.
|
|
|
|
Performing of request starts after observer is subscribed and not after invoking this method.
|
|
|
|
**URL requests will be performed per subscribed observer.**
|
|
|
|
Any error during fetching of the response will cause observed sequence to terminate with error.
|
|
|
|
If response is not HTTP response with status code in the range of `200 ..< 300`, sequence
|
|
will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`.
|
|
|
|
If there is an error during JSON deserialization observable sequence will fail with that error.
|
|
|
|
- parameter request: URL request.
|
|
- returns: Observable sequence of response JSON.
|
|
*/
|
|
@warn_unused_result(message="http://git.io/rxs.uo")
|
|
public func rx_JSON(request: NSURLRequest) -> Observable<AnyObject> {
|
|
return rx_data(request).map { (data) -> AnyObject in
|
|
do {
|
|
return try NSJSONSerialization.JSONObjectWithData(data ?? NSData(), options: [])
|
|
} catch let error {
|
|
throw RxCocoaURLError.DeserializationError(error: error)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Observable sequence of response JSON for GET request with `URL`.
|
|
|
|
Performing of request starts after observer is subscribed and not after invoking this method.
|
|
|
|
**URL requests will be performed per subscribed observer.**
|
|
|
|
Any error during fetching of the response will cause observed sequence to terminate with error.
|
|
|
|
If response is not HTTP response with status code in the range of `200 ..< 300`, sequence
|
|
will terminate with `(RxCocoaErrorDomain, RxCocoaError.NetworkError)`.
|
|
|
|
If there is an error during JSON deserialization observable sequence will fail with that error.
|
|
|
|
- parameter URL: URL of `NSURLRequest` request.
|
|
- returns: Observable sequence of response JSON.
|
|
*/
|
|
@warn_unused_result(message="http://git.io/rxs.uo")
|
|
public func rx_JSON(URL: NSURL) -> Observable<AnyObject> {
|
|
return rx_JSON(NSURLRequest(URL: URL))
|
|
}
|
|
}
|