Skip to content

Instantly share code, notes, and snippets.

@tanner0101
Last active May 15, 2019 19:36
Show Gist options
  • Save tanner0101/dacdff63f94b62e72917f165daff253e to your computer and use it in GitHub Desktop.
Save tanner0101/dacdff63f94b62e72917f165daff253e to your computer and use it in GitHub Desktop.
Vapor 3 Streaming Download
import Vapor
public func routes(_ router: Router) throws {
router.get("streaming") { req -> Future<Response> in
// URL to test file that will be downloaded
// https://speed.hetzner.de/100MB.bin
// https://speed.hetzner.de/10GB.bin
let testFile = URL(string: "https://speed.hetzner.de/100MB.bin")!
// create an instance of our URL session delegate
let downloader = Downloader(on: req)
// create a URL session download task, delegating to our downloader
let download = URLSession(configuration: .default, delegate: downloader, delegateQueue: nil)
.downloadTask(with: testFile)
// start the task
download.resume()
// will complete once the download has completed and we
// have a path to the temporary file
return downloader.filePromise.futureResult.flatMap { filePath in
// use Request's FileIO convenience to stream the
// temporary file to the connected client
return try req.streamFile(at: filePath)
}.map { res in
// add a Content-Disposition header which will cause web
// browsers to download the file
// if the file is meant to be an image, add content type headers instead
res.http.headers.replaceOrAdd(name: .contentDisposition, value: "attachment; filename=Download.test")
return res
}
}
}
// URL session download delegate that moves the downloaded file
// to a temporary directory and completes a promise with the new file path
final class Downloader: NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
// promise that will be completed when the file is downloaded
let filePromise: Promise<String>
// creates a new download delegate, using the supplied worker for
// promise creation
init(on worker: Worker) {
self.filePromise = worker.eventLoop.newPromise(of: String.self)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// create a new file name for identifying the download
// we'll store the file in the /tmp/ directory so that it will be
// cleared automatically on reboot
// note: we may want to clear this file manually after the download
// is served or at an interval to avoid ballooning disk usage
let new = "/tmp/vapor-download-\(UUID())"
// the location supplied to this method will be invalidated after
// we return, so we must move it
try! FileManager.default.moveItem(at: location, to: URL(string: "file://" + new)!)
// complete the promise with the newly generated file name
self.filePromise.succeed(result: new)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment