我想使用基于SwiftNIO的AsyncHTTPClient库下载一个大文件(数百兆字节) 。我希望将此文件流式传输到文件系统,同时消耗尽可能少的 RAM(理想情况下,它不应该将整个文件保留在 RAM 中),并且还能够通过显示的简单输出报告下载进度完成百分比。print
据我了解,我需要实现一个HTTPClientResponseDelegate
,但我应该使用什么确切的 API 来进行文件写入?文件写入可以被阻塞,同时仍然允许 HTTP 客户端进行吗?在这种情况下,委托代码会如何?
我想使用基于SwiftNIO的AsyncHTTPClient库下载一个大文件(数百兆字节) 。我希望将此文件流式传输到文件系统,同时消耗尽可能少的 RAM(理想情况下,它不应该将整个文件保留在 RAM 中),并且还能够通过显示的简单输出报告下载进度完成百分比。print
据我了解,我需要实现一个HTTPClientResponseDelegate
,但我应该使用什么确切的 API 来进行文件写入?文件写入可以被阻塞,同时仍然允许 HTTP 客户端进行吗?在这种情况下,委托代码会如何?
事实证明,HTTPClientResponseDelegate
它允许在其函数中返回一个未来,以允许它正确处理背压。将此方法与NonBlockingFileIO
and结合使用NIOFileHandle
,在下载文件时将文件写入磁盘并提供进度报告的委托如下所示:
import AsyncHTTPClient
import NIO
import NIOHTTP1
final class FileDownloadDelegate: HTTPClientResponseDelegate {
typealias Response = (totalBytes: Int?, receivedBytes: Int)
private var totalBytes: Int?
private var receivedBytes = 0
private let handle: NIOFileHandle
private let io: NonBlockingFileIO
private let reportProgress: (_ totalBytes: Int?, _ receivedBytes: Int) -> ()
private var writeFuture: EventLoopFuture<()>?
init(
path: String,
reportProgress: @escaping (_ totalBytes: Int?, _ receivedBytes: Int) -> ()
) throws {
handle = try NIOFileHandle(path: path, mode: .write, flags: .allowFileCreation())
let pool = NIOThreadPool(numberOfThreads: 1)
pool.start()
io = NonBlockingFileIO(threadPool: pool)
self.reportProgress = reportProgress
}
func didReceiveHead(
task: HTTPClient.Task<Response>,
_ head: HTTPResponseHead
) -> EventLoopFuture<()> {
if let totalBytesString = head.headers.first(name: "Content-Length"),
let totalBytes = Int(totalBytesString) {
self.totalBytes = totalBytes
}
return task.eventLoop.makeSucceededFuture(())
}
func didReceiveBodyPart(
task: HTTPClient.Task<Response>,
_ buffer: ByteBuffer
) -> EventLoopFuture<()> {
receivedBytes += buffer.readableBytes
reportProgress(totalBytes, receivedBytes)
let writeFuture = io.write(fileHandle: handle, buffer: buffer, eventLoop: task.eventLoop)
self.writeFuture = writeFuture
return writeFuture
}
func didFinishRequest(task: HTTPClient.Task<Response>) throws -> Response {
writeFuture?.whenComplete { [weak self] _ in
try? self?.handle.close()
self?.writeFuture = nil
}
return (totalBytes, receivedBytes)
}
}
使用此代码,下载和写入文件的过程对于大约 600MB 的下载文件消耗的 RAM 不会超过 5MB。