Skip to content

Commit d05bf23

Browse files
authored
Add head property to FileDownloadDelegate's Progress/Response struct (#811)
I needed a way to use a `FileDownloadDelegate` task to fish out the recommended file name. ```swift let response = try await downloadTask.get() // access content-disposition response.head.headers.first(name: "Content-Disposition") ``` The `head` property is an explicitly unwrapped optional because there is no "default value" to set it to, and it won't be accessed by the user until it's already been set anyway. This is a little inelegant, so I could change it to something like below where I fill in bogus init data, but that seems worse for some reason. ```swift public struct Progress: Sendable { public var totalBytes: Int? public var receivedBytes: Int public var head: HTTPResponseHead } private var progress = Progress( totalBytes: nil, receivedBytes: 0, head: .init( version: .init(major: 0, minor: 0), status: .badRequest ) ) ```
1 parent 333f511 commit d05bf23

File tree

2 files changed

+53
-20
lines changed

2 files changed

+53
-20
lines changed

Sources/AsyncHTTPClient/FileDownloadDelegate.swift

+25-2
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,34 @@ import NIOPosix
1919
/// Handles a streaming download to a given file path, allowing headers and progress to be reported.
2020
public final class FileDownloadDelegate: HTTPClientResponseDelegate {
2121
/// The response type for this delegate: the total count of bytes as reported by the response
22-
/// "Content-Length" header (if available) and the count of bytes downloaded.
22+
/// "Content-Length" header (if available), the count of bytes downloaded, and the
23+
/// response head.
2324
public struct Progress: Sendable {
2425
public var totalBytes: Int?
2526
public var receivedBytes: Int
27+
28+
public var head: HTTPResponseHead {
29+
get {
30+
assert(self._head != nil)
31+
return self._head!
32+
}
33+
set {
34+
self._head = newValue
35+
}
36+
}
37+
38+
fileprivate var _head: HTTPResponseHead? = nil
39+
40+
internal init(totalBytes: Int? = nil, receivedBytes: Int) {
41+
self.totalBytes = totalBytes
42+
self.receivedBytes = receivedBytes
43+
}
2644
}
2745

28-
private var progress = Progress(totalBytes: nil, receivedBytes: 0)
46+
private var progress = Progress(
47+
totalBytes: nil,
48+
receivedBytes: 0
49+
)
2950

3051
public typealias Response = Progress
3152

@@ -133,6 +154,8 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
133154
task: HTTPClient.Task<Response>,
134155
_ head: HTTPResponseHead
135156
) -> EventLoopFuture<Void> {
157+
self.progress._head = head
158+
136159
self.reportHead?(task, head)
137160

138161
if let totalBytesString = head.headers.first(name: "Content-Length"),

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+28-18
Original file line numberDiff line numberDiff line change
@@ -729,51 +729,57 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
729729
var request = try Request(url: self.defaultHTTPBinURLPrefix + "events/10/content-length")
730730
request.headers.add(name: "Accept", value: "text/event-stream")
731731

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

736-
let progress = try self.defaultClient.execute(
736+
let response = try self.defaultClient.execute(
737737
request: request,
738738
delegate: delegate
739739
)
740740
.wait()
741741

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

744-
return progress
744+
return response
745745
}
746746

747-
XCTAssertEqual(50, progress.totalBytes)
748-
XCTAssertEqual(50, progress.receivedBytes)
747+
XCTAssertEqual(.ok, response.head.status)
748+
XCTAssertEqual("50", response.head.headers.first(name: "content-length"))
749+
750+
XCTAssertEqual(50, response.totalBytes)
751+
XCTAssertEqual(50, response.receivedBytes)
749752
}
750753

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

755-
let progress =
756-
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in
758+
let response =
759+
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Response in
757760
let delegate = try FileDownloadDelegate(
758761
path: path,
759762
reportHead: {
760763
XCTAssertEqual($0.status, .notFound)
761764
}
762765
)
763766

764-
let progress = try self.defaultClient.execute(
767+
let response = try self.defaultClient.execute(
765768
request: request,
766769
delegate: delegate
767770
)
768771
.wait()
769772

770773
XCTAssertFalse(TemporaryFileHelpers.fileExists(path: path))
771774

772-
return progress
775+
return response
773776
}
774777

775-
XCTAssertEqual(nil, progress.totalBytes)
776-
XCTAssertEqual(0, progress.receivedBytes)
778+
XCTAssertEqual(.notFound, response.head.status)
779+
XCTAssertFalse(response.head.headers.contains(name: "content-length"))
780+
781+
XCTAssertEqual(nil, response.totalBytes)
782+
XCTAssertEqual(0, response.receivedBytes)
777783
}
778784

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

3913-
let progress =
3914-
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Progress in
3919+
let response =
3920+
try TemporaryFileHelpers.withTemporaryFilePath { path -> FileDownloadDelegate.Response in
39153921
let delegate = try FileDownloadDelegate(path: path)
39163922

3917-
let progress = try self.defaultClient.execute(
3923+
let response = try self.defaultClient.execute(
39183924
request: request,
39193925
delegate: delegate
39203926
)
39213927
.wait()
39223928

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

3925-
return progress
3931+
return response
39263932
}
39273933

3928-
XCTAssertEqual(nil, progress.totalBytes)
3929-
XCTAssertEqual(50, progress.receivedBytes)
3934+
XCTAssertEqual(.ok, response.head.status)
3935+
XCTAssertEqual("chunked", response.head.headers.first(name: "transfer-encoding"))
3936+
XCTAssertFalse(response.head.headers.contains(name: "content-length"))
3937+
3938+
XCTAssertEqual(nil, response.totalBytes)
3939+
XCTAssertEqual(50, response.receivedBytes)
39303940
}
39313941

39323942
func testCloseWhileBackpressureIsExertedIsFine() throws {

0 commit comments

Comments
 (0)