URLRequest.swift
1 //===----------------------------------------------------------------------===// 2 // 3 // This source file is part of the Swift.org open source project 4 // 5 // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors 6 // Licensed under Apache License v2.0 with Runtime Library Exception 7 // 8 // See http://swift.org/LICENSE.txt for license information 9 // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 // 11 //===----------------------------------------------------------------------===// 12 13 #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 14 import SwiftFoundation 15 #else 16 import Foundation 17 #endif 18 19 public struct URLRequest : ReferenceConvertible, Equatable, Hashable { 20 public typealias ReferenceType = NSURLRequest 21 public typealias CachePolicy = NSURLRequest.CachePolicy 22 public typealias NetworkServiceType = NSURLRequest.NetworkServiceType 23 24 /* 25 NSURLRequest has a fragile ivar layout that prevents the swift subclass approach here, so instead we keep an always mutable copy 26 */ 27 internal var _handle: _MutableHandle<NSMutableURLRequest> 28 29 internal mutating func _applyMutation<ReturnType>(_ whatToDo : (NSMutableURLRequest) -> ReturnType) -> ReturnType { 30 if !isKnownUniquelyReferenced(&_handle) { 31 let ref = _handle._uncopiedReference() 32 _handle = _MutableHandle(reference: ref) 33 } 34 return whatToDo(_handle._uncopiedReference()) 35 } 36 37 /// Creates and initializes a URLRequest with the given URL and cache policy. 38 /// - parameter url: The URL for the request. 39 /// - parameter cachePolicy: The cache policy for the request. Defaults to `.useProtocolCachePolicy` 40 /// - parameter timeoutInterval: The timeout interval for the request. See the commentary for the `timeoutInterval` for more information on timeout intervals. Defaults to 60.0 41 public init(url: URL, cachePolicy: CachePolicy = .useProtocolCachePolicy, timeoutInterval: TimeInterval = 60.0) { 42 _handle = _MutableHandle(adoptingReference: NSMutableURLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval)) 43 } 44 45 fileprivate init(_bridged request: NSURLRequest) { 46 _handle = _MutableHandle(reference: request.mutableCopy() as! NSMutableURLRequest) 47 } 48 49 /// The URL of the receiver. 50 public var url: URL? { 51 get { 52 return _handle.map { $0.url } 53 } 54 set { 55 _applyMutation { $0.url = newValue } 56 } 57 } 58 59 /// The cache policy of the receiver. 60 public var cachePolicy: CachePolicy { 61 get { 62 return _handle.map { $0.cachePolicy } 63 } 64 set { 65 _applyMutation { $0.cachePolicy = newValue } 66 } 67 } 68 69 //URLRequest.timeoutInterval should be given precedence over the URLSessionConfiguration.timeoutIntervalForRequest regardless of the value set, 70 // if it has been set at least once. Even though the default value is 60 ,if the user sets URLRequest.timeoutInterval 71 // to explicitly 60 then the precedence should be given to URLRequest.timeoutInterval. 72 internal var isTimeoutIntervalSet = false 73 74 /// Returns the timeout interval of the receiver. 75 /// - discussion: The timeout interval specifies the limit on the idle 76 /// interval allotted to a request in the process of loading. The "idle 77 /// interval" is defined as the period of time that has passed since the 78 /// last instance of load activity occurred for a request that is in the 79 /// process of loading. Hence, when an instance of load activity occurs 80 /// (e.g. bytes are received from the network for a request), the idle 81 /// interval for a request is reset to 0. If the idle interval ever 82 /// becomes greater than or equal to the timeout interval, the request 83 /// is considered to have timed out. This timeout interval is measured 84 /// in seconds. 85 public var timeoutInterval: TimeInterval { 86 get { 87 return _handle.map { $0.timeoutInterval } 88 } 89 set { 90 _applyMutation { $0.timeoutInterval = newValue } 91 isTimeoutIntervalSet = true 92 } 93 } 94 95 /// The main document URL associated with this load. 96 /// - discussion: This URL is used for the cookie "same domain as main 97 /// document" policy. 98 public var mainDocumentURL: URL? { 99 get { 100 return _handle.map { $0.mainDocumentURL } 101 } 102 set { 103 _applyMutation { $0.mainDocumentURL = newValue } 104 } 105 } 106 107 /// The URLRequest.NetworkServiceType associated with this request. 108 /// - discussion: This will return URLRequest.NetworkServiceType.default for requests that have 109 /// not explicitly set a networkServiceType 110 public var networkServiceType: NetworkServiceType { 111 get { 112 return _handle.map { $0.networkServiceType } 113 } 114 set { 115 _applyMutation { $0.networkServiceType = newValue } 116 } 117 } 118 119 /// `true` if the receiver is allowed to use the built in cellular radios to 120 /// satisfy the request, `false` otherwise. 121 public var allowsCellularAccess: Bool { 122 get { 123 return _handle.map { $0.allowsCellularAccess } 124 } 125 set { 126 _applyMutation { $0.allowsCellularAccess = newValue } 127 } 128 } 129 130 /// The HTTP request method of the receiver. 131 public var httpMethod: String? { 132 get { 133 return _handle.map { $0.httpMethod } 134 } 135 set { 136 _applyMutation { 137 if let value = newValue { 138 $0.httpMethod = value 139 } else { 140 $0.httpMethod = "GET" 141 } 142 } 143 } 144 } 145 146 /// A dictionary containing all the HTTP header fields of the 147 /// receiver. 148 public var allHTTPHeaderFields: [String : String]? { 149 get { 150 return _handle.map { $0.allHTTPHeaderFields } 151 } 152 set { 153 _applyMutation { $0.allHTTPHeaderFields = newValue } 154 } 155 } 156 157 /// The value which corresponds to the given header 158 /// field. Note that, in keeping with the HTTP RFC, HTTP header field 159 /// names are case-insensitive. 160 /// - parameter field: the header field name to use for the lookup (case-insensitive). 161 public func value(forHTTPHeaderField field: String) -> String? { 162 return _handle.map { $0.value(forHTTPHeaderField: field) } 163 } 164 165 /// If a value was previously set for the given header 166 /// field, that value is replaced with the given value. Note that, in 167 /// keeping with the HTTP RFC, HTTP header field names are 168 /// case-insensitive. 169 public mutating func setValue(_ value: String?, forHTTPHeaderField field: String) { 170 _applyMutation { 171 $0.setValue(value, forHTTPHeaderField: field) 172 } 173 } 174 175 /// This method provides a way to add values to header 176 /// fields incrementally. If a value was previously set for the given 177 /// header field, the given value is appended to the previously-existing 178 /// value. The appropriate field delimiter, a comma in the case of HTTP, 179 /// is added by the implementation, and should not be added to the given 180 /// value by the caller. Note that, in keeping with the HTTP RFC, HTTP 181 /// header field names are case-insensitive. 182 public mutating func addValue(_ value: String, forHTTPHeaderField field: String) { 183 _applyMutation { 184 $0.addValue(value, forHTTPHeaderField: field) 185 } 186 } 187 188 /// This data is sent as the message body of the request, as 189 /// in done in an HTTP POST request. 190 public var httpBody: Data? { 191 get { 192 return _handle.map { $0.httpBody } 193 } 194 set { 195 _applyMutation { $0.httpBody = newValue } 196 } 197 } 198 199 /// The stream is returned for examination only; it is 200 /// not safe for the caller to manipulate the stream in any way. Also 201 /// note that the HTTPBodyStream and HTTPBody are mutually exclusive - only 202 /// one can be set on a given request. Also note that the body stream is 203 /// preserved across copies, but is LOST when the request is coded via the 204 /// NSCoding protocol 205 public var httpBodyStream: InputStream? { 206 get { 207 return _handle.map { $0.httpBodyStream } 208 } 209 set { 210 _applyMutation { $0.httpBodyStream = newValue } 211 } 212 } 213 214 /// `true` if cookies will be sent with and set for this request; otherwise `false`. 215 public var httpShouldHandleCookies: Bool { 216 get { 217 return _handle.map { $0.httpShouldHandleCookies } 218 } 219 set { 220 _applyMutation { $0.httpShouldHandleCookies = newValue } 221 } 222 } 223 224 /// `true` if the receiver should transmit before the previous response 225 /// is received. `false` if the receiver should wait for the previous response 226 /// before transmitting. 227 public var httpShouldUsePipelining: Bool { 228 get { 229 return _handle.map { $0.httpShouldUsePipelining } 230 } 231 set { 232 _applyMutation { $0.httpShouldUsePipelining = newValue } 233 } 234 } 235 236 public func hash(into hasher: inout Hasher) { 237 hasher.combine(_handle.map { $0 }) 238 } 239 240 public static func ==(lhs: URLRequest, rhs: URLRequest) -> Bool { 241 return lhs._handle._uncopiedReference().isEqual(rhs._handle._uncopiedReference()) 242 } 243 244 var protocolProperties: [String: Any] { 245 get { 246 return _handle.map { $0.protocolProperties } 247 } 248 set { 249 _applyMutation { $0.protocolProperties = newValue } 250 } 251 } 252 } 253 254 extension URLRequest : CustomStringConvertible, CustomDebugStringConvertible, CustomReflectable { 255 public var description: String { 256 if let u = url { 257 return u.description 258 } else { 259 return "url: nil" 260 } 261 } 262 263 public var debugDescription: String { 264 return self.description 265 } 266 267 public var customMirror: Mirror { 268 var c: [(label: String?, value: Any)] = [] 269 c.append((label: "url", value: url as Any)) 270 c.append((label: "cachePolicy", value: cachePolicy.rawValue)) 271 c.append((label: "timeoutInterval", value: timeoutInterval)) 272 c.append((label: "mainDocumentURL", value: mainDocumentURL as Any)) 273 c.append((label: "networkServiceType", value: networkServiceType)) 274 c.append((label: "allowsCellularAccess", value: allowsCellularAccess)) 275 c.append((label: "httpMethod", value: httpMethod as Any)) 276 c.append((label: "allHTTPHeaderFields", value: allHTTPHeaderFields as Any)) 277 c.append((label: "httpBody", value: httpBody as Any)) 278 c.append((label: "httpBodyStream", value: httpBodyStream as Any)) 279 c.append((label: "httpShouldHandleCookies", value: httpShouldHandleCookies)) 280 c.append((label: "httpShouldUsePipelining", value: httpShouldUsePipelining)) 281 return Mirror(self, children: c, displayStyle: .struct) 282 } 283 } 284 285 extension URLRequest : _ObjectiveCBridgeable { 286 public static func _getObjectiveCType() -> Any.Type { 287 return NSURLRequest.self 288 } 289 290 @_semantics("convertToObjectiveC") 291 public func _bridgeToObjectiveC() -> NSURLRequest { 292 return _handle._copiedReference() 293 } 294 295 public static func _forceBridgeFromObjectiveC(_ input: NSURLRequest, result: inout URLRequest?) { 296 result = URLRequest(_bridged: input) 297 } 298 299 public static func _conditionallyBridgeFromObjectiveC(_ input: NSURLRequest, result: inout URLRequest?) -> Bool { 300 result = URLRequest(_bridged: input) 301 return true 302 } 303 304 public static func _unconditionallyBridgeFromObjectiveC(_ source: NSURLRequest?) -> URLRequest { 305 var result: URLRequest? = nil 306 _forceBridgeFromObjectiveC(source!, result: &result) 307 return result! 308 } 309 }