A small menubar app that allows you to switch between R versions quickly (if you have multiple versions of R framework installed). https://rud.is/rswitch
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1131 lines
31 KiB

5 years ago
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
#if os(Linux)
import Dispatch
#endif
// stolen from python-requests
let statusCodeDescriptions = [
// Informational.
100: "continue",
101: "switching protocols",
102: "processing",
103: "checkpoint",
122: "uri too long",
200: "ok",
201: "created",
202: "accepted",
203: "non authoritative info",
204: "no content",
205: "reset content",
206: "partial content",
207: "multi status",
208: "already reported",
226: "im used",
// Redirection.
300: "multiple choices",
301: "moved permanently",
302: "found",
303: "see other",
304: "not modified",
305: "use proxy",
306: "switch proxy",
307: "temporary redirect",
308: "permanent redirect",
// Client Error.
400: "bad request",
401: "unauthorized",
402: "payment required",
403: "forbidden",
404: "not found",
405: "method not allowed",
406: "not acceptable",
407: "proxy authentication required",
408: "request timeout",
409: "conflict",
410: "gone",
411: "length required",
412: "precondition failed",
413: "request entity too large",
414: "request uri too large",
415: "unsupported media type",
416: "requested range not satisfiable",
417: "expectation failed",
418: "im a teapot",
422: "unprocessable entity",
423: "locked",
424: "failed dependency",
425: "unordered collection",
426: "upgrade required",
428: "precondition required",
429: "too many requests",
431: "header fields too large",
444: "no response",
449: "retry with",
450: "blocked by windows parental controls",
451: "unavailable for legal reasons",
499: "client closed request",
// Server Error.
500: "internal server error",
501: "not implemented",
502: "bad gateway",
503: "service unavailable",
504: "gateway timeout",
505: "http version not supported",
506: "variant also negotiates",
507: "insufficient storage",
509: "bandwidth limit exceeded",
510: "not extended",
]
public enum HTTPFile {
case url(URL, String?) // URL to a file, mimetype
case data(String, Foundation.Data, String?) // filename, data, mimetype
case text(String, String, String?) // filename, text, mimetype
}
// Supported request types
public enum HTTPMethod: String {
case delete = "DELETE"
case get = "GET"
case head = "HEAD"
case options = "OPTIONS"
case patch = "PATCH"
case post = "POST"
case put = "PUT"
}
extension URLResponse {
var HTTPHeaders: [String: String] {
return (self as? HTTPURLResponse)?.allHeaderFields as? [String: String]
?? [:]
}
}
public protocol URLComponentsConvertible {
var urlComponents: URLComponents? { get }
}
extension String: URLComponentsConvertible {
public var urlComponents: URLComponents? {
return URLComponents(string: self)
}
}
extension URL: URLComponentsConvertible {
public var urlComponents: URLComponents? {
return URLComponents(url: self, resolvingAgainstBaseURL: true)
}
}
/// The only reason this is not a struct is the requirements for
/// lazy evaluation of `headers` and `cookies`, which is mutating the
/// struct. This would make those properties unusable with `HTTPResult`s
/// declared with `let`
public final class HTTPResult : NSObject {
public final var content: Data?
public var response: URLResponse?
public var error: Error?
public var request: URLRequest? { return task?.originalRequest }
public var task: URLSessionTask?
public var encoding = String.Encoding.utf8
public var JSONReadingOptions = JSONSerialization.ReadingOptions(rawValue: 0)
public var reason: String {
if let code = self.statusCode, let text = statusCodeDescriptions[code] {
return text
}
if let error = self.error {
return error.localizedDescription
}
return "Unknown"
}
public var isRedirect: Bool {
if let code = self.statusCode {
return code >= 300 && code < 400
}
return false
}
public var isPermanentRedirect: Bool {
return self.statusCode == 301
}
public override var description: String {
if let status = statusCode,
let urlString = request?.url?.absoluteString,
let method = request?.httpMethod
{
return "\(method) \(urlString) \(status)"
} else {
return "<Empty>"
}
}
public init(data: Data?, response: URLResponse?, error: Error?,
task: URLSessionTask?)
{
self.content = data
self.response = response
self.error = error
self.task = task
}
public var json: Any? {
return content.flatMap {
try? JSONSerialization.jsonObject(with: $0, options: JSONReadingOptions)
}
}
public var statusCode: Int? {
return (self.response as? HTTPURLResponse)?.statusCode
}
public var text: String? {
return content.flatMap { String(data: $0, encoding: encoding) }
}
public lazy var headers: CaseInsensitiveDictionary<String, String> = {
return CaseInsensitiveDictionary<String, String>(
dictionary: self.response?.HTTPHeaders ?? [:])
}()
public lazy var cookies: [String: HTTPCookie] = {
let foundCookies: [HTTPCookie]
if let headers = self.response?.HTTPHeaders, let url = self.response?.url {
foundCookies = HTTPCookie.cookies(withResponseHeaderFields: headers,
for: url)
} else {
foundCookies = []
}
var result: [String: HTTPCookie] = [:]
for cookie in foundCookies {
result[cookie.name] = cookie
}
return result
}()
public var ok: Bool {
return statusCode != nil && !(statusCode! >= 400 && statusCode! < 600)
}
public var url: URL? {
return response?.url
}
public lazy var links: [String: [String: String]] = {
var result = [String: [String: String]]()
guard let content = self.headers["link"] else {
return result
}
content.components(separatedBy: ", ").forEach { s in
let linkComponents = s.components(separatedBy: ";")
.map { $0.trimmingCharacters(in: CharacterSet.whitespaces) }
// although a link without a rel is valid, there's no way to reference it.
if linkComponents.count > 1 {
let url = linkComponents.first!
let start = url.index(url.startIndex, offsetBy: 1)
let end = url.index(url.endIndex, offsetBy: -1)
let urlRange = start..<end
var link: [String: String] = ["url": String(url[urlRange])]
linkComponents.dropFirst().forEach { s in
if let equalIndex = s.firstIndex(of: "=") {
let componentKey = String(s[s.startIndex..<equalIndex])
let range = s.index(equalIndex, offsetBy: 1)..<s.endIndex
let value = s[range]
if value.first == "\"" && value.last == "\"" {
let start = value.index(value.startIndex, offsetBy: 1)
let end = value.index(value.endIndex, offsetBy: -1)
link[componentKey] = String(value[start..<end])
} else {
link[componentKey] = String(value)
}
}
}
if let rel = link["rel"] {
result[rel] = link
}
}
}
return result
}()
public func cancel() {
task?.cancel()
}
}
public struct CaseInsensitiveDictionary<Key: Hashable, Value>: Collection,
ExpressibleByDictionaryLiteral
{
private var _data: [Key: Value] = [:]
private var _keyMap: [String: Key] = [:]
public typealias Element = (key: Key, value: Value)
public typealias Index = DictionaryIndex<Key, Value>
public var startIndex: Index {
return _data.startIndex
}
public var endIndex: Index {
return _data.endIndex
}
public func index(after: Index) -> Index {
return _data.index(after: after)
}
public var count: Int {
assert(_data.count == _keyMap.count, "internal keys out of sync")
return _data.count
}
public var isEmpty: Bool {
return _data.isEmpty
}
public init(dictionaryLiteral elements: (Key, Value)...) {
for (key, value) in elements {
_keyMap["\(key)".lowercased()] = key
_data[key] = value
}
}
public init(dictionary: [Key: Value]) {
for (key, value) in dictionary {
_keyMap["\(key)".lowercased()] = key
_data[key] = value
}
}
public subscript (position: Index) -> Element {
return _data[position]
}
public subscript (key: Key) -> Value? {
get {
if let realKey = _keyMap["\(key)".lowercased()] {
return _data[realKey]
}
return nil
}
set(newValue) {
let lowerKey = "\(key)".lowercased()
if _keyMap[lowerKey] == nil {
_keyMap[lowerKey] = key
}
_data[_keyMap[lowerKey]!] = newValue
}
}
public func makeIterator() -> DictionaryIterator<Key, Value> {
return _data.makeIterator()
}
public var keys: Dictionary<Key, Value>.Keys {
return _data.keys
}
public var values: Dictionary<Key, Value>.Values {
return _data.values
}
}
typealias TaskID = Int
public typealias Credentials = (username: String, password: String)
public typealias TaskProgressHandler = (HTTPProgress) -> Void
typealias TaskCompletionHandler = (HTTPResult) -> Void
struct TaskConfiguration {
let credential: Credentials?
let redirects: Bool
let originalRequest: URLRequest?
var data: Data
let progressHandler: TaskProgressHandler?
let completionHandler: TaskCompletionHandler?
}
public struct JustSessionDefaults {
public var JSONReadingOptions: JSONSerialization.ReadingOptions
public var JSONWritingOptions: JSONSerialization.WritingOptions
public var headers: [String: String]
public var multipartBoundary: String
public var credentialPersistence: URLCredential.Persistence
public var encoding: String.Encoding
public var cachePolicy: NSURLRequest.CachePolicy
public init(
JSONReadingOptions: JSONSerialization.ReadingOptions =
JSONSerialization.ReadingOptions(rawValue: 0),
JSONWritingOptions: JSONSerialization.WritingOptions =
JSONSerialization.WritingOptions(rawValue: 0),
headers: [String: String] = [:],
multipartBoundary: String = "Ju5tH77P15Aw350m3",
credentialPersistence: URLCredential.Persistence = .forSession,
encoding: String.Encoding = String.Encoding.utf8,
cachePolicy: NSURLRequest.CachePolicy = .reloadIgnoringLocalCacheData)
{
self.JSONReadingOptions = JSONReadingOptions
self.JSONWritingOptions = JSONWritingOptions
self.headers = headers
self.multipartBoundary = multipartBoundary
self.encoding = encoding
self.credentialPersistence = credentialPersistence
self.cachePolicy = cachePolicy
}
}
public struct HTTPProgress {
public enum `Type` {
case upload
case download
}
public let type: Type
public let bytesProcessed: Int64
public let bytesExpectedToProcess: Int64
public var chunk: Data?
public var percent: Float {
return Float(bytesProcessed) / Float(bytesExpectedToProcess)
}
}
let errorDomain = "net.justhttp.Just"
public protocol JustAdaptor {
func request(
_ method: HTTPMethod,
url: URLComponentsConvertible,
params: [String: Any],
data: [String: Any],
json: Any?,
headers: [String: String],
files: [String: HTTPFile],
auth: Credentials?,
cookies: [String: String],
redirects: Bool,
timeout: Double?,
urlQuery: String?,
requestBody: Data?,
asyncProgressHandler: TaskProgressHandler?,
asyncCompletionHandler: ((HTTPResult) -> Void)?
) -> HTTPResult
init(session: URLSession?, defaults: JustSessionDefaults?)
}
public struct JustOf<Adaptor: JustAdaptor> {
let adaptor: Adaptor
public init(session: URLSession? = nil,
defaults: JustSessionDefaults? = nil)
{
adaptor = Adaptor(session: session, defaults: defaults)
}
}
extension JustOf {
@discardableResult
public func request(
_ method: HTTPMethod,
url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
method,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func delete(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.delete,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func get(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.get,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func head(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.head,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func options(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.options,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func patch(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.patch,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func post(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.post,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
@discardableResult
public func put(
_ url: URLComponentsConvertible,
params: [String: Any] = [:],
data: [String: Any] = [:],
json: Any? = nil,
headers: [String: String] = [:],
files: [String: HTTPFile] = [:],
auth: (String, String)? = nil,
cookies: [String: String] = [:],
allowRedirects: Bool = true,
timeout: Double? = nil,
urlQuery: String? = nil,
requestBody: Data? = nil,
asyncProgressHandler: (TaskProgressHandler)? = nil,
asyncCompletionHandler: ((HTTPResult) -> Void)? = nil
) -> HTTPResult {
return adaptor.request(
.put,
url: url,
params: params,
data: data,
json: json,
headers: headers,
files: files,
auth: auth,
cookies: cookies,
redirects: allowRedirects,
timeout: timeout,
urlQuery: urlQuery,
requestBody: requestBody,
asyncProgressHandler: asyncProgressHandler,
asyncCompletionHandler: asyncCompletionHandler
)
}
}
public final class HTTP: NSObject, URLSessionDelegate, JustAdaptor {
public init(session: URLSession? = nil,
defaults: JustSessionDefaults? = nil)
{
super.init()
if let initialSession = session {
self.session = initialSession
} else {
self.session = URLSession(configuration: URLSessionConfiguration.default,
delegate: self, delegateQueue: nil)
}
if let initialDefaults = defaults {
self.defaults = initialDefaults
} else {
self.defaults = JustSessionDefaults()
}
}
var taskConfigs: [TaskID: TaskConfiguration]=[:]
var defaults: JustSessionDefaults!
var session: URLSession!
var invalidURLError = NSError(
domain: errorDomain,
code: 0,
userInfo: [NSLocalizedDescriptionKey: "[Just] URL is invalid"]
)
var syncResultAccessError = NSError(
domain: errorDomain,
code: 1,
userInfo: [
NSLocalizedDescriptionKey:
"[Just] You are accessing asynchronous result synchronously."
]
)
func queryComponents(_ key: String, _ value: Any) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents("\(key)[\(nestedKey)]", value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents("\(key)", value)
}
} else {
components.append((
percentEncodeString(key),
percentEncodeString("\(value)"))
)
}
return components
}
func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []
for (key, value) in parameters.sorted(by: { $0.key < $1.key }) {
components += self.queryComponents(key, value)
}
return (components.map { "\($0)=\($1)" }).joined(separator: "&")
}
func percentEncodeString(_ originalObject: Any) -> String {
if originalObject is NSNull {
return "null"
} else {
var reserved = CharacterSet.urlQueryAllowed
reserved.remove(charactersIn: ": #[]@!$&'()*+, ;=")
return String(describing: originalObject)
.addingPercentEncoding(withAllowedCharacters: reserved) ?? ""
}
}
func makeTask(_ request: URLRequest, configuration: TaskConfiguration)
-> URLSessionDataTask?
{
let task = session.dataTask(with: request)
taskConfigs[task.taskIdentifier] = configuration
return task
}
func synthesizeMultipartBody(_ data: [String: Any], files: [String: HTTPFile])
-> Data?
{
var body = Data()
let boundary = "--\(self.defaults.multipartBoundary)\r\n"
.data(using: defaults.encoding)!
for (k, v) in data {
let valueToSend: Any = v is NSNull ? "null" : v
body.append(boundary)
body.append("Content-Disposition: form-data; name=\"\(k)\"\r\n\r\n"
.data(using: defaults.encoding)!)
body.append("\(valueToSend)\r\n".data(using: defaults.encoding)!)
}
for (k, v) in files {
body.append(boundary)
var partContent: Data? = nil
var partFilename: String? = nil
var partMimetype: String? = nil
switch v {
case let .url(URL, mimetype):
partFilename = URL.lastPathComponent
if let URLContent = try? Data(contentsOf: URL) {
partContent = URLContent
}
partMimetype = mimetype
case let .text(filename, text, mimetype):
partFilename = filename
if let textData = text.data(using: defaults.encoding) {
partContent = textData
}
partMimetype = mimetype
case let .data(filename, data, mimetype):
partFilename = filename
partContent = data
partMimetype = mimetype
}
if let content = partContent, let filename = partFilename {
let dispose = "Content-Disposition: form-data; name=\"\(k)\"; filename=\"\(filename)\"\r\n"
body.append(dispose.data(using: defaults.encoding)!)
if let type = partMimetype {
body.append(
"Content-Type: \(type)\r\n\r\n".data(using: defaults.encoding)!)
} else {
body.append("\r\n".data(using: defaults.encoding)!)
}
body.append(content)
body.append("\r\n".data(using: defaults.encoding)!)
}
}
if body.count > 0 {
body.append("--\(self.defaults.multipartBoundary)--\r\n"
.data(using: defaults.encoding)!)
}
return body
}
public func synthesizeRequest(
_ method: HTTPMethod,
url: URLComponentsConvertible,
params: [String: Any],
data: [String: Any],
json: Any?,
headers: CaseInsensitiveDictionary<String, String>,
files: [String: HTTPFile],
auth: Credentials?,
timeout: Double?,
urlQuery: String?,
requestBody: Data?
) -> URLRequest? {
if var urlComponents = url.urlComponents {
let queryString = query(params)
if queryString.count > 0 {
urlComponents.percentEncodedQuery = queryString
}
var finalHeaders = headers
var contentType: String? = nil
var body: Data?
if let requestData = requestBody {
body = requestData
} else if files.count > 0 {
body = synthesizeMultipartBody(data, files: files)
let bound = self.defaults.multipartBoundary
contentType = "multipart/form-data; boundary=\(bound)"
} else {
if let requestJSON = json {
contentType = "application/json"
body = try? JSONSerialization.data(withJSONObject: requestJSON,
options: defaults.JSONWritingOptions)
} else {
if data.count > 0 {
// assume user wants JSON if she is using this header
if headers["content-type"]?.lowercased() == "application/json" {
body = try? JSONSerialization.data(withJSONObject: data,
options: defaults.JSONWritingOptions)
} else {
contentType = "application/x-www-form-urlencoded"
body = query(data).data(using: defaults.encoding)
}
}
}
}
if let contentTypeValue = contentType {
finalHeaders["Content-Type"] = contentTypeValue
}
if let auth = auth,
let utf8 = "\(auth.0):\(auth.1)".data(using: String.Encoding.utf8)
{
finalHeaders["Authorization"] = "Basic \(utf8.base64EncodedString())"
}
if let URL = urlComponents.url {
var request = URLRequest(url: URL)
request.cachePolicy = defaults.cachePolicy
request.httpBody = body
request.httpMethod = method.rawValue
if let requestTimeout = timeout {
request.timeoutInterval = requestTimeout
}
for (k, v) in defaults.headers {
request.addValue(v, forHTTPHeaderField: k)
}
for (k, v) in finalHeaders {
request.addValue(v, forHTTPHeaderField: k)
}
return request
}
}
return nil
}
public func request(
_ method: HTTPMethod,
url: URLComponentsConvertible,
params: [String: Any],
data: [String: Any],
json: Any?,
headers: [String: String],
files: [String: HTTPFile],
auth: Credentials?,
cookies: [String: String],
redirects: Bool,
timeout: Double?,
urlQuery: String?,
requestBody: Data?,
asyncProgressHandler: TaskProgressHandler?,
asyncCompletionHandler: ((HTTPResult) -> Void)?) -> HTTPResult {
let isSynchronous = asyncCompletionHandler == nil
let semaphore = DispatchSemaphore(value: 0)
var requestResult: HTTPResult = HTTPResult(data: nil, response: nil,
error: syncResultAccessError, task: nil)
let caseInsensitiveHeaders = CaseInsensitiveDictionary<String, String>(
dictionary: headers)
guard let request = synthesizeRequest(method, url: url,
params: params, data: data, json: json, headers: caseInsensitiveHeaders,
files: files, auth: auth, timeout: timeout, urlQuery: urlQuery,
requestBody: requestBody) else
{
let erronousResult = HTTPResult(data: nil, response: nil,
error: invalidURLError, task: nil)
if let handler = asyncCompletionHandler {
handler(erronousResult)
}
return erronousResult
}
addCookies(request.url!, newCookies: cookies)
let config = TaskConfiguration(
credential: auth,
redirects: redirects,
originalRequest: request,
data: Data(),
progressHandler: asyncProgressHandler)
{ result in
if let handler = asyncCompletionHandler {
handler(result)
}
if isSynchronous {
requestResult = result
semaphore.signal()
}
}
if let task = makeTask(request, configuration: config) {
task.resume()
}
if isSynchronous {
let timeout = timeout.flatMap { DispatchTime.now() + $0 }
?? DispatchTime.distantFuture
_ = semaphore.wait(timeout: timeout)
return requestResult
}
return requestResult
}
func addCookies(_ URL: Foundation.URL, newCookies: [String: String]) {
for (k, v) in newCookies {
if let cookie = HTTPCookie(properties: [
HTTPCookiePropertyKey.name: k,
HTTPCookiePropertyKey.value: v,
HTTPCookiePropertyKey.originURL: URL,
HTTPCookiePropertyKey.path: "/"
])
{
session.configuration.httpCookieStorage?.setCookie(cookie)
}
}
}
}
extension HTTP: URLSessionTaskDelegate, URLSessionDataDelegate {
public func urlSession(_ session: URLSession, task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition,
URLCredential?) -> Void)
{
var endCredential: URLCredential? = nil
if let taskConfig = taskConfigs[task.taskIdentifier],
let credential = taskConfig.credential
{
if !(challenge.previousFailureCount > 0) {
endCredential = URLCredential(
user: credential.0,
password: credential.1,
persistence: self.defaults.credentialPersistence
)
}
}
completionHandler(.useCredential, endCredential)
}
public func urlSession(_ session: URLSession, task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void)
{
if let allowRedirects = taskConfigs[task.taskIdentifier]?.redirects {
if !allowRedirects {
completionHandler(nil)
return
}
completionHandler(request)
} else {
completionHandler(request)
}
}
public func urlSession(_ session: URLSession, task: URLSessionTask,
didSendBodyData bytesSent: Int64, totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if let handler = taskConfigs[task.taskIdentifier]?.progressHandler {
handler(
HTTPProgress(
type: .upload,
bytesProcessed: totalBytesSent,
bytesExpectedToProcess: totalBytesExpectedToSend,
chunk: nil
)
)
}
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
didReceive data: Data)
{
if let handler = taskConfigs[dataTask.taskIdentifier]?.progressHandler {
handler(
HTTPProgress(
type: .download,
bytesProcessed: dataTask.countOfBytesReceived,
bytesExpectedToProcess: dataTask.countOfBytesExpectedToReceive,
chunk: data
)
)
}
if taskConfigs[dataTask.taskIdentifier]?.data != nil {
taskConfigs[dataTask.taskIdentifier]?.data.append(data)
}
}
public func urlSession(_ session: URLSession, task: URLSessionTask,
didCompleteWithError error: Error?)
{
if let config = taskConfigs[task.taskIdentifier],
let handler = config.completionHandler
{
let result = HTTPResult(
data: config.data,
response: task.response,
error: error,
task: task
)
result.JSONReadingOptions = self.defaults.JSONReadingOptions
result.encoding = self.defaults.encoding
handler(result)
}
taskConfigs.removeValue(forKey: task.taskIdentifier)
}
}
public let Just = JustOf<HTTP>()