236 lines
8.3 KiB
Swift
236 lines
8.3 KiB
Swift
//
|
|
// Transport.swift
|
|
// RSWeb
|
|
//
|
|
// Created by Maurice Parker on 5/4/19.
|
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
|
//
|
|
// Inspired by: http://robnapier.net/a-mockery-of-protocols
|
|
|
|
import Foundation
|
|
|
|
public enum TransportError: LocalizedError {
|
|
case noData
|
|
case noURL
|
|
case suspended
|
|
case httpError(status: Int)
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .httpError(let status):
|
|
switch status {
|
|
case 400:
|
|
return NSLocalizedString("Bad Request", comment: "Bad Request")
|
|
case 401:
|
|
return NSLocalizedString("Unauthorized", comment: "Unauthorized")
|
|
case 402:
|
|
return NSLocalizedString("Payment Required", comment: "Payment Required")
|
|
case 403:
|
|
return NSLocalizedString("Forbidden", comment: "Forbidden")
|
|
case 404:
|
|
return NSLocalizedString("Not Found", comment: "Not Found")
|
|
case 405:
|
|
return NSLocalizedString("Method Not Allowed", comment: "Method Not Allowed")
|
|
case 406:
|
|
return NSLocalizedString("Not Acceptable", comment: "Not Acceptable")
|
|
case 407:
|
|
return NSLocalizedString("Proxy Authentication Required", comment: "Proxy Authentication Required")
|
|
case 408:
|
|
return NSLocalizedString("Request Timeout", comment: "Request Timeout")
|
|
case 409:
|
|
return NSLocalizedString("Conflict", comment: "Conflict")
|
|
case 410:
|
|
return NSLocalizedString("Gone", comment: "Gone")
|
|
case 411:
|
|
return NSLocalizedString("Length Required", comment: "Length Required")
|
|
case 412:
|
|
return NSLocalizedString("Precondition Failed", comment: "Precondition Failed")
|
|
case 413:
|
|
return NSLocalizedString("Payload Too Large", comment: "Payload Too Large")
|
|
case 414:
|
|
return NSLocalizedString("Request-URI Too Long", comment: "Request-URI Too Long")
|
|
case 415:
|
|
return NSLocalizedString("Unsupported Media Type", comment: "Unsupported Media Type")
|
|
case 416:
|
|
return NSLocalizedString("Requested Range Not Satisfiable", comment: "Requested Range Not Satisfiable")
|
|
case 417:
|
|
return NSLocalizedString("Expectation Failed", comment: "Expectation Failed")
|
|
case 418:
|
|
return NSLocalizedString("I'm a teapot", comment: "I'm a teapot")
|
|
case 421:
|
|
return NSLocalizedString("Misdirected Request", comment: "Misdirected Request")
|
|
case 422:
|
|
return NSLocalizedString("Unprocessable Entity", comment: "Unprocessable Entity")
|
|
case 423:
|
|
return NSLocalizedString("Locked", comment: "Locked")
|
|
case 424:
|
|
return NSLocalizedString("Failed Dependency", comment: "Failed Dependency")
|
|
case 426:
|
|
return NSLocalizedString("Upgrade Required", comment: "Upgrade Required")
|
|
case 428:
|
|
return NSLocalizedString("Precondition Required", comment: "Precondition Required")
|
|
case 429:
|
|
return NSLocalizedString("Too Many Requests", comment: "Too Many Requests")
|
|
case 431:
|
|
return NSLocalizedString("Request Header Fields Too Large", comment: "Request Header Fields Too Large")
|
|
case 444:
|
|
return NSLocalizedString("Connection Closed Without Response", comment: "Connection Closed Without Response")
|
|
case 451:
|
|
return NSLocalizedString("Unavailable For Legal Reasons", comment: "Unavailable For Legal Reasons")
|
|
case 499:
|
|
return NSLocalizedString("Client Closed Request", comment: "Client Closed Request")
|
|
case 500:
|
|
return NSLocalizedString("Internal Server Error", comment: "Internal Server Error")
|
|
case 501:
|
|
return NSLocalizedString("Not Implemented", comment: "Not Implemented")
|
|
case 502:
|
|
return NSLocalizedString("Bad Gateway", comment: "Bad Gateway")
|
|
case 503:
|
|
return NSLocalizedString("Service Unavailable", comment: "Service Unavailable")
|
|
case 504:
|
|
return NSLocalizedString("Gateway Timeout", comment: "Gateway Timeout")
|
|
case 505:
|
|
return NSLocalizedString("HTTP Version Not Supported", comment: "HTTP Version Not Supported")
|
|
case 506:
|
|
return NSLocalizedString("Variant Also Negotiates", comment: "Variant Also Negotiates")
|
|
case 507:
|
|
return NSLocalizedString("Insufficient Storage", comment: "Insufficient Storage")
|
|
case 508:
|
|
return NSLocalizedString("Loop Detected", comment: "Loop Detected")
|
|
case 510:
|
|
return NSLocalizedString("Not Extended", comment: "Not Extended")
|
|
case 511:
|
|
return NSLocalizedString("Network Authentication Required", comment: "Network Authentication Required")
|
|
case 599:
|
|
return NSLocalizedString("Network Connect Timeout Error", comment: "Network Connect Timeout Error")
|
|
default:
|
|
let msg = NSLocalizedString("HTTP Status: ", comment: "Unexpected error")
|
|
return "\(msg) \(status)"
|
|
}
|
|
default:
|
|
return NSLocalizedString("An unknown network error occurred.", comment: "Unknown error")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public protocol Transport {
|
|
|
|
/// Cancels all pending requests
|
|
func cancelAll()
|
|
|
|
/// Sends URLRequest and returns the HTTP headers and the data payload.
|
|
func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void)
|
|
|
|
/// Sends URLRequest that doesn't require any result information.
|
|
func send(request: URLRequest, method: String, completion: @escaping (Result<Void, Error>) -> Void)
|
|
|
|
/// Sends URLRequest with a data payload and returns the HTTP headers and the data payload.
|
|
func send(request: URLRequest, method: String, payload: Data, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void)
|
|
|
|
}
|
|
|
|
extension URLSession: Transport {
|
|
|
|
public func cancelAll() {
|
|
getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks in
|
|
dataTasks.forEach { $0.cancel() }
|
|
uploadTasks.forEach { $0.cancel() }
|
|
downloadTasks.forEach { $0.cancel() }
|
|
}
|
|
}
|
|
|
|
public func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
|
|
let task = self.dataTask(with: request) { (data, response, error) in
|
|
DispatchQueue.main.async {
|
|
if let error = error {
|
|
return completion(.failure(error))
|
|
}
|
|
|
|
guard let response = response as? HTTPURLResponse, let data = data else {
|
|
return completion(.failure(TransportError.noData))
|
|
}
|
|
|
|
switch response.forcedStatusCode {
|
|
case 200...399:
|
|
completion(.success((response, data)))
|
|
default:
|
|
completion(.failure(TransportError.httpError(status: response.forcedStatusCode)))
|
|
}
|
|
}
|
|
}
|
|
task.resume()
|
|
}
|
|
|
|
public func send(request: URLRequest, method: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
|
|
|
var sendRequest = request
|
|
sendRequest.httpMethod = method
|
|
|
|
let task = self.dataTask(with: sendRequest) { (data, response, error) in
|
|
DispatchQueue.main.async {
|
|
if let error = error {
|
|
return completion(.failure(error))
|
|
}
|
|
|
|
guard let response = response as? HTTPURLResponse else {
|
|
return completion(.failure(TransportError.noData))
|
|
}
|
|
|
|
switch response.forcedStatusCode {
|
|
case 200...399:
|
|
completion(.success(()))
|
|
default:
|
|
completion(.failure(TransportError.httpError(status: response.forcedStatusCode)))
|
|
}
|
|
}
|
|
}
|
|
task.resume()
|
|
}
|
|
|
|
public func send(request: URLRequest, method: String, payload: Data, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) {
|
|
|
|
var sendRequest = request
|
|
sendRequest.httpMethod = method
|
|
|
|
let task = self.uploadTask(with: sendRequest, from: payload) { (data, response, error) in
|
|
DispatchQueue.main.async {
|
|
if let error = error {
|
|
return completion(.failure(error))
|
|
}
|
|
|
|
guard let response = response as? HTTPURLResponse, let data = data else {
|
|
return completion(.failure(TransportError.noData))
|
|
}
|
|
|
|
switch response.forcedStatusCode {
|
|
case 200...399:
|
|
completion(.success((response, data)))
|
|
default:
|
|
completion(.failure(TransportError.httpError(status: response.forcedStatusCode)))
|
|
}
|
|
|
|
}
|
|
}
|
|
task.resume()
|
|
}
|
|
|
|
public static func webserviceTransport() -> Transport {
|
|
|
|
let sessionConfiguration = URLSessionConfiguration.default
|
|
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
|
sessionConfiguration.timeoutIntervalForRequest = 60.0
|
|
sessionConfiguration.httpShouldSetCookies = false
|
|
sessionConfiguration.httpCookieAcceptPolicy = .never
|
|
sessionConfiguration.httpMaximumConnectionsPerHost = 2
|
|
sessionConfiguration.httpCookieStorage = nil
|
|
sessionConfiguration.urlCache = nil
|
|
|
|
if let userAgentHeaders = UserAgent.headers() {
|
|
sessionConfiguration.httpAdditionalHeaders = userAgentHeaders
|
|
}
|
|
|
|
return URLSession(configuration: sessionConfiguration)
|
|
}
|
|
}
|