@@ -14,7 +14,7 @@ public struct Stub : Equatable {
14
14
let matcher : Matcher
15
15
let builder : Builder
16
16
let uuid : NSUUID
17
-
17
+
18
18
init ( matcher: Matcher , builder: Builder ) {
19
19
self . matcher = matcher
20
20
self . builder = builder
@@ -30,36 +30,38 @@ var stubs = [Stub]()
30
30
31
31
public class MockingjayProtocol : NSURLProtocol {
32
32
// MARK: Stubs
33
-
33
+ private var enableDownloading = true
34
+ private let operationQueue = NSOperationQueue ( )
35
+
34
36
class func addStub( stub: Stub ) -> Stub {
35
37
stubs. append ( stub)
36
-
38
+
37
39
var token : dispatch_once_t = 0
38
40
dispatch_once ( & token) {
39
41
NSURLProtocol . registerClass ( self )
40
42
return
41
43
}
42
-
44
+
43
45
return stub
44
46
}
45
-
47
+
46
48
/// Register a matcher and a builder as a new stub
47
49
public class func addStub( matcher: Matcher , builder: Builder ) -> Stub {
48
50
return addStub ( Stub ( matcher: matcher, builder: builder) )
49
51
}
50
-
52
+
51
53
/// Unregister the given stub
52
54
public class func removeStub( stub: Stub ) {
53
55
if let index = stubs. indexOf ( stub) {
54
56
stubs. removeAtIndex ( index)
55
57
}
56
58
}
57
-
59
+
58
60
/// Remove all registered stubs
59
61
public class func removeAllStubs( ) {
60
62
stubs. removeAll ( keepCapacity: false )
61
63
}
62
-
64
+
63
65
/// Finds the appropriate stub for a request
64
66
/// This method searches backwards though the registered requests
65
67
/// to find the last registered stub that handles the request.
@@ -69,40 +71,130 @@ public class MockingjayProtocol : NSURLProtocol {
69
71
return stub
70
72
}
71
73
}
72
-
74
+
73
75
return nil
74
76
}
75
-
77
+
76
78
// MARK: NSURLProtocol
77
-
79
+
78
80
/// Returns whether there is a registered stub handler for the given request.
79
81
override public class func canInitWithRequest( request: NSURLRequest ) -> Bool {
80
82
return stubForRequest ( request) != nil
81
83
}
82
-
84
+
83
85
override public class func canonicalRequestForRequest( request: NSURLRequest ) -> NSURLRequest {
84
86
return request
85
87
}
86
-
88
+
87
89
override public func startLoading( ) {
88
90
if let stub = MockingjayProtocol . stubForRequest ( request) {
89
91
switch stub. builder ( request) {
90
92
case . Failure( let error) :
91
93
client? . URLProtocol ( self , didFailWithError: error)
92
- case . Success( let response, let data) :
93
- client? . URLProtocol ( self , didReceiveResponse: response, cacheStoragePolicy: . NotAllowed)
94
-
95
- if let data = data {
94
+ case . Success( var response, let download) :
95
+ let headers = self . request. allHTTPHeaderFields
96
+
97
+ switch ( download) {
98
+ case . Content( var data) :
99
+ applyRangeFromHTTPHeaders ( headers, toData: & data, andUpdateResponse: & response)
96
100
client? . URLProtocol ( self , didLoadData: data)
101
+ client? . URLProtocolDidFinishLoading ( self )
102
+ case . StreamContent( data: var data, inChunksOf: let bytes) :
103
+ applyRangeFromHTTPHeaders ( headers, toData: & data, andUpdateResponse: & response)
104
+ self . download ( data, inChunksOfBytes: bytes)
105
+ return
106
+ case . NoContent:
107
+ client? . URLProtocol ( self , didReceiveResponse: response, cacheStoragePolicy: . NotAllowed)
108
+ client? . URLProtocolDidFinishLoading ( self )
97
109
}
98
-
99
- client? . URLProtocolDidFinishLoading ( self )
100
110
}
101
111
} else {
102
112
let error = NSError ( domain: NSInternalInconsistencyException, code: 0 , userInfo: [ NSLocalizedDescriptionKey: " Handling request without a matching stub. " ] )
103
113
client? . URLProtocol ( self , didFailWithError: error)
104
114
}
105
115
}
106
-
107
- override public func stopLoading( ) { }
116
+
117
+ override public func stopLoading( ) {
118
+ self . enableDownloading = false
119
+ self . operationQueue. cancelAllOperations ( )
120
+ }
121
+
122
+ // MARK: Private Methods
123
+
124
+ private func download( data: NSData ? , inChunksOfBytes bytes: Int ) {
125
+ guard let data = data else {
126
+ client? . URLProtocolDidFinishLoading ( self )
127
+ return
128
+ }
129
+ self . operationQueue. maxConcurrentOperationCount = 1
130
+ self . operationQueue. addOperationWithBlock { ( ) -> Void in
131
+ self . download ( data, fromOffset: 0 , withMaxLength: bytes)
132
+ }
133
+ }
134
+
135
+
136
+ private func download( data: NSData , fromOffset offset: Int , withMaxLength maxLength: Int ) {
137
+ guard let queue = NSOperationQueue . currentQueue ( ) else {
138
+ return
139
+ }
140
+ guard ( offset < data. length) else {
141
+ client? . URLProtocolDidFinishLoading ( self )
142
+ return
143
+ }
144
+ let length = min ( data. length - offset, maxLength)
145
+
146
+ queue. addOperationWithBlock { ( ) -> Void in
147
+ guard self . enableDownloading else {
148
+ self . enableDownloading = true
149
+ return
150
+ }
151
+
152
+ let subdata = data. subdataWithRange ( NSMakeRange ( offset, length) )
153
+ self . client? . URLProtocol ( self , didLoadData: subdata)
154
+ NSThread . sleepForTimeInterval ( 0.01 )
155
+ self . download ( data, fromOffset: offset + length, withMaxLength: maxLength)
156
+ }
157
+ }
158
+
159
+ private func extractRangeFromHTTPHeaders( headers: [ String : String ] ? ) -> NSRange ? {
160
+ guard let rangeStr = headers ? [ " Range " ] else {
161
+ return nil
162
+ }
163
+ let range = rangeStr. componentsSeparatedByString ( " = " ) [ 1 ] . componentsSeparatedByString ( " - " ) . map ( { ( str) -> Int in
164
+ Int ( str) !
165
+ } )
166
+ let loc = range [ 0 ]
167
+ let length = range [ 1 ] - loc + 1
168
+ return NSMakeRange ( loc, length)
169
+ }
170
+
171
+ private func applyRangeFromHTTPHeaders(
172
+ headers: [ String : String ] ? ,
173
+ inout toData data: NSData ,
174
+ inout andUpdateResponse response: NSURLResponse ) {
175
+ guard let range = extractRangeFromHTTPHeaders ( headers) else {
176
+ client? . URLProtocol ( self , didReceiveResponse: response, cacheStoragePolicy: . NotAllowed)
177
+ return
178
+ }
179
+ let fullLength = data. length
180
+ data = data. subdataWithRange ( range)
181
+
182
+ //Attach new headers to response
183
+ if let r = response as? NSHTTPURLResponse {
184
+ var header = r. allHeaderFields as! [ String : String ]
185
+ header [ " Content-Length " ] = String ( data. length)
186
+ header [ " Content-Range " ] = String ( range. httpRangeStringWithFullLength ( fullLength) )
187
+ response = NSHTTPURLResponse ( URL: r. URL!, statusCode: r. statusCode, HTTPVersion: nil , headerFields: header) !
188
+ }
189
+
190
+ client? . URLProtocol ( self , didReceiveResponse: response, cacheStoragePolicy: . NotAllowed)
191
+ }
192
+
108
193
}
194
+
195
+ extension NSRange {
196
+ func httpRangeStringWithFullLength( fullLength: Int ) -> String {
197
+ let endLoc = self . location + self . length - 1
198
+ return " bytes " + String( self . location) + " - " + String( endLoc) + " / " + String( fullLength)
199
+ }
200
+ }
0 commit comments