Skip to content

Add head property to FileDownloadDelegate's Progress/Response struct #811

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Feb 18, 2025
27 changes: 25 additions & 2 deletions Sources/AsyncHTTPClient/FileDownloadDelegate.swift
Original file line number Diff line number Diff line change
@@ -19,13 +19,34 @@ import NIOPosix
/// Handles a streaming download to a given file path, allowing headers and progress to be reported.
public final class FileDownloadDelegate: HTTPClientResponseDelegate {
/// The response type for this delegate: the total count of bytes as reported by the response
/// "Content-Length" header (if available) and the count of bytes downloaded.
/// "Content-Length" header (if available), the count of bytes downloaded, and the
/// response head.
public struct Progress: Sendable {
public var totalBytes: Int?
public var receivedBytes: Int

public var head: HTTPResponseHead {
get {
assert(self._head != nil)
return self._head!
}
set {
self._head = newValue
}
}

fileprivate var _head: HTTPResponseHead? = nil

internal init(totalBytes: Int? = nil, receivedBytes: Int) {
self.totalBytes = totalBytes
self.receivedBytes = receivedBytes
}
}

private var progress = Progress(totalBytes: nil, receivedBytes: 0)
private var progress = Progress(
totalBytes: nil,
receivedBytes: 0
)

public typealias Response = Progress

@@ -133,6 +154,8 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
task: HTTPClient.Task<Response>,
_ head: HTTPResponseHead
) -> EventLoopFuture<Void> {
self.progress._head = head

self.reportHead?(task, head)

if let totalBytesString = head.headers.first(name: "Content-Length"),
46 changes: 28 additions & 18 deletions Tests/AsyncHTTPClientTests/HTTPClientTests.swift
Original file line number Diff line number Diff line change
@@ -729,51 +729,57 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
var request = try Request(url: self.defaultHTTPBinURLPrefix + "events/10/content-length")
request.headers.add(name: "Accept", value: "text/event-stream")

let progress =
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in
let response =
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Response in
let delegate = try FileDownloadDelegate(path: path)

let progress = try self.defaultClient.execute(
let response = try self.defaultClient.execute(
request: request,
delegate: delegate
)
.wait()

try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path))

return progress
return response
}

XCTAssertEqual(50, progress.totalBytes)
XCTAssertEqual(50, progress.receivedBytes)
XCTAssertEqual(.ok, response.head.status)
XCTAssertEqual("50", response.head.headers.first(name: "content-length"))

XCTAssertEqual(50, response.totalBytes)
XCTAssertEqual(50, response.receivedBytes)
}

func testFileDownloadError() throws {
var request = try Request(url: self.defaultHTTPBinURLPrefix + "not-found")
request.headers.add(name: "Accept", value: "text/event-stream")

let progress =
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in
let response =
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Response in
let delegate = try FileDownloadDelegate(
path: path,
reportHead: {
XCTAssertEqual($0.status, .notFound)
}
)

let progress = try self.defaultClient.execute(
let response = try self.defaultClient.execute(
request: request,
delegate: delegate
)
.wait()

XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path))

return progress
return response
}

XCTAssertEqual(nil, progress.totalBytes)
XCTAssertEqual(0, progress.receivedBytes)
XCTAssertEqual(.notFound, response.head.status)
XCTAssertFalse(response.head.headers.contains(name: "content-length"))

XCTAssertEqual(nil, response.totalBytes)
XCTAssertEqual(0, response.receivedBytes)
}

func testFileDownloadCustomError() throws {
@@ -3910,23 +3916,27 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
var request = try Request(url: self.defaultHTTPBinURLPrefix + "chunked")
request.headers.add(name: "Accept", value: "text/event-stream")

let progress =
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in
let response =
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Response in
let delegate = try FileDownloadDelegate(path: path)

let progress = try self.defaultClient.execute(
let response = try self.defaultClient.execute(
request: request,
delegate: delegate
)
.wait()

try XCTAssertEqual(50, TemporaryFileHelpers.fileSize(path: path))

return progress
return response
}

XCTAssertEqual(nil, progress.totalBytes)
XCTAssertEqual(50, progress.receivedBytes)
XCTAssertEqual(.ok, response.head.status)
XCTAssertEqual("chunked", response.head.headers.first(name: "transfer-encoding"))
XCTAssertFalse(response.head.headers.contains(name: "content-length"))

XCTAssertEqual(nil, response.totalBytes)
XCTAssertEqual(50, response.receivedBytes)
}

func testCloseWhileBackpressureIsExertedIsFine() throws {