/ Sources / FoundationNetworking / URLRequest.swift
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  }