Last active
May 15, 2019 19:36
-
-
Save tanner0101/dacdff63f94b62e72917f165daff253e to your computer and use it in GitHub Desktop.
Vapor 3 Streaming Download
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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