Skip to content

Commit 5f48836

Browse files
authored
Merge pull request #2108 from drodriguez/fix-urlsession-gzipped-responses
2 parents 0034526 + 770a8f5 commit 5f48836

File tree

4 files changed

+73
-31
lines changed

4 files changed

+73
-31
lines changed

Foundation/URLSession/NativeProtocol.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,6 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
136136
downloadDelegate.urlSession(s, downloadTask: task, didWriteData: Int64(data.count), totalBytesWritten: task.countOfBytesReceived,
137137
totalBytesExpectedToWrite: task.countOfBytesExpectedToReceive)
138138
}
139-
if task.countOfBytesExpectedToReceive == task.countOfBytesReceived {
140-
fileHandle.closeFile()
141-
self.properties[.temporaryFileURL] = self.tempFileURL
142-
}
143139
}
144140
}
145141

@@ -245,11 +241,13 @@ internal class _NativeProtocol: URLProtocol, _EasyHandleDelegate {
245241
}
246242
self.client?.urlProtocol(self, didLoad: data)
247243
self.internalState = .taskCompleted
248-
}
249-
250-
if case .toFile(let url, let fileHandle?) = bodyDataDrain {
244+
} else if case .toFile(let url, let fileHandle?) = bodyDataDrain {
251245
self.properties[.temporaryFileURL] = url
252246
fileHandle.closeFile()
247+
} else if task is URLSessionDownloadTask {
248+
let fileHandle = try! FileHandle(forWritingTo: self.tempFileURL)
249+
fileHandle.closeFile()
250+
self.properties[.temporaryFileURL] = self.tempFileURL
253251
}
254252
self.client?.urlProtocolDidFinishLoading(self)
255253
self.internalState = .taskCompleted

Foundation/URLSession/http/HTTPURLProtocol.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,20 @@ internal class _HTTPURLProtocol: _NativeProtocol {
3232
guard let task = task else {
3333
fatalError("Received header data but no task available.")
3434
}
35-
task.countOfBytesExpectedToReceive = contentLength > 0 ? contentLength : NSURLSessionTransferSizeUnknown
3635
do {
3736
let newTS = try ts.byAppending(headerLine: data)
3837
internalState = .transferInProgress(newTS)
3938
let didCompleteHeader = !ts.isHeaderComplete && newTS.isHeaderComplete
4039
if didCompleteHeader {
4140
// The header is now complete, but wasn't before.
41+
let response = newTS.response as! HTTPURLResponse
42+
if let contentEncoding = response.allHeaderFields["Content-Encoding"] as? String,
43+
contentEncoding != "identity" {
44+
// compressed responses do not report expected size
45+
task.countOfBytesExpectedToReceive = NSURLSessionTransferSizeUnknown
46+
} else {
47+
task.countOfBytesExpectedToReceive = contentLength > 0 ? contentLength : NSURLSessionTransferSizeUnknown
48+
}
4249
didReceiveResponse()
4350
}
4451
return .proceed

TestFoundation/HTTPServer.swift

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,6 @@ class _TCPSocket {
165165
#endif
166166
return String(cString: &buffer)
167167
}
168-
169-
func split(_ str: String, _ count: Int) -> [String] {
170-
return stride(from: 0, to: str.count, by: count).map { i -> String in
171-
let startIndex = str.index(str.startIndex, offsetBy: i)
172-
let endIndex = str.index(startIndex, offsetBy: count, limitedBy: str.endIndex) ?? str.endIndex
173-
return String(str[startIndex..<endIndex])
174-
}
175-
}
176168

177169
func writeRawData(_ data: Data) throws {
178170
#if os(Windows)
@@ -202,19 +194,23 @@ class _TCPSocket {
202194
#endif
203195
}
204196

205-
func writeData(header: String, body: String, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
197+
func writeData(header: String, bodyData: Data, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
206198
_ = try _send(Array(header.utf8))
207199

208200
if let sendDelay = sendDelay, let bodyChunks = bodyChunks {
209-
let count = max(1, Int(Double(body.utf8.count) / Double(bodyChunks)))
210-
let texts = split(body, count)
211-
212-
for item in texts {
201+
let count = max(1, Int(Double(bodyData.count) / Double(bodyChunks)))
202+
for startIndex in stride(from: 0, to: bodyData.count, by: count) {
213203
Thread.sleep(forTimeInterval: sendDelay)
214-
_ = try _send(Array(item.utf8))
204+
let endIndex = min(startIndex + count, bodyData.count)
205+
try bodyData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Void in
206+
let chunk = UnsafeRawBufferPointer(rebasing: ptr[startIndex..<endIndex])
207+
_ = try _send(Array(chunk.bindMemory(to: UInt8.self)))
208+
}
215209
}
216210
} else {
217-
_ = try _send(Array(body.utf8))
211+
try bodyData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> Void in
212+
_ = try _send(Array(ptr.bindMemory(to: UInt8.self)))
213+
}
218214
}
219215
}
220216

@@ -302,7 +298,7 @@ class _HTTPServer {
302298
Thread.sleep(forTimeInterval: delay)
303299
}
304300
do {
305-
try self.socket.writeData(header: response.header, body: response.body, sendDelay: sendDelay, bodyChunks: bodyChunks)
301+
try self.socket.writeData(header: response.header, bodyData: response.bodyData, sendDelay: sendDelay, bodyChunks: bodyChunks)
306302
} catch {
307303
}
308304
}
@@ -455,14 +451,18 @@ struct _HTTPResponse {
455451
}
456452
private let responseCode: Response
457453
private let headers: String
458-
public let body: String
454+
public let bodyData: Data
459455

460-
public init(response: Response, headers: String = _HTTPUtils.EMPTY, body: String) {
456+
public init(response: Response, headers: String = _HTTPUtils.EMPTY, bodyData: Data) {
461457
self.responseCode = response
462458
self.headers = headers
463-
self.body = body
459+
self.bodyData = bodyData
464460
}
465-
461+
462+
public init(response: Response, headers: String = _HTTPUtils.EMPTY, body: String) {
463+
self.init(response: response, headers: headers, bodyData: body.data(using: .utf8)!)
464+
}
465+
466466
public var header: String {
467467
let statusLine = _HTTPUtils.VERSION + _HTTPUtils.SPACE + "\(responseCode.rawValue)" + _HTTPUtils.SPACE + "\(responseCode)"
468468
return statusLine + (headers != _HTTPUtils.EMPTY ? _HTTPUtils.CRLF + headers : _HTTPUtils.EMPTY) + _HTTPUtils.CRLF2
@@ -639,6 +639,19 @@ public class TestURLSessionServer {
639639
return httpResponse
640640

641641
}
642+
643+
if uri == "/gzipped-response" {
644+
// This is "Hello World!" gzipped.
645+
let helloWorld = Data([0x1f, 0x8b, 0x08, 0x00, 0x6d, 0xca, 0xb2, 0x5c,
646+
0x00, 0x03, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0x57,
647+
0x08, 0xcf, 0x2f, 0xca, 0x49, 0x51, 0x04, 0x00,
648+
0xa3, 0x1c, 0x29, 0x1c, 0x0c, 0x00, 0x00, 0x00])
649+
return _HTTPResponse(response: .OK,
650+
headers: ["Content-Length: \(helloWorld.count)",
651+
"Content-Encoding: gzip"].joined(separator: _HTTPUtils.CRLF),
652+
bodyData: helloWorld)
653+
}
654+
642655
return _HTTPResponse(response: .OK, body: capitals[String(uri.dropFirst())]!)
643656
}
644657

@@ -738,7 +751,7 @@ class LoopbackServerTest : XCTestCase {
738751

739752
let timeout = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + 2_000_000_000)
740753

741-
while serverPort == -2 {
754+
while serverPort == -1 {
742755
guard serverReady.wait(timeout: timeout) == .success else {
743756
fatalError("Timedout waiting for server to be ready")
744757
}

TestFoundation/TestURLSession.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ class TestURLSession : LoopbackServerTest {
1616
("test_dataTaskWithURLCompletionHandler", test_dataTaskWithURLCompletionHandler),
1717
("test_dataTaskWithURLRequestCompletionHandler", test_dataTaskWithURLRequestCompletionHandler),
1818
// ("test_dataTaskWithHttpInputStream", test_dataTaskWithHttpInputStream), - Flaky test
19+
("test_gzippedDataTask", test_gzippedDataTask),
1920
("test_downloadTaskWithURL", test_downloadTaskWithURL),
2021
("test_downloadTaskWithURLRequest", test_downloadTaskWithURLRequest),
2122
("test_downloadTaskWithRequestAndHandler", test_downloadTaskWithRequestAndHandler),
2223
("test_downloadTaskWithURLAndHandler", test_downloadTaskWithURLAndHandler),
24+
("test_gzippedDownloadTask", test_gzippedDownloadTask),
2325
("test_finishTaskAndInvalidate", test_finishTasksAndInvalidate),
2426
("test_taskError", test_taskError),
2527
("test_taskCopy", test_taskCopy),
@@ -177,7 +179,18 @@ class TestURLSession : LoopbackServerTest {
177179
task.resume()
178180
waitForExpectations(timeout: 12)
179181
}
180-
182+
183+
func test_gzippedDataTask() {
184+
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/gzipped-response"
185+
let url = URL(string: urlString)!
186+
let d = DataTask(with: expectation(description: "GET \(urlString): gzipped response"))
187+
d.run(with: url)
188+
waitForExpectations(timeout: 12)
189+
if !d.error {
190+
XCTAssertEqual(d.capital, "Hello World!")
191+
}
192+
}
193+
181194
func test_downloadTaskWithURL() {
182195
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/country.txt"
183196
let url = URL(string: urlString)!
@@ -233,7 +246,18 @@ class TestURLSession : LoopbackServerTest {
233246
task.resume()
234247
waitForExpectations(timeout: 12)
235248
}
236-
249+
250+
func test_gzippedDownloadTask() {
251+
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/gzipped-response"
252+
let url = URL(string: urlString)!
253+
let d = DownloadTask(with: expectation(description: "GET \(urlString): gzipped response"))
254+
d.run(with: url)
255+
waitForExpectations(timeout: 12)
256+
if d.totalBytesWritten != "Hello World!".utf8.count {
257+
XCTFail("Expected the gzipped-response to be the length of Hello World!")
258+
}
259+
}
260+
237261
func test_finishTasksAndInvalidate() {
238262
let urlString = "http://127.0.0.1:\(TestURLSession.serverPort)/Nepal"
239263
let invalidateExpectation = expectation(description: "Session invalidation")

0 commit comments

Comments
 (0)